在分布式系统里,网络服务之间的强依赖往往让局部故障被放大成全局瘫痪。熔断器作为隔离故障的核心组件,其半开状态的设计目标是在服务疑似恢复时,用可控的一小撮流量去验证真实性,而不是瞬间把全部请求放过去。所谓半开试探比例,就是指在半开时间窗内,允许通过的试探请求占该窗口内总请求数的百分比,或者占预设探针配额的份额。用Ruby来精确控制这个比例,本质上是在状态机转换逻辑中引入一个数学约束,并结合并发安全的计数器完成调度。

熔断器半开状态与试探比例的基础原理
标准熔断器通常有三个状态:关闭、打开、半开。关闭状态下请求正常放行;当错误率超过阈值,熔断器跳到打开状态,拒绝所有请求一段时间;休眠期结束后进入半开状态,此时系统需要判断依赖服务是否恢复健康。如果半开阶段全部请求都放过去,那就和关闭状态没区别,失去了保护意义。试探比例就是用来限制半开阶段真正打到下游的请求量的,例如设置比例为百分之二十,意味着每进来十个请求,只有两个会被当成探针发出去,其余八个直接在本地被快速失败。
这个比例不是拍脑袋定的。比例过高,恢复期的流量冲击大,可能把刚启动的下游压垮;比例过低,探测样本太少,要花很长时间才能确认服务可用,拖慢整体恢复。在Ruby进程内,由于多数服务是多线程或基于事件循环(如EventMachine、Async),我们需要确保比例控制逻辑是线程安全的。通常可以用互斥锁保护一个时间窗内的计数器,或者用原子变量减少锁竞争。
从实现视角看,试探比例有两种常见表达:一是相对比例,即根据半开窗口内到达的总请求数按比放行;二是绝对配额,即半开期间总共只允许发N个探针,不管外部流量多大。Ruby代码中往往把二者结合,用比例算出当前窗口允许的探针数上限,再用计数器约束。理解这一点,才能写出既贴合业务流量模式又不会失控的熔断器。
基于Ruby的半开试探比例控制代码实现
下面给出一个简化但完整的Ruby熔断器类,重点展示如何用半开试探比例控制探针。我们使用一个 mutex 保护状态与计数,并在半开状态下依据配置的比例与窗口内总请求数计算可放行探针数。
require 'mutex_m'
class RubyCircuitBreaker
CLOSED = :closed
OPEN = :open
HALF_OPEN = :half_open
def initialize(threshold: 0.5, retry_time: 5, probe_ratio: 0.2, half_open_window: 10)
@threshold = threshold
@retry_time = retry_time
@probe_ratio = probe_ratio
@half_open_window = half_open_window
@state = CLOSED
@failure_count = 0
@total_count = 0
@opened_at = nil
@half_open_total = 0
@half_open_probe = 0
@lock = Mutex.new
@lock.extend Mutex_m
end
def call
@lock.synchronize do
now = Time.now
if @state == OPEN
if now - @opened_at >= @retry_time
enter_half_open(now)
else
return false
end
end
if @state == HALF_OPEN
@half_open_total += 1
allowed_probe = (@half_open_window * @probe_ratio).floor
if @half_open_probe < allowed_probe
@half_open_probe += 1
return true
else
return false
end
end
end
true
end
def record_success
@lock.synchronize do
if @state == HALF_OPEN
@state = CLOSED
reset_counters
else
@failure_count = 0
@total_count = 0
end
end
end
def record_failure
@lock.synchronize do
if @state == HALF_OPEN
@state = OPEN
@opened_at = Time.now
reset_half_open
else
@failure_count += 1
@total_count += 1
if @failure_count.to_f / @total_count >= @threshold
@state = OPEN
@opened_at = Time.now
end
end
end
end
private
def enter_half_open(now)
@state = HALF_OPEN
@half_open_total = 0
@half_open_probe = 0
@opened_at = now
end
def reset_counters
@failure_count = 0
@total_count = 0
reset_half_open
end
def reset_half_open
@half_open_total = 0
@half_open_probe = 0
end
end
上面的代码里,probe_ratio 就是半开试探比例。在 call 方法中,每当半开状态收到请求,先累加 @half_open_total,再按窗口大小乘以比例算出允许探针数 allowed_probe。只有已发出探针数小于该值,才返回 true 放行。这样无论外部流量在半开窗口内是汹涌还是稀疏,探针数量都被牢牢限制在比例配额内。
这种实现的优点是逻辑直观、易嵌入现有 Ruby 服务。缺点是在极高并发下,mutex 可能成为瓶颈。生产环境可改用 Concurrent::AtomicFixnum 等无锁结构,或将比例控制下沉到 Sidekiq 之类的任务调度层。无论如何,比例的语义保持一致:用 Ruby 算出边界,用计数器执行边界。
固定数量与按比例试探的策略对比及调优
很多团队在 Ruby 熔断器里习惯写死半开期间只允许两个探针,这属于绝对配额思路。它的好处是简单,不依赖流量大小;坏处是当系统平时每秒上千请求时,两个探针的样本代表性极差,可能误判。按比例试探则随流量自适应:流量大时探针多,流量小时探针少,始终维持风险与灵敏度的平衡。我们可以通过一张表看清差异。
| 策略类型 | 低峰期表现 | 高峰期表现 | 实现复杂度 |
|---|---|---|---|
| 固定数量(如2个) | 恢复慢,样本少易误判 | 冲击小但探测不充分 | 低 |
| 按比例(如20%) | 探针随请求微增,恢复合理 | 探针数随流量涨,感知快 | 中 |
在 Ruby 应用中调优试探比例,建议先拿历史故障日志回放。比如某接口依赖支付网关,超时阈值设百分五十,打开五秒后半开。若业务高峰 QPS 为 500,比例取 0.1 则半开首秒约放行 50 个探针,足以在数秒内确认网关健康;若取 0.3 则可能短时间给下游施加 150 QPS,对刚重启的网关不友好。因此比例常落在 0.1 到 0.2 之间。
另一个容易被忽略的点是半开窗口长度。比例乘以窗口才等于探针总数,如果窗口太短,按比例算出的允许探针数被 floor 成零,就会导致半开阶段永远不放行。Ruby 代码里用 (@half_open_window * @probe_ratio).floor 时,务必保证乘积至少为 1,否则要加保护逻辑。只有把窗口、比例、错误率阈值三者联立考量,熔断器才能真正既挡得住风暴,又恢复得平滑。
circuit_breakerrubyhalf_open_probe修改时间:2026-08-16 12:20:36