ReShare 8200 连续 5 天 500:健康检查盲区、单点故障到多源熔断架构重构

本文最后更新于 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"} # 永远返回 200

这是典型的「假装健康」。进程活着不代表服务可用。上游数据源挂了,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" # closed: 正常, open: 熔断, half-open: 试探
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
#### ReShare 配置
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 所有数据源均不可用"

单个源挂了不再触发告警(因为业务无感知),所有源都挂了才告警。

验证

重构完成后,我做了故障演练:

  1. 手动停掉 source_a,观察 ReShare 行为
  2. 预期:请求自动切到 source_b,8200 端口保持 200

实际效果:

1
2
3
4
5
6
7
8
# 停掉 source_a 后
curl -i http://localhost:8200/healthz
# HTTP/1.1 200 OK
# {"status":"ok","checks":[{"source":"primary","status":"down"},{"source":"backup","status":"ok"}]}

curl -i http://localhost:8200/api/v1/data
# HTTP/1.1 200 OK
# {"data":"from backup source","source":"source_b"}

3 秒内完成自动切换,业务方零感知。再也没出现连续 5 天 500 的情况。

故障恢复时间从 5 天(人肉发现 + 人肉修复)降到 30 秒(熔断器冷却时间),如果算上自动切换,实际恢复时间不到 1 秒

踩坑记录汇总

表现 根因 解法
健康检查盲区 监控全绿,实际 500 /healthz 不检查依赖 依赖纳入健康检查
单点故障 数据源挂了整个服务瘫 只有一个上游源 加备用源 + 熔断器
告警失效 挂了 5 天没人发现 告警规则只看进程存活 告警改为「所有源都挂」
恢复靠人肉 周末还要爬起来修 没有自动恢复机制 熔断器自动切换

参考


ReShare 8200 连续 5 天 500:健康检查盲区、单点故障到多源熔断架构重构
https://normdist.com/2026/08/27/ND-20260827-001-article/
作者
小瑞
发布于
2026年8月27日
许可协议