21.09-健康检查接口
要点
- 健康检查接口用于让监控系统自动检测服务是否可用
- 区分 Liveness Probe(是否活着)和 Readiness Probe(是否准备好接收流量)
- 检查依赖服务的连通性:数据库、缓存、第三方 API
- 返回详细的状态信息,方便排查问题
内容
1. 为什么需要健康检查
在以下场景中,健康检查是必不可少的:
| 场景 | 用途 |
|---|---|
| 负载均衡 | 自动剔除不健康的实例 |
| Kubernetes | Liveness Probe 重启挂掉的 Pod,Readiness Probe 控制流量 |
| 监控系统 | 检测服务是否可用,触发告警 |
| 部署系统 | 部署后自动检查是否启动成功 |
没有健康检查,你只能靠用户反馈才能发现问题。有了健康检查,监控系统会在你发现问题之前主动告警。
2. 基础健康检查
最简单的健康检查接口:
// src/index.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/health', (c) => {
return c.json({ status: 'ok' })
})这个接口返回 {"status": "ok"},状态码 200。监控系统定期请求这个接口,如果返回 5xx 或超时,就认为服务不可用。
但这个接口太简单了,它只告诉你「进程还在」,不告诉你「服务是否能正常工作」。
3. 深度健康检查
更完善的健康检查应该验证依赖服务:
// src/routes/health.ts
app.get('/health', async (c) => {
const checks = {
status: 'ok',
timestamp: new Date().toISOString(),
version: process.env.APP_VERSION || 'unknown',
checks: {} as Record<string, { status: string; latency?: number; error?: string }>,
}
// 1. 检查数据库
const dbStart = Date.now()
try {
await c.env.DB.prepare('SELECT 1').first()
checks.checks.database = {
status: 'ok',
latency: Date.now() - dbStart,
}
} catch (err) {
checks.checks.database = {
status: 'error',
error: err.message,
}
checks.status = 'degraded'
}
// 2. 检查缓存
const cacheStart = Date.now()
try {
await c.env.CACHE.get('health:check')
checks.checks.cache = {
status: 'ok',
latency: Date.now() - cacheStart,
}
} catch (err) {
checks.checks.cache = {
status: 'error',
error: err.message,
}
checks.status = 'degraded'
}
// 3. 检查 LLM API(可选,可能比较慢)
if (c.req.query('deep') === 'true') {
const llmStart = Date.now()
try {
await fetch('https://api.openai.com/v1/models', {
headers: { 'Authorization': `Bearer ${c.env.OPENAI_API_KEY}` },
})
checks.checks.llm = {
status: 'ok',
latency: Date.now() - llmStart,
}
} catch (err) {
checks.checks.llm = {
status: 'error',
error: err.message,
}
checks.status = 'degraded'
}
}
const statusCode = checks.status === 'ok' ? 200 : 503
return c.json(checks, statusCode)
})返回示例:
{
"status": "ok",
"timestamp": "2026-06-21T10:30:00.000Z",
"version": "1.2.3",
"checks": {
"database": {
"status": "ok",
"latency": 5
},
"cache": {
"status": "ok",
"latency": 2
},
"llm": {
"status": "ok",
"latency": 150
}
}
}4. Liveness 和 Readiness
Kubernetes 区分两种健康检查:
4.1 Liveness Probe(存活探针)
问:「进程还活着吗?需要重启吗?」
// 只检查进程是否还在运行
app.get('/health/live', (c) => {
return c.json({ status: 'ok' })
})如果 Liveness Probe 失败,Kubernetes 会重启 Pod。
4.2 Readiness Probe(就绪探针)
问:「服务准备好接收流量了吗?」
// 检查所有依赖服务
app.get('/health/ready', async (c) => {
const checks = await checkDependencies(c.env)
const isReady = Object.values(checks).every(c => c.status === 'ok')
const statusCode = isReady ? 200 : 503
return c.json({
status: isReady ? 'ready' : 'not_ready',
checks,
}, statusCode)
})如果 Readiness Probe 失败,Kubernetes 会把 Pod 从负载均衡中剔除,但不会重启。
5. 依赖检查的实现
// src/lib/health-checks.ts
export interface HealthCheckResult {
status: 'ok' | 'error' | 'degraded'
latency?: number
error?: string
details?: Record<string, unknown>
}
export async function checkDatabase(env: Env): Promise<HealthCheckResult> {
const start = Date.now()
try {
const result = await env.DB.prepare('SELECT 1 as value').first()
return {
status: 'ok',
latency: Date.now() - start,
details: { value: result?.value },
}
} catch (err) {
return {
status: 'error',
latency: Date.now() - start,
error: err.message,
}
}
}
export async function checkCache(env: Env): Promise<HealthCheckResult> {
const start = Date.now()
try {
const key = 'health:check'
const value = await env.CACHE.get(key)
if (!value) {
// 缓存未命中,写入一个测试值
await env.CACHE.put(key, 'ok', { expirationTtl: 60 })
}
return {
status: 'ok',
latency: Date.now() - start,
}
} catch (err) {
return {
status: 'error',
latency: Date.now() - start,
error: err.message,
}
}
}
export async function checkLLMAPI(env: Env): Promise<HealthCheckResult> {
const start = Date.now()
try {
const response = await fetch('https://api.openai.com/v1/models', {
headers: { 'Authorization': `Bearer ${env.OPENAI_API_KEY}` },
})
if (!response.ok) {
return {
status: 'error',
latency: Date.now() - start,
error: `HTTP ${response.status}`,
}
}
return {
status: 'ok',
latency: Date.now() - start,
}
} catch (err) {
return {
status: 'error',
latency: Date.now() - start,
error: err.message,
}
}
}
export async function checkKV(env: Env): Promise<HealthCheckResult> {
const start = Date.now()
try {
const key = 'health:check'
await env.KV.put(key, 'ok')
const value = await env.KV.get(key)
if (value !== 'ok') {
return {
status: 'error',
latency: Date.now() - start,
error: 'KV write/read mismatch',
}
}
return {
status: 'ok',
latency: Date.now() - start,
}
} catch (err) {
return {
status: 'error',
latency: Date.now() - start,
error: err.message,
}
}
}
export async function checkR2(env: Env): Promise<HealthCheckResult> {
const start = Date.now()
try {
const key = 'health/check.txt'
await env.R2.put(key, 'ok')
const obj = await env.R2.get(key)
if (!obj) {
return {
status: 'error',
latency: Date.now() - start,
error: 'R2 write/read mismatch',
}
}
return {
status: 'ok',
latency: Date.now() - start,
}
} catch (err) {
return {
status: 'error',
latency: Date.now() - start,
error: err.message,
}
}
}// src/routes/health.ts
import {
checkDatabase,
checkCache,
checkLLMAPI,
checkKV,
checkR2,
} from '../lib/health-checks'
app.get('/health', async (c) => {
const checks = {
database: await checkDatabase(c.env),
cache: await checkCache(c.env),
kv: await checkKV(c.env),
r2: await checkR2(c.env),
}
// 深度检查(可选)
if (c.req.query('deep') === 'true') {
checks.llm = await checkLLMAPI(c.env)
}
const hasError = Object.values(checks).some(c => c.status === 'error')
const allDegraded = Object.values(checks).every(c => c.status !== 'ok')
let status: 'ok' | 'degraded' | 'error'
if (allDegraded) {
status = 'error'
} else if (hasError) {
status = 'degraded'
} else {
status = 'ok'
}
const statusCode = status === 'ok' ? 200 : 503
return c.json({
status,
timestamp: new Date().toISOString(),
version: process.env.APP_VERSION || 'unknown',
checks,
}, statusCode)
})6. 健康检查的频率和超时
不同场景对健康检查的要求不同:
| 场景 | 频率 | 超时 |
|---|---|---|
| 负载均衡 | 每 5-10 秒 | 2 秒 |
| Kubernetes Liveness | 每 10 秒 | 5 秒 |
| Kubernetes Readiness | 每 10 秒 | 5 秒 |
| 外部监控(UptimeRobot) | 每 1-5 分钟 | 30 秒 |
注意:
- 健康检查不要太频繁,否则会成为性能负担
- 超时时间要小于监控系统的检查间隔
- 深度检查(如 LLM API)可能很慢,只在需要时启用
7. 安全考虑
健康检查接口不应该暴露敏感信息:
// ❌ 错误做法:暴露数据库连接字符串
app.get('/health', (c) => {
return c.json({
database: c.env.DATABASE_URL, // 泄露敏感信息
})
})
// ✅ 正确做法:只返回状态
app.get('/health', (c) => {
return c.json({
status: 'ok',
checks: {
database: { status: 'ok' }, // 只返回状态
},
})
})如果健康检查接口需要鉴权:
// 只对内部监控系统开放
app.get('/health', basicAuth({
username: 'monitor',
password: process.env.HEALTH_CHECK_PASSWORD,
}), async (c) => {
// ...
})或者只对特定 IP 开放:
app.get('/health', async (c, next) => {
const ip = c.req.header('CF-Connecting-IP')
const allowedIPs = ['10.0.0.0/8', '172.16.0.0/12']
if (!isIPInRanges(ip, allowedIPs)) {
return c.json({ error: 'Forbidden' }, 403)
}
await next()
})8. 实战:完整的健康检查系统
// src/routes/health.ts
import { Hono } from 'hono'
import {
checkDatabase,
checkCache,
checkLLMAPI,
checkKV,
checkR2,
} from '../lib/health-checks'
const health = new Hono()
// 1. Liveness Probe:只检查进程是否活着
health.get('/live', (c) => {
return c.json({ status: 'ok' })
})
// 2. Readiness Probe:检查依赖服务
health.get('/ready', async (c) => {
const checks = {
database: await checkDatabase(c.env),
cache: await checkCache(c.env),
kv: await checkKV(c.env),
r2: await checkR2(c.env),
}
const isReady = Object.values(checks).every(c => c.status === 'ok')
return c.json({
status: isReady ? 'ready' : 'not_ready',
checks,
}, isReady ? 200 : 503)
})
// 3. 完整健康检查:包含深度检查
health.get('/', async (c) => {
const checks = {
database: await checkDatabase(c.env),
cache: await checkCache(c.env),
kv: await checkKV(c.env),
r2: await checkR2(c.env),
}
if (c.req.query('deep') === 'true') {
checks.llm = await checkLLMAPI(c.env)
}
const hasError = Object.values(checks).some(c => c.status === 'error')
const allDegraded = Object.values(checks).every(c => c.status !== 'ok')
let status: 'ok' | 'degraded' | 'error'
if (allDegraded) {
status = 'error'
} else if (hasError) {
status = 'degraded'
} else {
status = 'ok'
}
return c.json({
status,
timestamp: new Date().toISOString(),
version: process.env.APP_VERSION || 'unknown',
uptime: process.uptime(),
checks,
}, status === 'ok' ? 200 : 503)
})
export default health// src/index.ts
import health from './routes/health'
app.route('/health', health)9. 小结
健康检查的关键点:
- 基础检查:返回
{"status": "ok"},用于负载均衡和进程存活检测 - 深度检查:验证数据库、缓存、第三方 API 的连通性
- Liveness vs Readiness:前者用于重启,后者用于流量控制
- 性能考虑:健康检查不要太频繁,深度检查只在需要时启用
- 安全考虑:不要暴露敏感信息,可以加鉴权或 IP 白名单
健康检查是运维自动化的基础。没有它,你就只能靠人工巡检。有了它,监控系统会自动发现问题并告警。