在我们实施微服务之后,服务间的调用变得异常频繁,多个服务之前可能存在互相依赖的关系,当某个服务出现故障或者是因为服务间的网络出现故障,导致服务调用的失败,进而影响到某个业务服务处理失败,服务依赖的故障可能导致级联崩溃,如一个微服务不可用拖垮整个系统。【服务雪崩】
服务雪崩通常遵循 “从局部故障到全局崩溃” 的递进路径,可拆解为以下步骤:
服务雪崩的本质是 “故障的无限制扩散”. 就像是蝴蝶效应最后导致美国得克萨斯州的一场龙卷风
针对雪崩的形成机制,需从 “限制资源消耗”“隔离故障”“快速失败” 三个维度设计防护策略:
Polly 是 .NET 生态中一款强大的 弹性和瞬态故障处理库,主要用于处理分布式系统中常见的网络故障、超时、资源限流等问题,通过预定义的策略(如重试、熔断、超时等)提高应用程序的稳定性和容错能力。从而增强服务的可用性
超时策略可防止因长时间阻塞导致的资源耗尽或级联故障.
在 Polly 中,TimeoutStrategy 是控制超时行为的核心枚举,
定义了两种不同的超时处理机制:乐观超时(Optimistic)和悲观超时(Pessimistic)
Optimistic使用CancellationToken取消服务,默认是使用乐观超时的处理机制
Pessimistic 不需要使用CancellationToken,强制终端业务逻辑,这种情形可能会导致相关联的资源没有关闭。需要对这部分逻辑做处理
策略类型 触发条件 依赖操作协作 抛出的异常类型 乐观超时 超时 + 操作响应取消 是 OperationCanceledException
悲观超时 超时(强制触发) 否 TimeoutRejectedException
///
/// Polly的超时策略【使用乐观处理机制】
///
///
///
[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);
}
///
/// Polly的超时策略【使用悲观处理机制】
///
///
///
[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 };
}
}
在分布式系统中,网络请求可能会因为临时故障(如网络抖动、服务暂时不可用)而失败。Polly 的重试策略(Retry Policy)是处理这类问题的有效工具,它可以在请求失败时自动重试,提高系统的稳定性和容错能力。
固定次数重试策略
///
/// 固定次数重试策略
///
///
///
[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 };
}
}
服务降级(Service Degradation 是一种应对系统过载或依赖服务故障的弹性策略,通过暂时牺牲部分非核心功能或服务质量,确保系统核心功能的可用性和稳定性.一般是碰到异常时会给一个默认回调的形式去代替原有的服务
///
/// 模拟从缓存中获得暂时的响应数据
///
///
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;
}
///
/// 服务降级策略【降级策略常常搭配其他策略一起使用】
///
///
///
[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);
}
Polly 的熔断策略(Circuit Breaker)用于在系统检测到持续故障时,自动断开(熔断)对特定服务的请求,防止系统资源被耗尽并快速失败。当熔断器处于开启状态时,所有请求会立即被拒绝(抛出
BrokenCircuitException
),直到经过设定的恢复期后,熔断器会进入半开状态,允许部分请求尝试恢复服务。如果这些请求成功,则关闭熔断器;否则,继续保持开启。熔断器包含三个状态
Closed(闭合):正常状态,操作允许执行。
Open(断开):熔断状态,所有操作被快速失败,不再尝试执行。
Half-Open(半开):熔断器尝试恢复,允许少量操作执行以测试依赖服务是否恢复。
Polly提供两种熔断器策略:
基于连续失败次数的熔断器(Basic Circuit Breaker)
基于高级失败率(滑动窗口)的熔断器(Advanced Circuit Breaker)
熔断器状态需要跨多个调用共享,因此通常将熔断器策略实例声明为静态或通过依赖注入容器注入(单例生命周期)。
BasicCircuitBreaker代码演示
// 静态熔断器实例(状态持久化)
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("熔断器进入半开状态,尝试恢复。");
}
);
///
/// 熔断策略[基于连续失败次数的熔断器]
///
/// 请求API
///
[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
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("熔断器进入半开状态,尝试恢复。");
});