跳到主要内容

健康检查与优雅停机

问题

服务端如何实现健康检查和优雅停机?在 K8s 环境下有什么特别考虑?

答案

健康检查

health-check.ts
import express from 'express';

const app = express();

// 存活检查(liveness):是否需要重启
app.get('/health', (_req, res) => {
res.status(200).json({ status: 'ok' });
});

// 就绪检查(readiness):是否能接收流量
app.get('/ready', async (_req, res) => {
try {
// 检查数据库连接
await db.$queryRaw`SELECT 1`;
// 检查 Redis 连接
await redis.ping();
res.status(200).json({ status: 'ready' });
} catch (error) {
res.status(503).json({ status: 'not ready', error: String(error) });
}
});

K8s 探针配置

deployment.yaml
spec:
containers:
- name: api
livenessProbe: # 失败 → 重启 Pod
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3

readinessProbe: # 失败 → 从 Service 摘除
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5

startupProbe: # 启动完成前不执行其他探针
httpGet:
path: /health
port: 3000
failureThreshold: 30
periodSeconds: 2

优雅停机

graceful-shutdown.ts
const server = app.listen(3000);

let isShuttingDown = false;

async function gracefulShutdown(signal: string) {
console.log(`Received ${signal}, starting graceful shutdown...`);
isShuttingDown = true;

// 1. 停止接受新连接
server.close(async () => {
console.log('HTTP server closed');

// 2. 等待进行中的请求完成
// 3. 关闭数据库连接
await db.$disconnect();
// 4. 关闭 Redis 连接
await redis.quit();
// 5. 清理其他资源
console.log('All resources cleaned up');
process.exit(0);
});

// 超时强制退出
setTimeout(() => {
console.error('Forced shutdown after timeout');
process.exit(1);
}, 30000);
}

// 健康检查中间件:停机中返回 503
app.use((req, res, next) => {
if (isShuttingDown) {
return res.status(503).json({ error: 'Server is shutting down' });
}
next();
});

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

常见面试问题

Q1: liveness 和 readiness 的区别?

答案

  • liveness:应用是否存活。失败 → K8s 重启 Pod
  • readiness:应用是否就绪。失败 → K8s 不发流量到该 Pod

典型场景:应用启动中(readiness 失败但 liveness 正常),或依赖的数据库暂时不可用。

Q2: 为什么需要优雅停机?

答案

直接 kill 进程会导致:

  • 进行中的请求被中断(用户看到 502)
  • 数据库事务未提交
  • 消息队列中的任务丢失

优雅停机确保已接收的请求处理完毕、资源正确释放。

Q3: NestJS 中如何实现优雅停机?

答案

// main.ts
app.enableShutdownHooks(); // 启用生命周期钩子

// service.ts
@Injectable()
class AppService implements OnModuleDestroy {
async onModuleDestroy() {
await this.cleanup();
}
}

相关链接