c# 如何用Polly处理短暂的数据库或网络故障

来源:这里教程网 时间:2026-02-21 17:40:30 作者:

什么时候该用 Polly 重试而不是直接抛异常

数据库连接超时、网络抖动、SQL Server 的

SqlException
错误号 1205(死锁)、40613(Azure 数据库暂时不可用)这类瞬态故障,适合用 Polly 的重试策略。它不是用来掩盖逻辑错误或永久性失败(比如 SQL 语法错、主键冲突),而是给系统留出几秒喘息时间,等资源恢复。

关键判断点:错误是否可预期、短暂、大概率重试成功。如果不是,加 Polly 只会让响应更慢、日志更乱。

RetryAsync
处理常见瞬态异常

最常用的是按异常类型重试,比如对

SqlException
HttpRequestException
单独建策略。注意别笼统捕获
Exception
,否则会把
NullReferenceException
也重试,毫无意义。

推荐只重试明确已知的瞬态错误码,比如
SqlException.Number
是 1205、40613、10928
HTTP 请求建议配合
HttpResponseMessage.IsSuccessStatusCode == false
+ 状态码 429、503、504 判断
默认最多重试 3 次,间隔用指数退避(
ExponentialBackoff
),避免雪崩式重试
var retryPolicy = Policy
    .Handle<SqlException>(ex => new[] { 1205, 40613, 10928 }.Contains(ex.Number))
    .Or<HttpRequestException>()
    .WaitAndRetryAsync(
        retryCount: 3,
        sleepDurationProvider: (retryAttempt) => TimeSpan.FromMilliseconds(Math.Pow(2, retryAttempt) * 100));

如何把 Polly 策略注入到 DbContext 或 HttpClient

不要在每个

SaveChangesAsync
调用里手写
retryPolicy.ExecuteAsync(...)
,容易漏、难测、耦合重。正确做法是封装一层执行器,或利用 DI 注入策略实例。

HttpClient
:用
AddHttpClient
+
ConfigurePrimaryHttpMessageHandler
不够,得用
AddPolicyHandler
链式注册
对 EF Core:不能直接包装
DbContext
,但可以包装仓储方法,例如
IRepository.SaveChangesAsync()
内部调用
retryPolicy.ExecuteAsync(() => context.SaveChangesAsync())
务必设置
PolicyRegistry
全局管理策略,避免重复创建(Polly 策略不是轻量对象)
services.AddHttpClient<IDataApiClient>()
    .AddPolicyHandler(Policy
        .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode && 
            new[] { 429, 503, 504 }.Contains(r.StatusCode.GetHashCode()))
        .WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(200)));

为什么重试后还失败?三个最容易被忽略的点

重试本身不保证成功,很多问题藏在策略之外:

DbContext
是 Scoped 生命周期,重试时如果上下文已追踪了脏数据,第二次
SaveChangesAsync
可能因并发冲突或状态异常直接炸掉——必须确保每次重试用的是干净状态,或启用
AsNoTracking
查询
HTTP 请求体如果是流(如
StreamContent
),重试时流可能已读完,导致后续请求发空体;改用
StringContent
或手动
Seek(0)
没设
Timeout
策略兜底,单次重试耗时太久,整体请求卡死;建议组合
Policy.WrapAsync
把重试包进超时里

瞬态故障处理真正难的不是写几行重试代码,而是确认哪部分状态可安全重放、哪部分必须幂等、哪部分根本不能重试。

相关推荐

热文推荐