feat: suppress consecutive-channel opening on evidently healthy sources
Round 8 forensics caught the healthy source circuit-opened by five random empty completions (~20% ambient failure rate makes a 5-streak land every ~3000 attempts), blacking out the only good source for 60s. When the window holds min_calls samples below the failure-rate threshold, a streak is noise and no longer opens the gate; cold-start and low-traffic semantics are unchanged and sudden death of a warm source is still caught by the rate channel.
This commit is contained in:
@@ -81,6 +81,14 @@ class InMemoryGate:
|
||||
if failed:
|
||||
g.f0 += 1
|
||||
|
||||
def _window_evidently_healthy(self, g: _SourceGate) -> bool:
|
||||
"""窗口样本充足且失败率低于阈值 = 有充分健康证据(迭代 6 抑制判据)。"""
|
||||
self._rotate_window(g)
|
||||
attempts = g.a0 + g.a1
|
||||
if attempts < self._cfg.min_calls:
|
||||
return False
|
||||
return (g.f0 + g.f1) / attempts < self._cfg.fail_rate
|
||||
|
||||
def _rate_channel_open(self, g: _SourceGate) -> bool:
|
||||
self._rotate_window(g)
|
||||
attempts = g.a0 + g.a1
|
||||
@@ -227,8 +235,10 @@ class InMemoryGate:
|
||||
g.fails += 1
|
||||
if self._rate_channel_open(g):
|
||||
self._open(g, reason, bump_streak=True)
|
||||
elif g.fails >= self._cfg.fail_threshold:
|
||||
self._open(g, reason, bump_streak=False) # 连续通道不递增(C1 封顶)
|
||||
elif g.fails >= self._cfg.fail_threshold and not self._window_evidently_healthy(g):
|
||||
# 连续通道不递增(C1 封顶);窗口证据充足且健康时连败是噪声,
|
||||
# 不误熔"当前最好的源"(迭代 6,第八轮实证: 源1 被 5 连空补全误关 60s)
|
||||
self._open(g, reason, bump_streak=False)
|
||||
return self._snapshot(g, applied=True)
|
||||
|
||||
async def release_probe(self, entry: GateDecision) -> GateUpdate:
|
||||
|
||||
@@ -179,6 +179,7 @@ end
|
||||
|
||||
local threshold = tonumber(ARGV[5])
|
||||
local open_via_rate = false
|
||||
local window_healthy = false
|
||||
if force_open == 0 and is_probe == 0 then
|
||||
rotate_window(KEYS[1], now, tonumber(ARGV[10]))
|
||||
redis.call('HINCRBY', KEYS[1], 'a0', 1)
|
||||
@@ -187,8 +188,13 @@ if force_open == 0 and is_probe == 0 then
|
||||
+ tonumber(redis.call('HGET', KEYS[1], 'a1') or '0')
|
||||
local fails_w = tonumber(redis.call('HGET', KEYS[1], 'f0') or '0')
|
||||
+ tonumber(redis.call('HGET', KEYS[1], 'f1') or '0')
|
||||
if attempts >= tonumber(ARGV[8]) and fails_w / attempts >= tonumber(ARGV[9]) then
|
||||
open_via_rate = true
|
||||
if attempts >= tonumber(ARGV[8]) then
|
||||
if fails_w / attempts >= tonumber(ARGV[9]) then
|
||||
open_via_rate = true
|
||||
else
|
||||
-- 窗口证据充足且健康: 连败是噪声,抑制连续通道(迭代 6)
|
||||
window_healthy = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,7 +203,8 @@ if force_open == 1 or is_probe == 1 then
|
||||
else
|
||||
failures = failures + 1
|
||||
end
|
||||
if force_open == 1 or is_probe == 1 or open_via_rate or failures >= threshold then
|
||||
local open_via_streak = failures >= threshold and not window_healthy
|
||||
if force_open == 1 or is_probe == 1 or open_via_rate or open_via_streak then
|
||||
-- 递增 streak 的只有率通道开路与探针失败重开(C1: 连续通道/force_open 不递增)
|
||||
if open_via_rate or is_probe == 1 then
|
||||
redis.call('HINCRBY', KEYS[1], 'reopen_streak', 1)
|
||||
|
||||
Reference in New Issue
Block a user