什么是 Polly 的 BulkheadPolicy
?
BulkheadPolicy是 Polly 中实现舱壁隔离(Bulkhead Isolation)的策略,它通过限制并发执行的请求数量和排队等待的请求数量,防止某个依赖故障或延迟拖垮整个调用方线程池或资源。它的核心不是重试或熔断,而是“物理隔离”——像轮船的舱壁一样,把失败控制在局部。
BulkheadPolicy
的两个关键参数:maxParallelization
和 maxQueuedActions
创建
BulkheadPolicy时必须指定这两个整数参数,它们共同定义了资源使用边界:
maxParallelization:最多允许多少个操作**同时执行**(即占用线程/任务)
maxQueuedActions:当所有并行槽位被占满时,最多允许多少个操作在队列中**等待执行**
超出这两个限制的调用会立即抛出
BulkheadRejectedException,而不是阻塞或排队无限等待。
var bulkhead = Policy.BulkheadAsync(
maxParallelization: 5,
maxQueuedActions: 10
);
注意:
maxParallelization不等于线程数(尤其在 async/await 场景下),它表示“同时处于
ExecuteAsync执行上下文中的操作数量”。对 I/O 密集型操作,实际线程消耗远小于此值;但对 CPU 密集型或同步阻塞调用,它可能直接对应线程池租用数。
为什么不能只靠 Task.Run
或 SemaphoreSlim
?
手动用
SemaphoreSlim或线程池限流,容易漏掉几个关键点: 不自动区分“执行中”和“排队中”,无法统一拒绝超限请求 异常处理分散:你得自己捕获
WaitAsync超时、手动抛出业务一致的拒绝异常 无法与 Polly 其他策略(如
RetryPolicy、
CircuitBreakerPolicy)自然组合 缺少内置指标(如当前排队数、拒绝计数),调试和监控成本高
而
BulkheadPolicy把这些封装进统一的
ExecuteAsync接口,且支持
PolicyWrap组合:
var policyWrap = Policy.WrapAsync(bulkhead, retry, breaker);
常见误用和坑点
舱壁策略最容易被当成“限流器”滥用,但它本质是**资源隔离机制**,不是速率控制器:
它不感知时间窗口(不像令牌桶),所以不能替代RateLimitPolicy如果内部操作本身是同步阻塞(如
Thread.Sleep),
maxParallelization会快速耗尽线程池,导致后续请求即使没超限也因无可用线程而卡死
maxQueuedActions设为 0 并不意味着“不排队”,而是“拒绝所有排队请求”——此时一旦 5 个并发满,第 6 个调用立刻抛
BulkheadRejectedException异步操作中,不要在
ExecuteAsync内部再用
.Wait()或
.Result,这会破坏异步流并可能导致死锁或线程饥饿
真正需要舱壁的场景,是调用外部不稳定服务(如第三方 HTTP API、数据库查询),且你明确知道该服务的吞吐瓶颈或连接池上限。盲目加舱壁反而增加延迟和拒绝率。
