Polly 韌性策略指南
用途:為 .NET 應用程式提供韌性(Resilience)和暫態故障處理能力
NuGet:
Polly(v8+)/Microsoft.Extensions.Resilience(DI 整合)授權:BSD-3-Clause
支援:.NET 6+、.NET Framework 4.7.2+
什麼是韌性策略
韌性(Resilience) 是指系統在遇到暫態故障時,能夠自動恢復正常運作的能力。暫態故障(Transient Fault) 是指會自行消失的短暫錯誤,例如:
- 網路連線瞬斷
- PLC 忙碌暫時無法回應
- 設備重啟期間的通訊中斷
- MES/ERP API 回應逾時
這些錯誤的共同特點是:過一下子再試就好了。手寫 try-catch + Thread.Sleep 重試當然可以,但一旦邏輯變複雜(指數退避、熔斷、逾時、fallback 組合),程式碼就會變得難以維護。
Polly 將這些韌性策略抽象為可組合的元件,讓你用宣告式方式定義「遇到錯誤怎麼辦」。
Polly v8 架構:ResiliencePipeline
Polly v8(2023 年發布)架構大幅改版。如果你看到 Policy.Handle<T>()、PolicyBuilder、ISyncPolicy 等 API,那是 v7 的舊 API。v8 改用 ResiliencePipeline 和 ResiliencePipelineBuilder。
v7 vs v8 核心差異
| 項目 | v7(舊) | v8(新) |
|---|---|---|
| 核心抽象 | Policy / AsyncPolicy | ResiliencePipeline |
| 建構方式 | Policy.Handle<T>().RetryAsync() | new ResiliencePipelineBuilder().AddRetry() |
| 同步/非同步 | 分開的 API | 統一 API |
| 泛型 | Policy<T> / AsyncPolicy<T> | ResiliencePipeline<T> |
| DI 整合 | 需額外包裝 | 原生 AddResiliencePipeline |
| 策略名稱 | Policy | Strategy |
| 組合方式 | Policy.Wrap | ResiliencePipelineBuilder 鏈式呼叫 |
ResiliencePipeline 概念
ResiliencePipeline 是一個由多個策略組成的管道。請求依序通過每個策略,每個策略都可以決定如何處理錯誤。下圖示意一個同時包含 Retry、CircuitBreaker、Timeout、Fallback 的典型管道:
各策略的職責:
- Retry:在暫態錯誤時自動重試,常搭配指數退避
- CircuitBreaker:失敗率超過閾值時熔斷,避免打爆已故障的下游
- Timeout:限制單次呼叫的最長等待時間,避免執行緒被長時間佔用
- Fallback:當前面所有策略都無法恢復時,回傳備援值或執行替代邏輯
加入順序決定策略的巢狀關係:在 ResiliencePipelineBuilder 中,先加入的為外層,後加入的為內層。
Circuit Breaker 狀態機
CircuitBreaker 本身就是一個三態狀態機,理解它的狀態轉換是使用這個策略的前提:
三個狀態的行為:
| 狀態 | 行為 | 何時轉出 |
|---|---|---|
| Closed | 正常放行所有請求 | 失敗率達閾值 → Open |
| Open | 直接拒絕請求(fail-fast),不打下游 | 冷卻時間到期 → HalfOpen |
| HalfOpen | 放行少量試探請求探測下游恢復狀況 | 成功 → Closed;失敗 → Open |
關鍵設定(詳見核心策略詳解):
FailureRatio:失敗率閾值(0.0 ~ 1.0)MinimumThroughput:取樣視窗內最低請求數,避免樣本太少誤判SamplingDuration:統計失敗率的時間視窗BreakDuration:進入 Open 後的冷卻時間
Polly vs 手寫 try-catch-retry
// ❌ 手寫:邏輯散亂、難以維護
async Task<int> ReadPlcWithRetry()
{
int retries = 0;
while (true)
{
try
{
return await plcClient.ReadRegisterAsync("D100");
}
catch (CommunicationException) when (retries < 3)
{
retries++;
var delay = TimeSpan.FromSeconds(Math.Pow(2, retries));
Logger.Warn($"讀取失敗,{delay.TotalSeconds}s 後重試 ({retries}/3)");
await Task.Delay(delay);
}
}
}
// ✅ Polly:宣告式、可組合、可測試
var pipeline = new ResiliencePipelineBuilder<int>()
.AddRetry(new RetryStrategyOptions<int>
{
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
Delay = TimeSpan.FromSeconds(1),
OnRetry = args =>
{
Logger.Warn($"讀取失敗,重試 {args.AttemptNumber}/3");
return ValueTask.CompletedTask;
}
})
.Build();
var value = await pipeline.ExecuteAsync(
async ct => await plcClient.ReadRegisterAsync("D100", ct));
Polly 的優勢:
- 宣告式:策略定義與業務邏輯分離
- 可組合:Retry + Timeout + CircuitBreaker 可自由組合
- 可測試:策略可注入替換
- 可觀測:內建事件回呼(OnRetry、OnBreak 等)
- DI 友好:透過
AddResiliencePipeline註冊
安裝
核心套件
dotnet add package Polly.Core
DI 整合(推薦)
dotnet add package Microsoft.Extensions.Resilience
HTTP 韌性(呼叫 MES/ERP API)
dotnet add package Microsoft.Extensions.Http.Resilience
常用 namespace
using Polly;
using Polly.Retry;
using Polly.CircuitBreaker;
using Polly.Timeout;
using Polly.Fallback;
using Polly.RateLimiting;
using Polly.Hedging;
最小範例
// 建立韌性管道
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(1),
BackoffType = DelayBackoffType.Exponential
})
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();
// 使用管道執行操作
await pipeline.ExecuteAsync(async ct =>
{
await plcClient.ReadRegisterAsync("D100", ct);
});
本指南結構
| 頁面 | 內容 |
|---|---|
| 概觀(本頁) | ResiliencePipeline 架構、安裝、入門 |
| 核心策略詳解 | Retry、CircuitBreaker、Timeout、Fallback、RateLimiter、Hedging |
| 公司場景 Pattern | PLC 通訊管道、SECS/GEM 重試、HTTP 韌性、Rx.NET 搭配 |
| 最佳實踐 | Transient vs Permanent、閾值設定、測試策略 |