Kodokon kodokon.com

قابلية الملاحظة: السجلّات، فحص الصحة، الإيقاف السلس

اجعل خدمتك قابلة للملاحظة وقابلة للإيقاف بنظافة: سجلّات JSON، ومسبار الحيوية/الجاهزية (liveness/readiness)، والتعامل مع SIGTERM.

9 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

في بيئة الإنتاج، يُوجَّه السجلّ إلى آلة (Loki، Datadog، CloudWatch)، لا إلى إنسان: أصدِر كائن JSON واحدًا في كل سطر (سطر واحد = حدث واحد) مع مستوى، وطابع زمني بصيغة ISO، وحقول مُهيكلة - لا تُدرِج أبدًا نصوصًا حرّة يستحيل الاستعلام عنها. وثمة تفصيل قليل المعرفة: الكتابة إلى stdout متزامنة إلى ملف أو طرفية، لكنها غير متزامنة إلى أنبوب (pipe). لذا يمكن لمُسجِّل ثرثار أن يحجب حلقة الأحداث تبعًا للوجهة - وهذا هو كامل الغرض من مكتبات مثل pino، التي تُسلسِل بسرعة ويمكنها إحالة عملية الكتابة إلى جهة أخرى.

JAVASCRIPT
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 أثناء الإيقاف، كي يزيل موازِن الحِمل النسخة قبل أن تتوقّف. والخلط بينهما يؤدي إلى إعادة تشغيل عمليات سليمة في اللحظة التي تتعثّر فيها تبعية ما.

JAVASCRIPT
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.

تسلسل الإيقاف السلس: يرسل المُنسِّق (orchestrator) الإشارة SIGTERM؛ فتقلب الجاهزية إلى 503؛ وترفض server.close() الاتصالات الجديدة وتنتظر انتهاء الطلبات الجارية؛ وتُغلِق closeIdleConnections() اتصالات keep-alive الخاملة التي، لولا ذلك، لأبقت close() مُعلَّقة إلى الأبد. ويعمل المؤقّت المُعلَّم بـ unref() كشبكة أمان: فهو يُنهي العملية بعد 10 ثوانٍ، دون أن يمنع بذاته العملية من الخروج قبل ذلك.

JAVASCRIPT
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;
});
الملاذ الأخير: سجّل، ثم اخرج.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. ما الفرق بين مسباري الحيوية والجاهزية؟
    • لا فرق: فهما اسمان للاختبار نفسه
    • يتحقّق مسبار الحيوية من أن العملية قيد التشغيل؛ ويتحقّق مسبار الجاهزية من أنها تستطيع قبول حركة المرور، بما في ذلك التبعيات
    • يعيد مسبار الجاهزية تشغيل الحاوية، ويزيلها مسبار الحيوية من موازِن الحِمل
  2. ماذا تفعل server.close() بالضبط؟
    • تقطع فورًا كل الاتصالات المفتوحة
    • تُنهي العملية بعد مهلة ثابتة قدرها 30 ثانية
    • ترفض الاتصالات الجديدة وتنتظر انتهاء القائمة منها
  3. ماذا ينبغي أن يفعل مُعالِج uncaughtException؟
    • أن يسجّل الخطأ، ثم يُنهي العملية
    • أن يتجاهل الخطأ: فـ Node تتعافى من تلقاء نفسها
    • أن يعيد محاولة العملية المُخفِقة تلقائيًا