feat: add rate-channel breaker config and health_aware default
Threshold auto-raise now uses per-source concurrency only (the M2 global-concurrency formula neutered the breaker at scale). Four new optional keys: MIN_CALLS, FAIL_RATE, WINDOW_S, MAX_COOLDOWN_S.
This commit is contained in:
@@ -45,7 +45,7 @@ _SOURCE_FIELDS: dict[str, tuple[str, str]] = {
|
||||
"TRUST_ENV": ("trust_env", "bool"),
|
||||
}
|
||||
_RESERVED_SEGMENTS = frozenset({"GLOBAL", "RETRY", "BREAKER", "BACKPRESSURE"})
|
||||
_SELECTORS = frozenset({"round_robin", "least_inflight"})
|
||||
_SELECTORS = frozenset({"round_robin", "least_inflight", "health_aware"})
|
||||
_QUOTA_FULL = frozenset({"wait", "fail_fast"})
|
||||
# 背压默认(M1 仅 poll 生效;CHS _BACKOFF_S=0.05 同源)
|
||||
_DEFAULT_STALL_WINDOW_S = 300.0
|
||||
@@ -136,7 +136,7 @@ class GatewaySettings:
|
||||
retry=retry,
|
||||
breaker=breaker,
|
||||
backpressure=_load_backpressure(scope_u, env),
|
||||
selector=_load_choice(env, f"{scope_u}__SELECTOR", _SELECTORS, "round_robin"),
|
||||
selector=_load_choice(env, f"{scope_u}__SELECTOR", _SELECTORS, "health_aware"),
|
||||
quota_full=_load_choice(env, f"{scope_u}__QUOTA_FULL", _QUOTA_FULL, "wait"),
|
||||
**_load_pgw(env),
|
||||
)
|
||||
@@ -211,10 +211,9 @@ def _load_breaker(
|
||||
key_c, cool = _require(env, f"{scope}__BREAKER__COOLDOWN_S", "LLM_CIRCUIT_BREAKER_COOLDOWN")
|
||||
threshold = int(_cast(thr, "int", key_t))
|
||||
cooldown_s = float(_cast(cool, "float", key_c))
|
||||
# 有效阈值 = max(配置值, 并发×2)——三项目 .env 注释的手动约定入库(设计 §9 行 4)
|
||||
concurrency = global_limits.max_concurrency or max(
|
||||
(s.max_concurrency for s in sources), default=0
|
||||
)
|
||||
# 有效阈值 = max(配置值, 源级并发×2)。M2.5 修正: 只看源级并发——M2 曾用
|
||||
# 全局并发抬升(SOAK 100→阈值 200)使熔断失灵(病灶 2,设计 2026-07-21-m25)
|
||||
concurrency = max((s.max_concurrency for s in sources), default=0)
|
||||
if concurrency > 0:
|
||||
threshold = max(threshold, concurrency * 2)
|
||||
slowest = max(s.timeout_s for s in sources)
|
||||
@@ -231,7 +230,28 @@ def _load_breaker(
|
||||
else:
|
||||
# 派生规则: 探针租约须撑过一次最慢调用,且不短于冷却期(第三项保证守卫恒成立)
|
||||
probe_ttl_s = max(2 * slowest, cooldown_s, probe_floor)
|
||||
return BreakerConfig(fail_threshold=threshold, cooldown_s=cooldown_s, probe_ttl_s=probe_ttl_s)
|
||||
# M2.5 失败率通道参数(可选键,库缺省——韧性参数缺省先例同 backpressure)
|
||||
min_calls = int(_opt_float(env, f"{scope}__BREAKER__MIN_CALLS", 10))
|
||||
fail_rate = _opt_float(env, f"{scope}__BREAKER__FAIL_RATE", 0.6)
|
||||
window_s = _opt_float(env, f"{scope}__BREAKER__WINDOW_S", 60.0)
|
||||
max_cooldown_s = _opt_float(env, f"{scope}__BREAKER__MAX_COOLDOWN_S", max(300.0, cooldown_s))
|
||||
return BreakerConfig(
|
||||
fail_threshold=threshold,
|
||||
cooldown_s=cooldown_s,
|
||||
probe_ttl_s=probe_ttl_s,
|
||||
min_calls=min_calls,
|
||||
fail_rate=fail_rate,
|
||||
window_s=window_s,
|
||||
max_cooldown_s=max_cooldown_s,
|
||||
)
|
||||
|
||||
|
||||
def _opt_float(env: Mapping[str, str], key: str, default: float) -> float:
|
||||
"""可选韧性参数: 缺省用库值,显式配置则解析(坏值 fail-loud)。"""
|
||||
raw = env.get(key)
|
||||
if raw is None or raw == "":
|
||||
return default
|
||||
return float(_cast(raw, "float", key))
|
||||
|
||||
|
||||
def _load_backpressure(scope: str, env: Mapping[str, str]) -> BackpressurePolicy:
|
||||
|
||||
Reference in New Issue
Block a user