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

11次阅读

Polly重试适用于数据库连接超时、网络抖动及sql Server错误1205/40613等瞬态故障,而非逻辑错误;应按明确异常类型和错误码配置指数退避重试,并结合超时策略与状态管理。

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

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

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

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

RetryAsync 处理常见瞬态异常

最常用的是按异常类型重试,比如对 SqlExceptionhttpRequestException 单独建策略。注意别笼统捕获 Exception,否则会把 NullReferenceException 也重试,毫无意义。

  • 推荐只重试明确已知的瞬态错误码,比如 SqlException.number 是 1205、40613、10928
  • HTTP 请求建议配合 HttpResponseMessage.IsSuccessStatusCode == false + 状态码 429、503、504 判断
  • 默认最多重试 3 次,间隔用指数退避(ExponentialBackoff),避免雪崩式重试
var retryPolicy = Policy     .Handle(ex => new[] { 1205, 40613, 10928 }.Contains(ex.Number))     .Or()     .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()     .AddPolicyHandler(Policy         .HandleResult(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 把重试包进超时里

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

text=ZqhQzanResources