分布式弹性故障处理框架——Polly(1)

1 前言之服务雪崩

在我们实施微服务之后,服务间的调用变得异常频繁,多个服务之前可能存在互相依赖的关系,当某个服务出现故障或者是因为服务间的网络出现故障,导致服务调用的失败,进而影响到某个业务服务处理失败,服务依赖的故障可能导致级联崩溃,如一个微服务不可用拖垮整个系统。【服务雪崩】

服务雪崩通常遵循 "从局部故障到全局崩溃" 的递进路径,可拆解为以下步骤:

  1. 初始故障
    某个基础服务(如数据库、缓存、第三方 API)因过载、网络中断或 bug 出现响应延迟或失败。
    例:支付服务因数据库连接池耗尽,处理请求的时间从 100ms 增至 5s。
  2. 请求堆积
    依赖该故障服务的上游服务(如订单服务)因等待响应,线程 / 连接被长时间占用,无法释放资源。
    例:订单服务调用支付服务时,每个请求都需等待 5s,而线程池容量有限(如 200 线程),很快所有线程被占满。
  3. 级联阻塞
    上游服务因资源耗尽,无法处理新请求,导致依赖它的更上游服务(如用户服务)也出现资源耗尽。
    例:用户服务调用订单服务时,因订单服务无响应,用户服务的线程也被占满,无法处理用户登录请求。
  4. 全局崩溃
    故障像多米诺骨牌一样扩散,最终整个系统的核心功能(如下单、支付、登录)全部失效。

服务雪崩的本质是 "故障的无限制扩散". 就像是蝴蝶效应最后导致美国得克萨斯州的一场龙卷风

2 如何解决服务雪崩

针对雪崩的形成机制,需从 "限制资源消耗""隔离故障""快速失败" 三个维度设计防护策略:

  1. 熔断机制(Circuit Breaker)
  • 原理:当某个服务的失败率超过阈值(如 50% 失败),暂时 "断开" 对它的调用,直接返回降级结果,避免资源浪费。
  • 效果:防止故障服务持续消耗上游资源,给故障服务恢复的时间窗口。
  1. 限流机制(Rate Limiting)
  • 原理:限制单位时间内对某个服务的请求量(如每秒 1000 次),防止瞬时流量冲垮服务。
  • 场景:针对下游服务的最大承载能力,提前设置流量阈值。
  1. 隔离机制(Isolation)
  • 原理:为不同服务的调用分配独立的资源池(线程池、连接池),避免一个服务的故障耗尽全局资源。
  • :订单服务调用支付服务时使用单独的线程池(20 线程),调用库存服务时使用另一个线程池(30 线程),即使支付服务故障,也不会影响库存服务的调用。
  1. 超时控制(Timeout)
  • 原理:为服务调用设置明确的超时时间(如 2s),超过时间直接终止请求,避免线程被无限期占用。
  • 关键:超时时间需根据下游服务的正常响应时间合理设置(通常略高于 99% 请求的处理时间)。
  1. 降级策略(Fallback)
  • 原理:当服务调用失败时,返回预设的兜底结果(而非直接报错),保证核心流程可用。
  • :推荐服务故障时,返回默认热门商品列表;支付服务超时后,返回 "支付中,请稍后查询"。

3 什么是Polly

Polly 是 .NET 生态中一款强大的 弹性和瞬态故障处理库,主要用于处理分布式系统中常见的网络故障、超时、资源限流等问题,通过预定义的策略(如重试、熔断、超时等)提高应用程序的稳定性和容错能力。从而增强服务的可用性

3.1 超时策略

超时策略可防止因长时间阻塞导致的资源耗尽或级联故障.

在 Polly 中,TimeoutStrategy 是控制超时行为的核心枚举,

定义了两种不同的超时处理机制:乐观超时(Optimistic)和悲观超时(Pessimistic)

Optimistic使用CancellationToken取消服务,默认是使用乐观超时的处理机制

Pessimistic 不需要使用CancellationToken,强制终端业务逻辑,这种情形可能会导致相关联的资源没有关闭。需要对这部分逻辑做处理

策略类型 触发条件 依赖操作协作 抛出的异常类型
乐观超时 超时 + 操作响应取消 OperationCanceledException
悲观超时 超时(强制触发) TimeoutRejectedException
csharp 复制代码
     /// <summary>
     /// Polly的超时策略【使用乐观处理机制】
     /// </summary>
     /// <param name="url"></param>
     /// <returns></returns>

     [HttpGet]
     public async Task<IActionResult> TimeOutOptimisticPolicy(string url) {
         // 示例:设置 5 秒悲观超时 超时后将执行回调函数
         // 超时策略可防止因长时间阻塞导致的资源耗尽或级联故障
         // 在 Polly 中,TimeoutStrategy 是控制超时行为的核心枚举,
         // 定义了两种不同的超时处理机制:乐观超时(Optimistic)和悲观超时(Pessimistic)
         // Optimistic使用CancellationToken取消服务,默认是使用乐观超时的处理机制
         // Pessimistic不需要使用CancellationToken取消服务

         // onTimeoutAsync回调函数是在超时发生时执行的附加操作,但它不会改变 ExecuteAsync 的返回值。
         // 这个回调主要用于日志记录、监控或其他副作用操作,而不是直接返回 HTTP 响应。
         using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
         var token = cts.Token;

         var timeOutPolicy = Policy.TimeoutAsync<IActionResult>(
             timeout: TimeSpan.FromSeconds(3),
             timeoutStrategy: TimeoutStrategy.Optimistic,
             onTimeoutAsync: (context, timespan, task) =>
             {
                 Console.WriteLine($"操作超时:{timespan.TotalSeconds}秒");
                 return Task.CompletedTask;
             }
         );

        return await  timeOutPolicy. ExecuteAsync(async token =>
        {
            try
            {
                using (HttpClient client = new HttpClient())
                {
                    HttpResponseMessage message = await client.GetAsync(url, token);
                    //序列化响应结果
                    string resContent = await message.Content.ReadAsStringAsync();
                    return new OkObjectResult(resContent);

                }

            }
            //任务由于超时异常被取消 ,在任务内部抛出 TaskCanceledException 异常
            catch (TaskCanceledException e) {
                return new ObjectResult(new ReturnMessageModel<string>
                {
                    Code = "408",
                    Message = "操作超时",
                })
                { StatusCode = 408 };
            }
            catch (Exception e)
            {
                return new ObjectResult(new ReturnMessageModel<string>
                {
                    Code = "500",
                    Message = $"服务错误:{e.Message}" + e.GetType().Name,
                    Status = (int)HttpStatusCode.InternalServerError
                })
                { StatusCode = 500 };
            }
        }, token);
         
     }


     /// <summary>
     /// Polly的超时策略【使用悲观处理机制】
     /// </summary>
     /// <param name="url"></param>
     /// <returns></returns>

     [HttpGet]
     public async Task<IActionResult> TimeOutPessimisticPolicy(string url)
     {
         var timeOutPolicy = Policy.TimeoutAsync<IActionResult>(
             timeout: TimeSpan.FromSeconds(3),
             timeoutStrategy: TimeoutStrategy.Pessimistic,
             onTimeoutAsync: (context, timespan, task) =>
             {
                 Console.WriteLine($"操作超时:{timespan.TotalSeconds}秒");
                 return Task.CompletedTask;
             }
         );

         try
         {
             return await timeOutPolicy.ExecuteAsync(async () =>
             {
                 try
                 {
                     using (HttpClient client = new HttpClient())
                     {
                         HttpResponseMessage message = await client.GetAsync(url);
                         //序列化响应结果
                         string resContent = await message.Content.ReadAsStringAsync();
                         return new OkObjectResult(resContent);

                     }

                 }
                 catch (Exception e)
                 {
                     return new ObjectResult(new ReturnMessageModel<string>
                     {
                         Code = "500",
                         Message = $"服务错误:{e.Message}" + e.GetType().Name,
                         Status = (int)HttpStatusCode.InternalServerError
                     })
                     { StatusCode = 500 };
                 }
             });

         }
         catch (TimeoutRejectedException e) {
             // 服务超时。
             return new ObjectResult(new ReturnMessageModel<string>
             {
                 Code = "408",
                 Message = "操作超时",
             })
             { StatusCode = 408 };
         }

     }
3.2 重试策略

在分布式系统中,网络请求可能会因为临时故障 (如网络抖动、服务暂时不可用)而失败。Polly 的重试策略(Retry Policy)是处理这类问题的有效工具,它可以在请求失败时自动重试,提高系统的稳定性和容错能力

固定次数重试策略

csharp 复制代码
  /// <summary>
  /// 固定次数重试策略
  /// </summary>
  /// <param name="url"></param>
  /// <returns></returns>
  [HttpGet]
  public async Task<IActionResult> RetryPolicy(string url) {
      // 重试策略,对所有捕获的异常进行重试次数策略,当重试次数<= 设定值时,均会进入回调函数逻辑
      var policy = Policy.Handle<Exception>().RetryAsync(3, (exception, retryCount) => {

          Console.WriteLine($"重试第 {retryCount} 次: {exception.Message}");
      });
      try
      {
          return await policy.ExecuteAsync(async () =>
          {

              using (HttpClient client = new HttpClient())
              {
                  HttpResponseMessage message = await client.GetAsync(url);
                  //序列化响应结果
                  string resContent = await message.Content.ReadAsStringAsync();
                  return new OkObjectResult(resContent);
              }
          });
      }
      catch (Exception e) {
          return new ObjectResult(new ReturnMessageModel<string>
          {
              Code = "500",
              Message = $"服务错误:{e.Message}" + e.GetType().Name,
              Status = (int)HttpStatusCode.InternalServerError
          })
          { StatusCode = 500 };
      }
  }
3.3 降级策略

服务降级(Service Degradation 是一种应对系统过载或依赖服务故障的弹性策略,通过暂时牺牲部分非核心功能或服务质量,确保系统核心功能的可用性和稳定性.一般是碰到异常时会给一个默认回调的形式去代替原有的服务

csharp 复制代码
        /// <summary>
        /// 模拟从缓存中获得暂时的响应数据
        /// </summary>
        /// <returns></returns>
        private async Task<HttpResponseMessage> GetDataFromRedis() {

            HttpResponseMessage httpResponse = new HttpResponseMessage();
            httpResponse.StatusCode = HttpStatusCode.OK;
            var resdata=new  ReturnMessageModel<bool>("0",0,"从缓存中获得数据",true);
            string json = JsonConvert.SerializeObject(resdata);
            httpResponse.Content = new StringContent(json, Encoding.UTF8, "application/json");
            return httpResponse;
        }

        /// <summary>
        /// 服务降级策略【降级策略常常搭配其他策略一起使用】
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<ReturnMessageModel<string>> DegradationPolicy(string url) {

            // 定义重试策略
            var retryPolicy = Policy<HttpResponseMessage>.Handle<Exception>().RetryAsync(3,
                onRetryAsync: (ex, times) => {
                    Debug.WriteLine($"当前重试次数为 {times}");
                    return Task.CompletedTask;
                });

            //  定义降级策略
            var fallbackPolicy = Policy<HttpResponseMessage>.Handle<Exception>()
                .FallbackAsync(async (cancellaction) =>
                {
                    //  该回调函数中执行降级的逻辑,比如说从缓存中获取逻辑
                    Debug.WriteLine("进行了服务降级策略");
                    return await GetDataFromRedis();
                });


            // Polly 处理策略,先重试,后降级
            // Policy.WrapAsync 包含多种执行策略
            // 在Polly中,策略的包裹顺序非常重要,因为策略的执行顺序是从最外层策略开始,逐层向内执行。
            // 当我们使用PolicyWrap时,通过Policy.WrapAsync方法组合策略,会形成一个策略管道,请求首先进入最先包裹的策略(最外层),然后依次向内传递,直到执行用户提供的原始委托。
            var policy = Policy.WrapAsync(fallbackPolicy,retryPolicy);

            // 使用降级策略去处理任务
            HttpResponseMessage res=   await policy.ExecuteAsync(async() =>
            {
                using (HttpClient client = new HttpClient())
                {
                    HttpResponseMessage message = await client.GetAsync(url);
                    return message;
                }

            });

            string resContent = await res.Content.ReadAsStringAsync();
            return new ReturnMessageModel<string>(resContent);

        }
3.4 熔断策略

Polly 的熔断策略(Circuit Breaker)用于在系统检测到持续故障时,自动断开(熔断)对特定服务的请求,防止系统资源被耗尽并快速失败。当熔断器处于开启状态时,所有请求会立即被拒绝(抛出 BrokenCircuitException),直到经过设定的恢复期后,熔断器会进入半开状态,允许部分请求尝试恢复服务。如果这些请求成功,则关闭熔断器;否则,继续保持开启。

熔断器包含三个状态

Closed(闭合):正常状态,操作允许执行。

Open(断开):熔断状态,所有操作被快速失败,不再尝试执行。

Half-Open(半开):熔断器尝试恢复,允许少量操作执行以测试依赖服务是否恢复。

Polly提供两种熔断器策略:

  1. 基于连续失败次数的熔断器(Basic Circuit Breaker)

  2. 基于高级失败率(滑动窗口)的熔断器(Advanced Circuit Breaker)

熔断器状态需要跨多个调用共享,因此通常将熔断器策略实例声明为静态或通过依赖注入容器注入(单例生命周期)。

BasicCircuitBreaker代码演示

csharp 复制代码
  // 静态熔断器实例(状态持久化)
  private static readonly AsyncCircuitBreakerPolicy _circuitBreaker = Policy
      .Handle<Exception>()
      .CircuitBreakerAsync(
          exceptionsAllowedBeforeBreaking: 4,
          durationOfBreak: TimeSpan.FromSeconds(30),
          onBreak: (ex, breakDelay) =>
          {
              Debug.WriteLine($"熔断开启!持续 {breakDelay.TotalSeconds} 秒。");

          },
          onReset: () =>
          {
              Debug.WriteLine("熔断关闭,恢复正常请求。");
          },
          onHalfOpen: () =>
          {
              Debug.WriteLine("熔断器进入半开状态,尝试恢复。");
          }
      );
csharp 复制代码
       /// <summary>
       /// 熔断策略[基于连续失败次数的熔断器]
       /// </summary>
       /// <param name="url">请求API</param>
       /// <returns></returns>
       [HttpGet]
       public async Task<ReturnMessageModel<string>> BasicCircuitBreakPolicy(string url) {
           // 重试策略
           var retryPolicy = Policy.Handle<Exception>().RetryAsync(5, (ex, qty) => {
               Thread.Sleep(100);
               Debug.WriteLine($"当前重试次数为 {qty} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
               return Task.CompletedTask;
           });


           // 先重试 再熔断 最后降级服务
           var fallbackPolicy = Policy<string>.Handle<Exception>().FallbackAsync("先熔断降级策略结果", 
               onFallbackAsync: _ => {
               Debug.WriteLine($"降级策略将要执行 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
               return Task.CompletedTask;
           }).WrapAsync(_circuitBreaker.WrapAsync(retryPolicy));


           string res = await fallbackPolicy.ExecuteAsync(async() =>
            {
                using (HttpClient client = new HttpClient())
                {
                    HttpResponseMessage message = await client.GetAsync(url);
                    string messageStr=await message.Content.ReadAsStringAsync();
                    return messageStr;
                }

           });
           return new ReturnMessageModel<string>(res); 
       
       }

Advanced Circuit Breaker代码演示,将上述的基础异常次数_circuitBreaker熔断器换成_advancedCircuitBreaker

csharp 复制代码
   private static readonly AsyncCircuitBreakerPolicy _advancedCircuitBreaker = Policy
     .Handle<Exception>()
     .AdvancedCircuitBreakerAsync(failureThreshold: 0.5, // 失败率阈值(50%)
      samplingDuration: TimeSpan.FromSeconds(10), // 采样时间窗口(10秒)
      minimumThroughput: 5, // 最小吞吐量(10秒内至少8个请求)
      durationOfBreak: TimeSpan.FromSeconds(30), // 熔断持续时间
      onBreak: (ex, breakDelay) =>
     {
          Debug.WriteLine($"熔断开启!持续 {breakDelay.TotalSeconds} 秒。");
     },
     onReset: () =>
     {
          Debug.WriteLine("熔断关闭,恢复正常请求。");
     },
     onHalfOpen: () =>
     {
           Debug.WriteLine("熔断器进入半开状态,尝试恢复。");
      });
相关推荐
sniper_fandc14 分钟前
RabbitMQ的介绍与安装
分布式·rabbitmq
许苑向上3 小时前
分布式缓存击穿以及本地击穿解决方案
java·分布式·缓存
EyeDropLyq4 小时前
从 0 到 1 掌握 自研企业级分布式 ID 发号器
分布式·架构
云淡风轻~~6 小时前
从 CSV文件的加载、分区和处理 来理解 Spark RDD
大数据·分布式·spark
Kevinyu_8 小时前
基于redis的分布式锁 lua脚本解决原子性
redis·分布式·lua
黄雪超8 小时前
Kafka——应该选择哪种Kafka?
大数据·分布式·kafka
项目題供诗8 小时前
Hadoop(二)
大数据·hadoop·分布式
武子康10 小时前
Java-74 深入浅出 RPC Dubbo Admin可视化管理 安装使用 源码编译、Docker启动
java·分布式·后端·spring·docker·rpc·dubbo
sniper_fandc10 小时前
RabbitMQ工作流程
分布式·rabbitmq