94 lines
3.0 KiB
Python
94 lines
3.0 KiB
Python
|
|
"""健康检查 — 存活探针与就绪探针分离
|
|||
|
|
|
|||
|
|
为什么必须拆开:
|
|||
|
|
- 存活探针(liveness)只回答「进程还活着吗」,绝不能探测外部依赖。
|
|||
|
|
否则数据库抖一下,编排系统会判定进程已死并反复重启容器,
|
|||
|
|
把一次依赖故障放大成全站雪崩。
|
|||
|
|
- 就绪探针(readiness)回答「现在能对外服务吗」。依赖不可用时返回 503,
|
|||
|
|
由负载均衡把该实例摘掉,依赖恢复后自动回来。
|
|||
|
|
"""
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
|
|||
|
|
import anyio
|
|||
|
|
from fastapi import APIRouter
|
|||
|
|
from fastapi.responses import JSONResponse
|
|||
|
|
from sqlalchemy import text
|
|||
|
|
|
|||
|
|
from app.core.config import settings
|
|||
|
|
from app.core.database import AsyncSessionLocal
|
|||
|
|
from app.core.mom_database import mom_engine
|
|||
|
|
|
|||
|
|
logger = logging.getLogger("track.health")
|
|||
|
|
|
|||
|
|
router = APIRouter(tags=["健康检查"])
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _probe_primary_db() -> bool:
|
|||
|
|
"""主库探活 — 业务强依赖,失败即不就绪"""
|
|||
|
|
try:
|
|||
|
|
async with AsyncSessionLocal() as session:
|
|||
|
|
await session.execute(text("SELECT 1"))
|
|||
|
|
return True
|
|||
|
|
except Exception:
|
|||
|
|
logger.exception("主库探活失败")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _probe_mom_db_sync() -> bool:
|
|||
|
|
try:
|
|||
|
|
with mom_engine.connect() as conn:
|
|||
|
|
conn.execute(text("SELECT 1"))
|
|||
|
|
return True
|
|||
|
|
except Exception:
|
|||
|
|
logger.exception("MOM 库探活失败")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _probe_mom_db() -> bool:
|
|||
|
|
# MOM 用的是同步引擎,放线程池执行,避免阻塞事件循环
|
|||
|
|
return await anyio.to_thread.run_sync(_probe_mom_db_sync)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _collect() -> tuple[bool, dict[str, str]]:
|
|||
|
|
primary_ok = await _probe_primary_db()
|
|||
|
|
mom_ok = await _probe_mom_db()
|
|||
|
|
checks = {
|
|||
|
|
"database": "ok" if primary_ok else "fail",
|
|||
|
|
# MOM 是外部只读依赖:挂掉时登录/选料降级,但扫码、流转、看板仍可用。
|
|||
|
|
# 因此只标记 degraded、不摘流量 —— 否则 MOM 一抖就让在产车间全线停摆。
|
|||
|
|
"mom_database": "ok" if mom_ok else "degraded",
|
|||
|
|
}
|
|||
|
|
return primary_ok, checks
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/health/live", include_in_schema=False)
|
|||
|
|
async def liveness() -> dict:
|
|||
|
|
"""存活探针:不触碰任何依赖,恒定快速返回"""
|
|||
|
|
return {"status": "ok"}
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/health/ready", include_in_schema=False)
|
|||
|
|
async def readiness() -> JSONResponse:
|
|||
|
|
"""就绪探针:主库不可用时返回 503,让负载均衡摘流量"""
|
|||
|
|
ready, checks = await _collect()
|
|||
|
|
return JSONResponse(
|
|||
|
|
{"status": "ready" if ready else "not_ready", "checks": checks},
|
|||
|
|
status_code=200 if ready else 503,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/health", include_in_schema=False)
|
|||
|
|
async def health() -> JSONResponse:
|
|||
|
|
"""兼容旧监控脚本:语义等同就绪探针,并附带版本号"""
|
|||
|
|
ready, checks = await _collect()
|
|||
|
|
return JSONResponse(
|
|||
|
|
{
|
|||
|
|
"status": "ok" if ready else "unavailable",
|
|||
|
|
"version": settings.APP_VERSION,
|
|||
|
|
"checks": checks,
|
|||
|
|
},
|
|||
|
|
status_code=200 if ready else 503,
|
|||
|
|
)
|