NET 平台下的一个弹性和瞬态故障处理库 Polly
Polly 是一个.NET弹性和瞬态故障处理库,它允许开发者以流畅和线程安全的方式表达重试(Retry)、断路器(Circuit Breaker)、超时(Timeout)、隔板隔离(Bulkhead Isolation)和回退策略(Fallback)等策略。非常适合用于构建容错能力更强的应用程序。
Polly 的应用场景
- 网络请求重试
- 第三方服务调用超时处理
- 服务降级
- 缓存实现以提高响应速度
Polly 的安装
可以通过 Nuget 包管理器安装 Polly:
Install-Package Polly
Polly的主要策略及使用
- 重试(Retry):当执行的方法发生异常时,可以按照指定的次数进行重试。Polly 允许你指定需要处理的异常类型,重试次数以及每次重试的回调函数。
using Polly;
//RetryForever 表示一直重试
//Retry 表示重试一次
//Retry(n) 表示重试n次
//WaitAndRetryAsync可实现等待100ms再试,还不行在等150ms再试
Policy polly = Policy.Handle<Exception>().Retry(3);
polly.Execute(() => {
Console.WriteLine("开始");
//DateTime.Now.Second%10!=0 重试错误的条件
if (DateTime.Now.Second % 10 != 0)
{
Console.WriteLine("错误");
throw new Exception("error");
}
Console.WriteLine("====完成=====");
});
- 断路器(Circuit Breaker):用于在连续的失败达到一定阈值后,停止执行操作,避免继续失败并允许服务有时间来恢复。
using Polly;
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreaker(
// 故障阈值:连续失败次数达到3次将触发断路器开启
3,
// 采样时长:30秒内的失败将被计入断路器状态
TimeSpan.FromSeconds(30),
// 断路器开启时执行的操作
onBreak: (ex, breakDelay) =>
{
Console.WriteLine($"断路器由于 {ex.Message} 打开。" +
$"等待 {breakDelay.TotalSeconds} 秒后再次尝试。");
// 可以在这里添加额外的操作,如记录日志、记录指标等。
},
// 可选:断路器重置时执行的操作
onReset: () =>
{
Console.WriteLine("断路器已重置。");
},
// 可选:断路器半开启时执行的操作
onHalfOpen: () =>
{
Console.WriteLine("断路器处于半开启状态。");
});
// 使用断路器策略的示例代码
circuitBreakerPolicy.Execute(() =>
{
var http=new HttpClient();
// 可能会抛出 HttpRequestException 的代码
var response = http.GetAsync("https://www.example.com/api/data").Result;
response.EnsureSuccessStatusCode();
var content = response.Content.ReadAsStringAsync().Result;
Console.WriteLine($"收到的响应:{content}");
});
- 超时(Timeout):用于监控任务执行的时长,如果超出指定时长则认为任务执行失败,不会继续等待结果。
Polly 中关于超时的两个策略:一个是悲观策略(Pessimistic
),一个是乐观策略(Optimistic
)。其中,悲观策略超时后会直接抛异常,而乐观策略则不会,而只是触发CancellationTokenSource.Cancel函数,需要等待委托自行终止操作。一般情况下,我们都会用悲观策略。
using Polly.Timeout;
using Polly;
// 创建一个超时策略,设置超时时间为5秒
var timeoutPolicy = Policy.TimeoutAsync(5, TimeoutStrategy.Pessimistic);
try
{
// 执行策略包裹的方法
await timeoutPolicy.ExecuteAsync(async () =>
{
// 模拟一个耗时操作,延迟6秒
Console.WriteLine("开始执行耗时操作...");
await Task.Delay(6000); // 使用异步等待
Console.WriteLine("耗时操作完成。");
});
}
catch (TimeoutRejectedException)
{
// 处理超时异常
Console.WriteLine("操作超时");
}
- 回退(Fallback):当操作注定失败时,提供一个备用方案来替代失败的操作,从而挽救失败的操作。
var httpClient = new HttpClient();
// 定义一个回退策略,当操作失败时,返回一个默认值
var fallbackPolicy = Policy<string>
.Handle<HttpRequestException>()
.FallbackAsync(async (cancellationToken) =>
{
// 这里定义回退操作,返回默认值
Console.WriteLine("Fallback action is executed.");
return await Task.FromResult("Fallback response");
});
try
{
var response = await fallbackPolicy.ExecuteAsync(async () =>
{
var result = await httpClient.GetStringAsync("https://example.com");
return result;
});
// 打印最终的响应结果
Console.WriteLine($"Response: {response}");
}
catch (Exception ex)
{
Console.WriteLine($"Request failed: {ex.Message}");
}
- 隔板隔离策略(Bulkhead Isolation):用于限制并发操作数量的模式,以防止系统过载。这种策略将系统资源分隔成多个独立的部分(隔板),每个部分可以独立地处理请求,从而限制单个部分的资源消耗,保护整体系统的稳定性。
var httpClient = new HttpClient();
// 定义一个隔板隔离策略,限制并发操作数为5,队列长度为10
var bulkheadPolicy = Policy.BulkheadAsync<HttpResponseMessage>(
maxParallelization: 5,
maxQueuingActions: 10,
onBulkheadRejectedAsync: async context =>
{
Console.WriteLine("Bulkhead isolation: Too many requests.");
await Task.CompletedTask;
});
var tasks = new Task<HttpResponseMessage>[15];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = bulkheadPolicy.ExecuteAsync(async () =>
{
var response = await httpClient.GetAsync("https://example.com");
response.EnsureSuccessStatusCode();
return response;
});
}
try
{
var responses = await Task.WhenAll(tasks);
foreach (var response in responses)
{
Console.WriteLine($"Response: {response.StatusCode}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Request failed: {ex.Message}");
}
- 缓存策略(Cache):对于数据更新周期长且频繁使用的数据,首次加载后缓存起来,后续直接从缓存中读取。
dotnet add package Polly.Caching.Memory
using Microsoft.Extensions.Caching.Memory;
using Polly;
using Polly.Caching.Memory;
var httpClient = new HttpClient();
var memoryCache = new MemoryCache(new MemoryCacheOptions());
// 定义一个缓存策略,缓存时间为60秒
var cachePolicy = Policy.CacheAsync<HttpResponseMessage>(
new MemoryCacheProvider(memoryCache),
TimeSpan.FromSeconds(60));
try
{
// 第一次请求,将缓存结果
var response1 = await cachePolicy.ExecuteAsync(async context =>
{
return await httpClient.GetAsync("https://example.com");
}, new Context("cacheKey"));
Console.WriteLine($"First request status code: {response1.StatusCode}");
// 第二次请求,将从缓存中获取结果
var response2 = await cachePolicy.ExecuteAsync(async context =>
{
return await httpClient.GetAsync("https://example.com");
}, new Context("cacheKey"));
Console.WriteLine($"Second request status code: {response2.StatusCode}");
}
catch (Exception ex)
{
Console.WriteLine($"Request failed: {ex.Message}");
}
- 策略包装策略(Policy Wrap):不同的异常需要不同的策略,策略包装允许将不同的策略组合在一起,灵活应对不同的异常情况。
var httpClient = new HttpClient();
// 定义重试策略
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timeSpan, context) =>
{
Console.WriteLine($"Retrying due to: {exception.Message}");
});
// 定义断路器策略
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(2, TimeSpan.FromSeconds(30),
onBreak: (exception, timespan) =>
{
Console.WriteLine("Circuit breaker opened");
},
onReset: () =>
{
Console.WriteLine("Circuit breaker closed");
});
// 将策略包装起来
var policyWrap = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);
try
{
var response = await policyWrap.ExecuteAsync(async () =>
{
var result = await httpClient.GetAsync("https://example.com");
result.EnsureSuccessStatusCode();
return result;
});
// 打印响应结果
Console.WriteLine($"Response: {response.StatusCode}");
}
catch (Exception ex)
{
Console.WriteLine($"Request failed: {ex.Message}");
}