返回
创建于
状态
公开
这份关于 Hono 优雅停机的总结非常到位。你准确地抓住了 Hono 的核心特性:它是一个逻辑层框架,而网络层的生命周期是由 Runtime 适配器 掌控的。
既然你提到了 K8s (Kubernetes),这正是优雅停机从“理论”走向“实战”最严苛的场景。在 K8s 中,仅仅监听 SIGTERM 有时是不够的,因为 Pod 的摘除(从 Service Endpoint 中移除)与发送信号给容器是异步并行发生的。
以下是针对 K8s 环境下 Hono 应用的深度优化建议:
1. 为什么需要 PreStop Hook?
在 K8s 删除 Pod 时,即便你的 Hono 应用收到了 SIGTERM 并停止接收新连接,此时负载均衡器(如 Nginx Ingress 或网关)可能还没来得及更新路由表。这会导致一小部分新请求仍然被发往正在关闭的 Pod,从而触发 502 Bad Gateway。
解决方案: 在 SIGTERM 真正触发逻辑前,人为延迟几秒。
2. K8s + Hono 完美实践代码
1import { serve } from '@hono/node-server'
2import { Hono } from 'hono'
3
4const app = new Hono()
5let isShuttingDown = false
6
7// 中间件:在停机期间通过探针告知 K8s
8app.get('/healthz', (c) => {
9 if (isShuttingDown) return c.text('Service Unavailable', 503)
10 return c.text('OK')
11})
12
13const server = serve({
14 fetch: app.fetch,
15 port: 3000
16})
17
18async function gracefulShutdown(signal: string) {
19 console.log(`收到信号 ${signal},准备进入优雅停机流程...`)
20 isShuttingDown = true
21
22 // 1. 给负载均衡器留出同步时间(通常 5-10s)
23 // 在 K8s 中,这通常配合 preStop 钩子使用,或者在代码里硬编码等待
24 await new Promise(resolve => setTimeout(resolve, 5000))
25
26 // 2. 停止接收新连接
27 server.close(async (err) => {
28 if (err) {
29 console.error('关闭服务器时出错:', err)
30 process.exit(1)
31 }
32
33 console.log('HTTP 连接已停止,开始清理资源...')
34
35 // 3. 处理存量任务(如数据库断开、消息队列 Offset 提交)
36 try {
37 // await prisma.$disconnect()
38 // await redis.quit()
39 console.log('所有资源已清理完毕')
40 process.exit(0)
41 } catch (e) {
42 console.error('资源清理失败:', e)
43 process.exit(1)
44 }
45 })
46
47 // 4. 强制超时保护
48 setTimeout(() => {
49 console.error('停机超时,强制退出')
50 process.exit(1)
51 }, 30000)
52}
53
54process.on('SIGTERM', () => gracefulShutdown('SIGTERM'))
55process.on('SIGINT', () => gracefulShutdown('SIGINT'))3. K8s 部署配置 (YAML)
为了配合上述代码,你的 deployment.yaml 应该如下配置:
1spec:
2 template:
3 spec:
4 containers:
5 - name: hono-app
6 image: your-hono-image
7 lifecycle:
8 preStop:
9 exec:
10 # 在发送 SIGTERM 之前先等待 5 秒
11 # 确保流量彻底不再进入该 Pod
12 command: ["/bin/sh", "-c", "sleep 5"]
13 terminationGracePeriodSeconds: 60 # 给足存量长任务(如 AI 生成)处理时间关键点补遗:
- terminationGracePeriodSeconds: 默认是 30 秒。如果你之前的“任务卡死”是因为 AI 生成任务通常超过 30 秒,你必须在 K8s 中调大这个值,否则 K8s 会在 30 秒后强制
SIGKILL你的进程。 - 存量任务追踪: 你提到的
activeRequests计数器方案非常棒。在 K8s 环境下,这个计数器应该配合server.close()使用,确保在terminationGracePeriodSeconds结束前,尽可能完成所有 active 任务。 - 连接复用 (Keep-Alive):
@hono/node-server默认处理了 HTTP Keep-Alive。调用server.close()会向所有存量长连接发送Connection: close头部,通知客户端在当前请求结束后关闭连接。
如果你正在处理的是 SSE (Server-Sent Events) 或 WebSocket 等长连接任务,优雅停机的逻辑会更复杂(需要手动通知客户端断开),你需要针对这些场景单独细化吗?