让你的服务可观测且能干净地停止:JSON 日志、liveness/readiness 探针,以及 SIGTERM 处理。
在 Kodokon 中打开本课在生产环境中,一条日志是给机器(Loki、Datadog、CloudWatch)看的,而不是给人看的:请每行输出一个 JSON 对象(一行 = 一个事件),带上一个级别、一个 ISO 时间戳和结构化的字段 - 绝不要用无法查询的自由格式插值。一个鲜为人知的细节:向 stdout 写入时,写到文件或终端是同步的,但写到管道是异步的。所以一个话痨式的日志器可能会根据目标而阻塞事件循环 - 这正是 pino 这类库的意义所在,它们序列化很快,并且能把写入操作卸载出去。
function log(level, message, fields = {}) {
const entry = {
level,
message,
time: new Date().toISOString(),
pid: process.pid,
...fields,
};
process.stdout.write(JSON.stringify(entry) + "\n");
}
log("info", "server started", { port: 3000 });
log("error", "db unreachable", { retryInMs: 5000 });要区分两种探针。Liveness(存活)回答的是"进程应该被重启吗?":它必须保持极其简单,不依赖任何外部依赖。Readiness(就绪)回答的是"我能接收流量吗?":它可以检查数据库,而且尤其重要的是在关闭期间切换到 503,好让负载均衡器在实例停机之前把它移除。把两者混为一谈会导致健康的进程在某个依赖打个嗝的那一刻就被重启。
import { createServer } from "node:http";
let ready = true;
const server = createServer((req, res) => {
if (req.url === "/healthz") {
res.writeHead(ready ? 200 : 503);
res.end(ready ? "ok" : "draining");
return;
}
res.end("hello");
});
server.listen(3000);
function shutdown() {
ready = false;
server.closeIdleConnections();
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 10_000).unref();
}
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);一次优雅关闭的流程:编排器发送 SIGTERM;你把 readiness 翻转为 503;server.close() 拒绝新连接并等待处理中的请求完成;closeIdleConnections() 关闭那些空闲的 keep-alive 连接,否则它们会无限期地拖住 close()。那个被 unref() 标记的定时器充当安全网:它会在 10 秒后杀掉进程,同时自身又不会阻止进程更早地退出。
process.on("uncaughtException", (err) => {
const entry = {
level: "fatal",
message: err.message,
stack: err.stack,
time: new Date().toISOString(),
};
process.stderr.write(JSON.stringify(entry) + "\n");
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
throw reason;
});