c# 如何用 Polly 实现舱壁隔离 Bulkhead Isolation 模式

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

什么是 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
很容易在压测时暴露为瓶颈点。

相关推荐