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

381 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Health Check Development Guide
本文档只定义部署稳定性需要的健康检查能力。健康检查不承载业务判断,不替代监控告警,也不用于修复故障。进程下线和 drain 细节见 `docs/graceful-shutdown.md`
## Goals
健康检查必须解决三个问题:
- 负载均衡是否可以把新流量打到这个进程。
- 发布或重启时,进程是否可以安全下线。
- 多机部署时,依赖异常是否会被及时摘流,而不是继续接收请求并放大故障。
健康检查不解决这些问题:
- 不判断房间是否存在。
- 不判断用户是否有权限。
- 不扫描全量 MySQL 表。
- 不做 Redis 修复、房间恢复、outbox 补偿。
- 不在检查路径里触发业务写入。
## Check Types
所有服务都必须区分 `live``ready`
| Type | Meaning | Failure Action |
| --- | --- | --- |
| `live` | 进程还活着,事件循环或 gRPC/HTTP runtime 未卡死 | 由容器平台重启进程 |
| `ready` | 当前进程可以接新流量 | 从负载均衡摘流,不一定重启 |
不要把下游短暂不可用放进 `live`。例如 MySQL 抖动时,`room-service` 应该 `ready=false`,而不是 `live=false` 导致所有副本被重启。
## Required Interfaces
### HTTP Services
HTTP 服务暴露两个只读接口:
```text
GET /healthz/live
GET /healthz/ready
```
适用服务:
- `gateway-service`
- `im-service` 的 HTTP/WebSocket 入口
成功响应:
```json
{
"status": "ok",
"service": "gateway-service",
"node_id": "gateway-node-1",
"time_unix_ms": 1777000000000
}
```
失败响应:
```json
{
"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
```protobuf
grpc.health.v1.Health
```
适用服务:
- `room-service`
- `wallet-service`
- `user-service`
- `activity-service`
- `im-service` 的内部 gRPC 入口
服务名使用完整服务边界名:
```text
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 可连接,用于 `VerifyRoomPresence``CheckSpeakPermission`
`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 服务使用 `curl`gRPC 服务使用 `grpc-health-probe` 或服务提供的 admin health endpoint。
HTTP 示例:
```yaml
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:13000/healthz/ready"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
```
gRPC 示例:
```yaml
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`
推荐变量:
```text
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` 后执行相同状态机:
```text
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。客户端收到后应重连并重新 `auth``subscribe_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 秒刷一条成功日志。
状态变化日志必须包含:
```text
service
node_id
old_status
new_status
failed_check
reason
duration_ms
```
建议指标:
```text
health_ready_status
health_live_status
health_check_duration_ms
health_dependency_failures_total
service_draining
service_shutdown_duration_ms
```
`room-service` 额外指标:
```text
room_lease_check_failures_total
room_snapshot_flush_failures_total
room_outbox_worker_alive
```
`im-service` 额外指标:
```text
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
- 不要用业务接口当健康检查,例如 `CreateRoom``JoinRoom``SendGift`
- 不要在健康检查里创建房间、写 command log、写 outbox。
- 不要把所有下游都塞进每个服务的 ready。
- 不要把 Redis/MySQL 抖动映射成 `live=false`
- 不要让健康检查依赖外网。
- 不要把健康检查失败日志刷屏。
- 不要在滚动更新时跳过 drain。