feat(health): 被动式健康检查 API(FastAPI,覆盖服务健康+数据健康)
在同步进程内内置 FastAPI/uvicorn HTTP endpoint (GET /health),返回结构化 健康快照,便于运维/监控被动探活: - 服务健康:cycle_lag(核心指标,超过 3×poll_interval 判 unhealthy)、 队列状态(pending/error/dead/cleaned 实时查)、capture 累计(enqueued/ deferred/aged_out)、最近 cycle 时间/耗时/错误。 - 数据健康:解析最近一次定时 compare_ids 报告(不跑全量 compare 太重)、 实时查 SyncLogArchive 异常痕迹(AgedOut/降级)、SyncQueue dead 行样本。 status 三态:healthy(200) / degraded(503, 有dead/error/aged_out/compare 不一致) / unhealthy(503, 主循环停滞)。状态码映射便于按码告警。 线程模型:uvicorn 占主线程,capture/apply/cleanup 循环跑后台 daemon 线程, NSSM 停服务时主线程退出、daemon 自动终止。health.enabled=false 时退化为 旧行为(同步循环占主线程)。Web 层(routes/)与业务逻辑(HealthChecker) 解耦,后期加运维接口(compare触发/死信管理/metrics)零结构改动。 零 SQL/零 schema 改动。新增依赖 fastapi/uvicorn[standard]。
This commit is contained in:
42
src/sync/web/deps.py
Normal file
42
src/sync/web/deps.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Shared FastAPI dependencies.
|
||||
|
||||
The :class:`ServiceState` and :class:`SyncConfig` are created once in
|
||||
``service.run`` and injected into routes via ``Depends``, avoiding global
|
||||
singletons. Routes get a ready-to-use :class:`HealthChecker` rather than the
|
||||
raw state, so each endpoint declares only what it needs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from ..config import SyncConfig
|
||||
from ..health import HealthChecker, ServiceState
|
||||
|
||||
# Bound at app creation time (create_app). Module-level holders are acceptable
|
||||
# here because there is exactly one app per process and they are write-once.
|
||||
_state: ServiceState | None = None
|
||||
_cfg: SyncConfig | None = None
|
||||
|
||||
|
||||
def bind(state: ServiceState, cfg: SyncConfig) -> None:
|
||||
"""Stash the singletons for the lifetime of this app (called once)."""
|
||||
global _state, _cfg
|
||||
_state = state
|
||||
_cfg = cfg
|
||||
|
||||
|
||||
def get_state() -> ServiceState:
|
||||
assert _state is not None, "ServiceState not bound -- call bind() first"
|
||||
return _state
|
||||
|
||||
|
||||
def get_config() -> SyncConfig:
|
||||
assert _cfg is not None, "SyncConfig not bound -- call bind() first"
|
||||
return _cfg
|
||||
|
||||
|
||||
def get_checker(
|
||||
state: ServiceState = Depends(get_state),
|
||||
cfg: SyncConfig = Depends(get_config),
|
||||
) -> HealthChecker:
|
||||
return HealthChecker(state, cfg)
|
||||
Reference in New Issue
Block a user