Skip to content

熔断器配置

AutoRouter 给每条上游单独维护一个熔断器,行为遵循 CLOSED → OPEN → HALF_OPEN 的标准状态机。状态机本身的转移规则、与 failover 的协同详见现有长篇 docs/circuit-breaker.md;本页只补全实际配置层面的三块内容:可调阈值字段、自定义失败规则、Admin 强制开关。

状态机一句话回顾

枚举位于 src/lib/services/circuit-breaker.ts:13-17

CLOSED      // 正常服务
OPEN        // 已熔断,拒绝新流量;到达 openDuration 后自动转 HALF_OPEN
HALF_OPEN   // 半开,按 probeInterval 节奏放探针请求

驱动状态转移的三个函数:

函数触发行为
recordFailure转发失败时调(circuit-breaker.ts:243CLOSED 累计失败到 failureThreshold → OPEN;HALF_OPEN 任意失败 → 回 OPEN
recordSuccess转发成功时调(circuit-breaker.ts:208仅 HALF_OPEN 生效,连续 successThreshold 次成功 → CLOSED
acquireCircuitBreakerPermit每次请求前调(circuit-breaker.ts:160OPEN 状态下若 openDuration 已超时自动转 HALF_OPEN

详细行为图与边界处理见 docs/circuit-breaker.md

上游级可调阈值

熔断参数直接落在 upstreams 表上,而是按上游写入 circuit_breaker_states.configsrc/lib/db/schema-pg.ts:236-243)。创建或编辑上游时通过 circuit_breaker_config 嵌套对象提交,路由层用「上游覆盖值 → 全局默认值」的回退顺序读取。

API 字段(管理 API 层接收以秒为单位的输入并转换为毫秒存储;src/app/api/admin/upstreams/route.ts:23-31:81-88):

API 字段类型默认单位(API)单位(DB)含义
failure_thresholdinteger 1–1005次数次数CLOSED 状态下累计多少次失败转 OPEN
success_thresholdinteger 1–1002次数次数HALF_OPEN 状态下连续多少次成功转 CLOSED
open_durationinteger 1–300000300毫秒OPEN 持续多久后可转 HALF_OPEN(默认 5 分钟)
probe_intervalinteger 1–6000030毫秒HALF_OPEN 探针节流:相邻两次探针的最小间隔(默认 30 秒)
first_byte_timeoutinteger 1–30000030毫秒上游响应首字节超时(默认 30 秒)
stream_idle_timeoutinteger 1–30000060毫秒SSE 流空闲超时(默认 60 秒)

默认值来源:src/lib/circuit-breaker-defaults.ts:10-17open_duration / probe_interval 等字段在 API 层接收秒、在 DB 内部按毫秒存储,是为兼容更早期的纯毫秒提交格式而设的双单位约定。

UI 入口:上游编辑弹框的「Reliability → Circuit Breaker Config」分区(src/components/admin/upstream-form-dialog.tsx:3651-3808,分区 id advanced-circuit-breakerupstream-form-dialog.tsx:1282-1285)。每个字段都带 i18n 标签与默认值显示,编辑后保存即生效——不需要重启进程。

调参建议

场景该调哪个
上游偶有抖动但不希望整条断开调大 failure_threshold(默认 5 已经比较宽容)
上游故障后希望尽快尝试恢复调小 open_duration(最小 1 秒,太短会让故障上游被反复探活)
HALF_OPEN 探针请求被压垮调大 probe_interval,让探针之间留出更多缓冲
上游 SSE 流空闲很久才继续输出调大 stream_idle_timeout(默认 60 秒)
上游首字节响应慢但稳定调大 first_byte_timeout(默认 30 秒)

自定义失败规则(upstream_failure_rules)

不是所有 HTTP 错误都应该被记入熔断器——有些是已知的、可预期的、对上游健康度没有意义的失败。AutoRouter 提供「失败规则」让你针对特定错误特征抑制熔断计数(但 failover 仍然发生)。

Schema 与匹配语义

upstream_failure_rulessrc/lib/db/schema-pg.ts:257-274):

字段类型含义
upstream_iduuidNULLNULL = 全局规则,命中所有上游;非 NULL = 仅对该上游
namevarchar(128)规则名
enabledboolean,默认 true是否启用
priorityinteger,默认 0匹配优先级(升序,越小越先匹配)
matchjson匹配条件

match 字段结构(src/lib/services/upstream-failure-rules.ts:8-14):

子字段含义
status_codesHTTP 状态码白名单(命中即匹配)
error_types错误类型字符串列表(如 timeout
body_pattern响应体正则
header_name + header_pattern响应头名 + 值正则

四类子条件以 AND 关系拼接:都给值就都得满足;都不给则永远不匹配(空规则无意义)。

规则命中的效果

matchFailureRuleupstream-failure-rules.ts:307)按 priority 升序找第一条命中规则,返回 MatchedFailureRule | null。返回非 null 时:

  • failover 仍发生:请求会换下一条上游继续重试。
  • 熔断不计数route.ts:1549-1557 显式判断 matchedFailureRule === null,命中规则时跳过 recordFailure(upstream, errorType)

也就是说失败规则的语义是「这次失败已经被规则解释了,不再算作上游故障」,而不是「这次失败不算失败」。

管理 API

方法路径用途
GET / POST/api/admin/upstream-failure-rules列 / 建全局规则
GET / PATCH / DELETE/api/admin/upstream-failure-rules/[id]取 / 改 / 删全局规则
GET / POST/api/admin/upstreams/[id]/failure-rules列 / 建上游局部规则

POST body 字段对应 match 结构(upstream-failure-rules.ts:16-22failure-rules/route.ts:18-24):nameenabledprioritymatch.status_codesmatch.error_typesmatch.body_patternmatch.header_namematch.header_pattern

典型用法

想做的事写一条这样的规则
上游侧返回的「模型暂时不可用」不要导致整条熔断status_codes: [503] + body_pattern: "model.*not.*available"
用户侧 4xx 错误不影响上游健康度status_codes: [400, 401, 403, 422](默认 failover 会跳过 4xx,但保险起见)
单一上游的特定 retry-after 不计入熔断status_codes: [429] + header_name: "retry-after" + header_pattern: "^[1-9]"

circuit_breaker_states 表与持久化

字段(src/lib/db/schema-pg.ts:222-251):

字段类型说明
upstream_iduuid UNIQUE NOT NULL一条上游对应一行
statevarchar(16),默认 closed当前状态
failure_countinteger,默认 0累计失败次数
success_countinteger,默认 0HALF_OPEN 下连续成功计数
last_failure_attimestamptz最后一次失败时间
opened_attimestamptz最近进入 OPEN 的时间(用于计算 openDuration)
last_probe_attimestamptz最近探针时间(用于 probeInterval 节流)
configjson 或 null上游覆盖配置(null = 全用默认值)

状态完全持久化:所有字段写入 PostgreSQL,进程重启后熔断状态完整恢复(circuit-breaker.ts:43-46getOrCreateCircuitBreakerState 直接 db.query 读盘)。重启不会重置 OPEN 状态,opened_at 时间戳仍然有效,重启后下一次请求会按真实经过时间判断是否可转 HALF_OPEN。

Admin 强制开关

有些场景下手动控制熔断比等自动状态机更直接,例如:

  • 已知上游计划维护,提前 force open 避免触发 failover 浪费请求。
  • 故障已修复但 OPEN 状态还没到期,想立刻恢复服务,force close。
  • 想清空累积的 failure_count,让计数从零开始。
方法路径行为
POST/api/admin/circuit-breakers/[id]/force-openforceOpen,写 opened_at = nowstate = OPEN,不动 failure_count
POST/api/admin/circuit-breakers/[id]/force-closeforceClose,写 state = CLOSEDfailure_count = 0success_count = 0
GET/api/admin/circuit-breakers/[id]查单条上游熔断状态
GET/api/admin/circuit-breakers?state=open&page=1&page_size=20分页列出,可按状态过滤

源码:src/app/api/admin/circuit-breakers/[id]/force-open/route.ts:18-50force-close/route.ts:18-50;底层调用 forceOpencircuit-breaker.ts:293)与 forceClosecircuit-breaker.ts:309)。

UI 入口:上游列表页(src/app/[locale]/(dashboard)/upstreams/page.tsx:86)可按 circuit_open 状态过滤;useForceCircuitBreaker() hook(src/hooks/use-circuit-breaker.ts:33-58)封装两个 mutation,按钮点击后自动 invalidate circuit-breakersupstreams 查询缓存。

force-openforce-close 都不需要 body,仅需 Authorization: Bearer <ADMIN_TOKEN> 头。

与 failover 的关系

熔断与 failover 共用同一次 HTTP 失败事件,但处于两个独立的代码路径:

  • failover:「换一个上游重试」。触发条件由 src/lib/services/failover-config.ts:57-73 决定,默认任何非 2xx 都触发,可通过 excludeStatusCodes 排除;策略可选 exhaust_all(默认)或 max_attempts(默认 10 次,failover-config.ts:44-48)。
  • 熔断计数:「这条上游不健康」。由 recordFailure 写入,受 shouldRecordCircuitBreakerFailure(path)route.ts:800-803)与 matchedFailureRule === nullroute.ts:1549-1557)两个条件共同控制。

shouldRecordCircuitBreakerFailure 维护一个路径白名单 CIRCUIT_BREAKER_NEUTRAL_PATHS = {"messages/count_tokens"}route.ts:793)。命中白名单的路径即使失败也不计入熔断(这种 token 计数类请求不代表上游真实健康度)。

matchedFailureRule 在三处出现:HTTP 错误分支(route.ts:1549-1556)、流式错误 settlement 分支(:1708-1712)、网络 / 超时 settlement 分支(:1948-1951)。例外:流式 runtime 错误分支(:1632-1635)不检查 failure rule,直接按白名单决定。

排查清单

现象检查
上游频繁被熔断检查 failure_threshold 是否过小;是否有规律性失败需要加 failure rule 抑制
已知好转但熔断状态不解除force-close;或调小该上游的 open_duration
HALF_OPEN 探针请求太密集压垮上游调大 probe_interval
熔断状态进程重启后还在是正常行为(持久化在 DB),如需清空请 force-close
某类已知错误不应导致熔断upstream_failure_rules 规则匹配该错误的状态码 / 响应体特征
force-open 后忘了恢复列表页过滤 circuit_open,再 force-close 单条恢复

不在本页范围内

Released under the MIT License.