什么是 Polly 的 BulkheadPolicy
?
它不是“限流”也不是“熔断”,而是限制**并发执行数量**的隔离机制:当多个请求同时调用同一外部依赖(比如数据库连接池、HTTP 客户端),
BulkheadPolicy会把它们塞进一个固定容量的“舱壁”里,超出的请求立刻失败(或排队等待,取决于配置),避免一个慢依赖拖垮整个线程池或耗尽资源。
如何创建并使用 BulkheadPolicy
?
核心是
Polly.Bulkhead.BulkheadPolicy类型,需指定最大并发数
maxParallelization和最大排队数
maxQueuingActions。注意:Polly v8+ 已将 Bulkhead 合并进主包,无需额外安装
Polly.Bulkhead子包。
var bulkhead = Policy.BulkheadAsync(
maxParallelization: 3, // 同时最多 3 个任务在执行
maxQueuingActions: 5 // 最多允许 5 个任务在队列中等待
);
await bulkhead.ExecuteAsync(async () =>
{
// 这里放可能耗时/不稳定的调用,例如:
await httpClient.GetAsync("https://api.example.com/data");
});
maxParallelization必须 ≥ 1;设为 1 就等效于串行化访问
maxQueuingActions设为 0 表示不排队,超限时直接抛
BulkheadRejectedException如果队列满且未启用排队(即
maxQueuingActions == 0),会立即失败,不会阻塞线程
为什么 ExecuteAsync
抛出 BulkheadRejectedException
却没被捕获?
这是常见疏忽:Polly 的
BulkheadPolicy在拒绝请求时抛的是
BulkheadRejectedException,不是
Exception基类。若只写
catch (Exception),这个异常会漏掉。
try
{
await bulkhead.ExecuteAsync(() => DoRiskyWork());
}
catch (BulkheadRejectedException ex) // 必须显式 catch 这个类型
{
// 处理被拒请求:返回降级值、打日志、触发告警等
Log.Warning(ex, "Request rejected by bulkhead");
return new FallbackResult();
}
别依赖全局异常处理器自动捕获它——它不是 AggregateException的子类,也不会被
Task.Wait()包装 如果你用的是
PolicyWrap(比如套了重试 + 舱壁),确保
BulkheadPolicy在最内层,否则拒绝逻辑可能被外层策略干扰
和 SemaphoreSlim
手动限流比有什么区别?
本质都是控制并发,但
BulkheadPolicy提供了更贴近业务语义的抽象:它自带队列管理、拒绝统计、与其它 Polly 策略(如
RetryPolicy、
CircuitBreakerPolicy)天然可组合,且拒绝行为是同步判断的(不真正 await),而
SemaphoreSlim.WaitAsync()是纯底层信号量,容易误写成死锁或忽略超时。 不要在
ExecuteAsync内部再套一层
SemaphoreSlim—— 会造成双重并发控制,逻辑混乱
BulkheadPolicy的队列是内存中的 FIFO,不持久化,服务重启即清空 监控指标如
bulkhead.CurrentlyExecuting和
bulkhead.CurrentlyQueued需通过
Context或自定义事件获取,没有开箱即用的 Metrics 输出
舱壁的关键不在“设个数字”,而在理解哪些依赖真正需要隔离、这个数字是否随负载动态调整、以及被拒后系统是否具备可观测性和应对路径。硬编码
maxParallelization: 5很容易在压测时暴露为瓶颈点。
