本文最后更新于 2026年8月27日 凌晨
ReShare 8200 连续 5 天 500:健康检查盲区、单点故障到多源熔断架构重构
ReShare 8200 端口连续 5 天返回 500,监控面板上一片绿。我查了三天才发现:健康检查只检查了进程活着,没检查依赖活着。这篇讲清楚我从「假装健康」到「多源熔断」的全过程,以及最终怎么让服务在数据源挂掉时依然可用。
前置条件
先交代环境,方便你对号入座:
- 服务器:ModelBase(内部网络),双卡 RTX 2080 Ti 魔改版(单卡 22 GB,双卡 44 GB)
- 推理引擎:llama.cpp,models-preset 多模型模式
- 代理层:new-api(内部网络:3000 端口)
- 数据管道:ReShare 服务,监听 8200 端口
- 监控:Prometheus + Alertmanager,健康检查走 HTTP
/healthz
问题是什么
ReShare 8200 端口从 8 月 20 日开始,连续 5 天返回 HTTP 500。奇怪的是,监控面板上 up 状态一直是 1,/healthz 永远返回 200。业务方天天催,我天天查日志,日志显示「上游数据源连接超时」。
这就是第一个盲区:健康检查只检查了进程是否存活,没检查依赖是否可用。进程活着,但 ReShare 依赖的上游数据源(一个外部 API)已经挂了 5 天。
我一开始没意识到这点,白白浪费了两天。
踩过的坑
坑一:健康检查只看进程活着
ReShare 的 healthz 最初长这样:
1 2 3
| @app.get("/healthz") async def healthz(): return {"status": "ok"}
|
这是典型的「假装健康」。进程活着不代表服务可用。上游数据源挂了,healthz 照样 200,Prometheus 认为一切正常,告警永远不触发。
修法:健康检查必须包含依赖检查。
1 2 3 4 5 6 7 8 9 10 11
| @app.get("/healthz") async def healthz(): try: upstream_status = await check_upstream() if upstream_status != "ok": return JSONResponse(status_code=503, content={"status": "degraded"}) except Exception as e: return JSONResponse(status_code=503, content={"status": "unavailable", "detail": str(e)})
return {"status": "ok"}
|
改完后,数据源挂掉时 /healthz 返回 503,Prometheus 立刻告警。但这只解决了「发现」问题,没解决「恢复」问题。
坑二:单一数据源是定时炸弹
ReShare 管道只连了一个数据源。这个源挂了,整个服务就瘫了。没有备选,没有降级,没有熔断。
这时候我想起了之前写过一篇断路器模式的文章(见文末参考),但当时只是理论,没落地到 ReShare 上。现在报应来了。
修法:引入第二个数据源,加熔断器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class CircuitBreaker: def __init__(self, failure_threshold=3, cooldown=30): self.failure_count = 0 self.failure_threshold = failure_threshold self.state = "closed" self.last_failure_time = None self.cooldown = cooldown
def call(self, func, *args, **kwargs): if self.state == "open": if time.time() - self.last_failure_time > self.cooldown: self.state = "half-open" else: raise CircuitOpenError("断路器已打开,请求降级")
try: result = func(*args, **kwargs) self.failure_count = 0 self.state = "closed" return result except Exception: self.failure_count += 1 if self.failure_count >= self.failure_threshold: self.state = "open" self.last_failure_time = time.time() raise
|
怎么做的:多源熔断架构重构
核心思路:一个源挂了,自动切到另一个源,业务无感知。
第一步:接上第二个数据源
ReShare 原本只连 source_a,我加了 source_b 作为备选:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| data_sources: primary: name: source_a url: https://api.example.com/v1/data timeout: 5 circuit_breaker: failure_threshold: 3 cooldown: 30 backup: name: source_b url: https://backup.example.org/v2/data timeout: 8 circuit_breaker: failure_threshold: 3 cooldown: 60
|
source_b 是同一个数据的镜像源,平时流量不大,但关键时候能救命。
第二步:熔断器 + 自动切换
在 ReShare 的数据获取层加了一层「多源路由」:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| async def fetch_data(request): for source in [primary_source, backup_source]: try: data = await source.fetch(request) return data except CircuitOpenError: logger.warning(f"源 {source.name} 熔断中,尝试备用源") continue except TimeoutError: logger.warning(f"源 {source.name} 超时,尝试备用源") continue
return JSONResponse(status_code=503, content={"status": "unavailable"})
|
配合熔断器,source_a 连续失败 3 次后熔断 30 秒,这 30 秒内所有请求直接走 source_b,不再超时等待。
第三步:健康检查覆盖依赖
/healthz 改为检查所有依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @app.get("/healthz") async def healthz(): checks = []
primary_ok = await check_source(primary_source) checks.append({"source": "primary", "status": "ok" if primary_ok else "down"})
backup_ok = await check_source(backup_source) checks.append({"source": "backup", "status": "ok" if backup_ok else "down"})
if primary_ok or backup_ok: return {"status": "ok", "checks": checks} else: return JSONResponse(status_code=503, content={"status": "unavailable", "checks": checks})
|
第四步:告警规则跟着改
Prometheus 告警规则从「进程挂了」改为「所有源都挂了」:
1 2 3 4 5 6 7 8 9 10
| groups: - name: reshare-alerts rules: - alert: ReShareAllSourcesDown expr: probe_success{job="reshare", target="primary"} == 0 AND probe_success{job="reshare", target="backup"} == 0 for: 5m labels: severity: critical annotations: summary: "ReShare 所有数据源均不可用"
|
单个源挂了不再触发告警(因为业务无感知),所有源都挂了才告警。
验证
重构完成后,我做了故障演练:
- 手动停掉
source_a,观察 ReShare 行为
- 预期:请求自动切到
source_b,8200 端口保持 200
实际效果:
1 2 3 4 5 6 7 8
| curl -i http://localhost:8200/healthz
curl -i http://localhost:8200/api/v1/data
|
3 秒内完成自动切换,业务方零感知。再也没出现连续 5 天 500 的情况。
故障恢复时间从 5 天(人肉发现 + 人肉修复)降到 30 秒(熔断器冷却时间),如果算上自动切换,实际恢复时间不到 1 秒。
踩坑记录汇总
| 坑 |
表现 |
根因 |
解法 |
| 健康检查盲区 |
监控全绿,实际 500 |
/healthz 不检查依赖 |
依赖纳入健康检查 |
| 单点故障 |
数据源挂了整个服务瘫 |
只有一个上游源 |
加备用源 + 熔断器 |
| 告警失效 |
挂了 5 天没人发现 |
告警规则只看进程存活 |
告警改为「所有源都挂」 |
| 恢复靠人肉 |
周末还要爬起来修 |
没有自动恢复机制 |
熔断器自动切换 |
参考