hyapp-server/docs/health-checks.md
2026-04-25 01:18:30 +08:00

9.8 KiB
Raw Blame History

Health Check Development Guide

本文档只定义部署稳定性需要的健康检查能力。健康检查不承载业务判断,不替代监控告警,也不用于修复故障。进程下线和 drain 细节见 docs/graceful-shutdown.md

Goals

健康检查必须解决三个问题:

  • 负载均衡是否可以把新流量打到这个进程。
  • 发布或重启时,进程是否可以安全下线。
  • 多机部署时,依赖异常是否会被及时摘流,而不是继续接收请求并放大故障。

健康检查不解决这些问题:

  • 不判断房间是否存在。
  • 不判断用户是否有权限。
  • 不扫描全量 MySQL 表。
  • 不做 Redis 修复、房间恢复、outbox 补偿。
  • 不在检查路径里触发业务写入。

Check Types

所有服务都必须区分 liveready

Type Meaning Failure Action
live 进程还活着,事件循环或 gRPC/HTTP runtime 未卡死 由容器平台重启进程
ready 当前进程可以接新流量 从负载均衡摘流,不一定重启

不要把下游短暂不可用放进 live。例如 MySQL 抖动时,room-service 应该 ready=false,而不是 live=false 导致所有副本被重启。

Required Interfaces

HTTP Services

HTTP 服务暴露两个只读接口:

GET /healthz/live
GET /healthz/ready

适用服务:

  • gateway-service
  • im-service 的 HTTP/WebSocket 入口

成功响应:

{
  "status": "ok",
  "service": "gateway-service",
  "node_id": "gateway-node-1",
  "time_unix_ms": 1777000000000
}

失败响应:

{
  "status": "fail",
  "service": "gateway-service",
  "node_id": "gateway-node-1",
  "time_unix_ms": 1777000000000,
  "checks": [
    {
      "name": "room_grpc",
      "status": "fail",
      "message": "deadline exceeded"
    }
  ]
}

状态码规则:

  • live 成功返回 200
  • live 失败返回 500
  • ready 成功返回 200
  • ready 失败返回 503

gRPC Services

gRPC 服务必须注册标准 gRPC Health Checking Protocol

grpc.health.v1.Health

适用服务:

  • room-service
  • wallet-service
  • user-service
  • activity-service
  • im-service 的内部 gRPC 入口

服务名使用完整服务边界名:

gateway-service
room-service
im-service
wallet-service
user-service
activity-service

状态映射:

gRPC Health Status Meaning
SERVING ready
NOT_SERVING not ready
UNKNOWN 启动中或检查器未初始化

本地 Docker 可以用 grpc-health-probe 或服务内置 HTTP admin 口做容器 healthcheck。线上优先使用云负载均衡、Kubernetes 或服务发现系统原生的 gRPC health check。

Service Readiness Rules

gateway-service

live

  • HTTP server runtime 正常。
  • 进程没有进入不可恢复 panic 状态。

ready

  • JWT 配置已加载。
  • HTTP listener 已启动。
  • room-service gRPC 地址可连接。

gateway-service 不检查 MySQL、Redis、IM、wallet。它只关心自己能否把客户端 HTTP 命令转到 room-service

room-service

live

  • gRPC server runtime 正常。
  • Room manager 主循环未停止。
  • Outbox worker 主循环未停止。

ready

  • MySQL PingContext 在超时时间内成功。
  • Redis PING 在超时时间内成功。
  • 当前节点不处于 draining 状态。
  • gRPC listener 已启动。
  • Redis lease 操作能力正常,至少能执行一次轻量读写或 Lua 脚本探测。

wallet-service 建议作为 SendGift 链路的强依赖监控,但不要默认作为 room-service 整体 ready 的硬失败条件;否则 wallet 短暂异常会导致房间基础命令也被摘流。

im-service 不作为 room-service ready 的硬依赖。PublishRoomEvent 同步失败后由 outbox 补投,不能因为 IM 抖动把房间状态服务整体摘掉。

im-service

live

  • HTTP/WebSocket server runtime 正常。
  • gRPC bridge runtime 正常。
  • Session manager 主循环未停止。

ready

  • HTTP listener 已启动。
  • gRPC listener 已启动。
  • 当前节点不处于 draining 状态。
  • room-service gRPC 可连接,用于 VerifyRoomPresenceCheckSpeakPermission

im-service 多机部署后,如果在线状态迁移到 Redis 或消息总线Redis/MQ 必须进入 ready 检查。当前内存版不应假装具备跨节点投递能力。

wallet-service

live

  • gRPC server runtime 正常。

ready

  • gRPC listener 已启动。
  • 未来接入账务数据库后,账务数据库连接必须成功。

user-service

live

  • gRPC server runtime 正常。

ready

  • gRPC listener 已启动。
  • 未来接入用户数据库后,用户数据库连接必须成功。

activity-service

live

  • gRPC server runtime 正常。
  • Consumer 主循环未停止。

ready

  • gRPC listener 已启动。
  • 未来接入 MQ/Redis Stream 后,消费位点存储可访问。
  • 当前节点不处于 draining 状态。

Timeout Budget

健康检查必须快失败,不能拖垮服务。

Check Timeout
本进程状态 50ms
HTTP/gRPC listener 自检 100ms
Redis PING 200ms
MySQL PingContext 300ms
下游 gRPC health 300ms

单次 ready 总耗时建议小于 500ms。如果检查项较多,使用并发检查并设置总 deadline。

Docker Compose

本地 docker-compose.yml 应为每个服务配置 healthcheck。HTTP 服务使用 curlgRPC 服务使用 grpc-health-probe 或服务提供的 admin health endpoint。

HTTP 示例:

healthcheck:
  test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:13000/healthz/ready"]
  interval: 5s
  timeout: 3s
  retries: 20
  start_period: 10s

gRPC 示例:

healthcheck:
  test: ["CMD-SHELL", "grpc-health-probe -addr=127.0.0.1:13001 -service=room-service"]
  interval: 5s
  timeout: 3s
  retries: 20
  start_period: 10s

depends_on.condition=service_healthy 只用于本地编排启动顺序。线上不能依赖 compose 的启动顺序保证服务稳定,必须依赖负载均衡 readiness 和服务自身重试。

Online Deployment

多机部署时,每个实例必须有唯一 node_id

推荐变量:

SERVICE_NAME=room-service
NODE_ID=room-prod-az1-001
HEALTH_READY_TIMEOUT=500ms
DRAIN_TIMEOUT=30s

线上接入规则:

  • SLB、Kubernetes Service、Nacos、Consul 只把 ready=true 的实例放进可用池。
  • 发布新版本时先等待新实例 ready=true,再下线旧实例。
  • 下线旧实例时先切 ready=false,再进入 drain最后退出进程。
  • 不允许直接 kill -9 正常发布中的服务。

Graceful Shutdown

所有服务收到 SIGTERM 后执行相同状态机:

running -> draining -> stopped

draining 要求:

  • ready 立即返回失败。
  • 停止接收新连接或新请求。
  • 等待当前请求完成,超过 deadline 后强制关闭。
  • 关闭前写清必要的部署状态日志。

各服务额外要求:

Service Drain Requirement
gateway-service 停止接新 HTTP 请求,等待已有请求结束
room-service 停止接管新房间,停止或释放 lease刷 snapshot暂停 outbox 当前批次后退出
im-service 停止接新 WS向连接发送服务重启关闭码给客户端重连窗口
wallet-service 等待当前扣费 RPC 完成,禁止中途截断
activity-service 完成当前消费消息或安全提交失败状态

WebSocket 下线建议使用 close code 1012,表示 service restart。客户端收到后应重连并重新 authsubscribe_room

Room Service Lease Safety

room-service 的健康检查必须和 Redis lease 下线策略配合。

发布或故障时:

  1. 节点进入 draining。
  2. ready=false,负载均衡不再打新请求。
  3. 节点停止获取新的 room:route:{room_id} lease。
  4. 已持有 Room Cell 刷 snapshot。
  5. lease 释放或自然过期。
  6. 新节点接到请求后通过 snapshot + command log 恢复。

禁止在 ready=false 之后继续接管新房间。否则滚动发布时会出现旧节点持续获得新 room lease导致下线时间不可控。

Observability Fields

健康检查日志只在状态变化时输出,不能每 5 秒刷一条成功日志。

状态变化日志必须包含:

service
node_id
old_status
new_status
failed_check
reason
duration_ms

建议指标:

health_ready_status
health_live_status
health_check_duration_ms
health_dependency_failures_total
service_draining
service_shutdown_duration_ms

room-service 额外指标:

room_lease_check_failures_total
room_snapshot_flush_failures_total
room_outbox_worker_alive

im-service 额外指标:

ws_active_connections
ws_draining_connections
room_subscription_count

Implementation Checklist

新增或修改服务时,必须检查:

  • 是否提供 live
  • 是否提供 ready
  • ready 是否包含本服务真正的硬依赖。
  • 下游依赖失败是否只影响 ready,没有误杀 live
  • 检查是否有总 timeout。
  • 检查路径是否没有业务写入。
  • SIGTERM 后是否立即让 ready=false
  • Dockerfile 是否包含 healthcheck 所需工具,或服务是否提供可被容器调用的 health endpoint。
  • docker-compose.yml 是否配置 service healthcheck。
  • 线上部署脚本是否等待新实例 ready 后再切流。

Do Not

  • 不要用业务接口当健康检查,例如 CreateRoomJoinRoomSendGift
  • 不要在健康检查里创建房间、写 command log、写 outbox。
  • 不要把所有下游都塞进每个服务的 ready。
  • 不要把 Redis/MySQL 抖动映射成 live=false
  • 不要让健康检查依赖外网。
  • 不要把健康检查失败日志刷屏。
  • 不要在滚动更新时跳过 drain。