0

0

c# Polly 熔断、重试和降级策略在高并发中的应用

煙雲

煙雲

发布时间:2026-01-21 13:56:03

|

1017人浏览过

|

来源于php中文网

原创

polly策略应按重试→熔断→降级顺序组合,即policy.wrapasync(fallback, circuitbreaker, retry),确保重试先执行、熔断监控重试结果、降级兜底最终失败;需统一异常处理谓词、启用日志回调并单独标注context。

c# polly 熔断、重试和降级策略在高并发中的应用

熔断器在高并发下频繁触发,CircuitState 变成 Open 后请求全被拒绝

高并发时,下游服务响应变慢或超时增多,Polly 的熔断策略会快速累积失败计数,一旦达到 FailureThreshold 就跳闸。此时所有新请求都会立即抛出 BrokenCircuitException,连重试机会都没有。

关键点在于:熔断器默认不区分异常类型,HttpRequestExceptionTimeoutException 都算失败;但像 404、401 这类业务错误不该触发熔断。

  • HandleResult<t>()</t> + HttpStatusCode 判断显式排除非致命 HTTP 状态码
  • SamplingDuration 设得稍长(比如 30 秒),避免短时间毛刺导致误熔断
  • MinimumThroughput 建议设为 20+,防止低流量下因偶然失败就开闸
  • 启用 AutomaticTransition,让熔断器在 HalfOpen 状态自动试探,而不是靠定时器硬切
var circuitBreaker = Policy.HandleResult<HttpResponseMessage>(
        r => !r.IsSuccessStatusCode && r.StatusCode is not (HttpStatusCode.NotFound or HttpStatusCode.Unauthorized))
    .CircuitBreakerAsync(
        handledEventsAllowedBeforeBreaking: 10,
        durationOfBreak: TimeSpan.FromMinutes(1),
        samplingDuration: TimeSpan.FromSeconds(30),
        minimumThroughput: 20,
        automaticTransition: true);

重试策略在并发激增时引发雪崩,下游压力反而更大

多个线程/请求同时失败,若都按相同间隔重试(尤其是固定延迟),容易形成“重试风暴”,把本已吃紧的下游彻底压垮。

Polly 默认的 WaitAndRetryAsync 如果没加退避和抖动,就是典型风险点。比如 5 次重试全卡在 100ms,第 2 轮所有请求几乎同时砸过去。

  • 必须用 WaitAndRetryAsync 的指数退避重载,例如 Backoff.DecorrelatedJitterBackoffV2
  • 设置 maxRetryCount ≤ 3,高并发场景下重试次数宁少勿多
  • TimeoutException 和连接级异常优先重试,对 500 错误可考虑降级而非重试
  • 结合 Context 传递请求 ID,在日志里标记是否为重试请求,方便定位放大效应
var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .Or<TimeoutException>()
    .WaitAndRetryAsync(
        retryCount: 3,
        sleepDurationProvider: (retryAttempt, context) =>
            Backoff.DecorrelatedJitterBackoffV2(
                medianFirstRetryDelay: TimeSpan.FromMilliseconds(100),
                retryCount: 3)[retryAttempt]);

降级逻辑写在 FallbackAsync 里,但实际没生效

常见误区是把降级当成“兜底打印日志”或返回空对象,结果上游调用方没做 null 检查直接 NRE;或者降级函数本身也抛异常,导致 fallback 链路中断。

What-the-Diff
What-the-Diff

检查请求差异,自动生成更改描述

下载

更隐蔽的问题是:fallback 执行时仍处于原始请求的 CancellationToken 生命周期内,如果原请求已超时,fallback 可能被取消——而你根本没意识到它没跑完。

  • fallback 函数体内必须用 try/catch 包住所有逻辑,尤其涉及 IO 或外部调用
  • 不要在 fallback 里复用原请求的 CancellationToken,改用 CancellationToken.None 或独立超时控制
  • 降级返回值需与主逻辑类型严格一致,避免隐式转换失败;必要时用 ResultSelector 统一包装
  • 对核心接口,降级建议返回缓存快照(如 Redis 中的 GetAsync("fallback:user:123")),而非硬编码默认值
var fallbackPolicy = Policy<string>
    .Handle<Exception>()
    .FallbackAsync(
        fallbackAction: async (ct) =>
        {
            try
            {
                // 注意:这里用 CancellationToken.None,避免被上游超时干扰
                return await _cache.GetStringAsync("fallback:config", CancellationToken.None) 
                       ?? "default_config";
            }
            catch
            {
                return "fallback_failed"; // 真正的保底
            }
        },
        onFallbackAsync: (ex, ct) => Log.Warning(ex.Exception, "Fallback triggered"));

三种策略组合后执行顺序混乱,熔断器没等重试就提前介入

Polly 策略组合不是简单叠加,而是按 WrapAsync 的嵌套顺序执行:最外层策略最先拦截,最内层最后生效。如果把熔断器包在重试外面,那只要第一次失败就进熔断,重试根本不会发生。

正确顺序永远是:重试 → 熔断 → 降级。即重试策略要最靠近业务调用,熔断器监控重试后的整体成败,降级则兜住整个链路的最终失败。

  • Policy.WrapAsync(retry, circuitBreaker, fallback) 是错的;必须是 Policy.WrapAsync(fallback, circuitBreaker, retry)
  • 所有策略的异常/结果处理谓词必须对齐,比如重试只捕获网络异常,熔断器却统计所有异常,会导致状态不一致
  • 调试时开启 onRetry/onBreak/onFallback 回调,打日志确认各策略触发时机和上下文
  • 高并发下建议给每个策略单独配 Context 标签(如 "retry-v1"),避免日志混在一起无法归因

真正容易被忽略的是:当重试策略内部抛出未被捕获的异常(比如自定义策略里忘了 await),整个 wrapper 会直接崩溃,熔断和降级全部失效——这种问题在线上只会在 CPU 突增时偶然暴露。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

254

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1089

2024.03.01

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1902

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

656

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2387

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

47

2026.01.19

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

常用的数据库软件
常用的数据库软件

常用的数据库软件有MySQL、Oracle、SQL Server、PostgreSQL、MongoDB、Redis、Cassandra、Hadoop、Spark和Amazon DynamoDB。更多关于数据库软件的内容详情请看本专题下面的文章。php中文网欢迎大家前来学习。

1005

2023.11.02

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
进程与SOCKET
进程与SOCKET

共6课时 | 0.4万人学习

Redis+MySQL数据库面试教程
Redis+MySQL数据库面试教程

共72课时 | 7.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号