أذكرها وكأنها البارحة، ليلة شتاء قارسة، والساعة تجاوزت الثانية صباحًا. كنا، فريق التطوير، في مكالمة طوارئ. أحد أهم عملاء منصتنا يشتكي من أن طلباته تفشل بشكل عشوائي. لا نمط واضح، لا رسالة خطأ صريحة، مجرد “حدث خطأ ما، يرجى المحاولة لاحقًا”. جملة بريئة المظهر، لكنها كانت كابوسًا لنا.
توزّعنا المهام. أنا فتحت سجلات خدمة الطلبات (Orders Service)، وزميلي أحمد غاص في سجلات خدمة الدفع (Payment Service)، وسارة كانت تحاول فك شيفرة ما يحدث في خدمة المصادقة (Auth Service). كل واحد منا يرى جزءًا من الصورة. أنا أرى أن الطلب تم إنشاؤه بنجاح. أحمد يصرخ “ما وصلني إشي يا جماعة!”. سارة تقول إن التوكن (token) الخاص بالمستخدم سليم ومفعوله سارٍ. بقينا هكذا لساعات، ننتقل بين عشرات الملفات النصية، ننسخ ونلصق “معرّفات المستخدمين” و”معرّفات الطلبات” في حقول البحث، في محاولة يائسة لربط الخيوط. شعرنا وكأننا محققون في جريمة ليس لها مسرح، والأدلة مبعثرة في كل مكان.
في تلك اللحظة من الإرهاق واليأس، قلت للفريق: “يا جماعة، شغلنا هذا غلط. نحن نركض في حلقة مفرغة. نحن بحاجة إلى طريقة نرى بها رحلة الطلب كاملة، من أول ما يضغط المستخدم على الزر حتى تظهر له النتيجة”. كان هذا هو المسمار الأخير في نعش طريقتنا القديمة في المراقبة، والباب الذي فتحناه على عالم “التتبع الموزع”.
من نعيم المونوليث إلى جحيم المايكروسيرفس (أحيانًا!)
قبل سنوات، كانت الحياة أبسط. كان لدينا تطبيق واحد ضخم (Monolith). كل الكود في مكان واحد. عندما يحدث خطأ، تفتح ملف سجلات واحد، وتتبع تسلسل الأحداث بسهولة. كان الأمر أشبه بالبحث عن شيء مفقود في شقة من غرفة واحدة.
ثم جاءت الخدمات المصغرة (Microservices) ووعدت بالكثير: قابلية التوسع، سهولة التطوير والنشر المستقل لكل خدمة، فرق عمل أصغر وأكثر تركيزًا. وقد وفت بوعدها في كثير من الجوانب. لكنها أتت مع ضريبة خفية باهظة الثمن: التعقيد في المراقبة وتصحيح الأخطاء.
أصبح النظام الموزع أشبه بمدينة كبيرة. طلب المستخدم الواحد قد يسافر عبر عدة “أحياء” (خدمات). يبدأ رحلته من بوابة الواجهة الأمامية (API Gateway)، ثم يمر بخدمة المصادقة، ثم خدمة المستخدمين، ثم خدمة المنتجات، ثم خدمة الطلبات، وأخيرًا خدمة الدفع. إذا ضاع الطلب أو فشل في أي محطة، فأنت أمام مهمة شبه مستحيلة لتحديد مكان وزمان الحادث بالضبط. لقد انتقلنا من البحث في شقة من غرفة واحدة إلى البحث عن شخص مفقود في مدينة بحجم القاهرة أو لندن بدون أي خيوط.
ما هو التتبع الموزع (Distributed Tracing)؟ دعونا نبسّط الأمور
تخيل أنك تطلب طردًا عبر الإنترنت. تحصل على “رقم تتبع”. هذا الرقم يسمح لك برؤية رحلة الطرد كاملة: متى خرج من المستودع، متى وصل إلى مركز الفرز في مدينتك، متى خرج مع عامل التوصيل، ومتى وصل إلى باب بيتك. كل محطة في هذه الرحلة تسجل تحديثًا باستخدام نفس رقم التتبع.
التتبع الموزع هو بالضبط نفس المبدأ، ولكن للتطبيقات. إنه يعطي لكل طلب يدخل نظامك “رقم تتبع” فريد يسمى Trace ID.
التتبع الموزع هو عملية تتبع مسار طلب واحد عبر مختلف الخدمات المصغرة التي يتفاعل معها في بيئة الأنظمة الموزعة.
المصطلحات الأساسية: Trace, Span, و Context
لفهم آلية العمل، دعنا نتعرف على ثلاثة مصطلحات رئيسية:
- Trace (الأثر/الرحلة الكاملة): يمثل رحلة الطلب بأكملها عبر جميع الخدمات. يتم تعريفه بمعرّف فريد (
Trace ID). يمكنك اعتباره “رقم تتبع الطرد”. - Span (المقطع/المحطة): يمثل وحدة عمل واحدة أو عملية محددة داخل الخدمة (مثل استدعاء قاعدة بيانات، أو طلب HTTP لخدمة أخرى). لكل Span معرّف خاص به (
Span ID)، ويحتوي أيضًا علىTrace IDالخاص بالرحلة كلها. الـ Spans يمكن أن تكون لها علاقة “أب-ابن”، حيث يمثل الأب العملية التي استدعت عملية الابن. - Context (السياق): هي “الحقيبة” التي تحمل معلومات التتبع (مثل
Trace IDوParent Span ID) أثناء انتقالها من خدمة إلى أخرى. عادة ما يتم تمرير هذا السياق عبر هيدرز HTTP (HTTP Headers). هذا هو الغراء الذي يربط كل الـ Spans معًا.
كيف يعمل السحر؟ رحلة طلب من البداية للنهاية
دعنا نعد إلى مثالنا: مستخدم يقوم بإنشاء طلب جديد على منصة تجارة إلكترونية.
المرحلة الأولى: بداية الرحلة
عندما يصل الطلب الأولي (مثلاً، POST /api/orders) إلى أول نقطة في نظامنا (عادة ما تكون API Gateway أو الواجهة الخلفية الرئيسية):
- نظام التتبع يتحقق: هل هناك
Trace IDفي هيدرز الطلب؟ بما أنه طلب جديد، فالجواب هو لا. - يقوم النظام بإنشاء
Trace IDجديد وفريد، وإنشاء أول Span (يسمى الـ Root Span) معSpan IDخاص به. - يتم وضع هذه المعلومات في “سياق” الطلب الحالي.
المرحلة الثانية: تمرير الشعلة (Context Propagation)
الآن، خدمة الطلبات (Orders Service) تحتاج إلى التحقق من صحة بيانات المستخدم عبر استدعاء خدمة المصادقة (Auth Service).
- قبل إرسال طلب HTTP إلى خدمة المصادقة، تقوم مكتبة التتبع “بحقن” (inject) السياق الحالي في هيدرز الطلب الجديد. قد يبدو الهيدر كالتالي:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01. - عندما تستقبل خدمة المصادقة الطلب، ترى هيدر الـ
traceparent. فتفهم فورًا أنها جزء من رحلة أكبر. - تقوم بإنشاء Span جديد خاص بها (مثلاً، “validate_user_token”)، وتستخدم الـ
Trace IDمن الهيدر، وتسجل الـSpan IDللخدمة السابقة كـParent Span ID. - عندما تنتهي خدمة المصادقة من عملها، ترسل بيانات الـ Span الخاص بها (المدة، الحالة، أي أخطاء) إلى مكان مركزي يسمى الـ Collector.
تتكرر هذه العملية مع كل استدعاء بين الخدمات. خدمة الطلبات تستدعي خدمة الدفع، وخدمة الدفع تستدعي بوابة الدفع الخارجية… وفي كل خطوة، يتم تمرير “الشعلة” أو السياق.
المرحلة الثالثة: تجميع الخيوط
كل خدمة في النظام ترسل بيانات الـ Spans الخاصة بها بشكل مستقل إلى الـ Collector. هذا الـ Collector ذكي بما يكفي لتجميع كل الـ Spans التي تحمل نفس الـ Trace ID وإعادة بناء الرحلة الكاملة بالترتيب الزمني الصحيح، مستخدمًا علاقات الأب والابن بين الـ Spans.
النتيجة النهائية هي عرض مرئي، غالبًا على شكل مخطط Gantt، يوضح لك بالضبط:
- ما هي الخدمات التي مر بها الطلب.
- كم من الوقت استغرقت كل عملية في كل خدمة.
- أين حدث التأخير (bottleneck).
- وأهم شيء: أي خدمة فشلت ولماذا.
لقد تحولت عملية البحث العمياء في السجلات إلى تحليل منطقي ومباشر لمخطط واضح.
“حلو هالحكي يا أبو عمر، بس كيف أطبّقه؟” – الأدوات والتقنيات
في الماضي، كان هذا يتطلب الكثير من العمل اليدوي. اليوم، أصبح الأمر أسهل بكثير بفضل المعايير المفتوحة مثل OpenTelemetry (OTel).
OpenTelemetry هو مشروع مدعوم من كبرى الشركات التقنية، ويهدف إلى توحيد طريقة جمع بيانات المراقبة (التتبع، المقاييس، والسجلات). باختصار، هو يوفر لك:
- APIs & SDKs: واجهات برمجية ومكتبات لكل لغات البرمجة الشائعة (Java, Python, Node.js, Go, etc.) لإضافة التتبع إلى كودك.
- Auto-Instrumentation: هذا هو الجزء الأجمل. مكتبات تقوم تلقائيًا بإضافة التتبع لأشهر الأطر والمكتبات (مثل Express.js في Node، أو Spring Boot في Java، أو طلبات HTTP). غالبًا ما يمكنك الحصول على تتبع جيد بأقل من 20 سطرًا من الكود!
- Collector: برنامج وسيط يستقبل البيانات من تطبيقاتك، يمكنه معالجتها (مثلاً، إزالة بيانات حساسة)، ثم يرسلها إلى الواجهة التي تختارها.
أما عن الأدوات التي تعرض لك هذه البيانات (Backends)، فالخيارات كثيرة:
- مفتوحة المصدر: Jaeger و Zipkin هما الأكثر شهرة. يمكنك تنصيبها على سيرفراتك الخاصة.
- حلول تجارية: Datadog, New Relic, Honeycomb وغيرها الكثير. تقدم ميزات متقدمة ودعمًا فنيًا مقابل اشتراك.
مثال بسيط (Node.js مع OpenTelemetry)
لأريك كم هو بسيط البدء، هذا مثال لإضافة تتبع تلقائي لتطبيق Express.js بسيط:
// 1. tracer.js - ملف إعداد التتبع
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
// إعداد الـ Exporter الذي سيرسل البيانات إلى الـ Collector
const exporter = new OTLPTraceExporter({
// url: 'http://localhost:4318/v1/traces', // عنوان الـ Collector
});
const sdk = new NodeSDK({
traceExporter: exporter,
instrumentations: [getNodeAutoInstrumentations()], // تفعيل التتبع التلقائي!
serviceName: 'my-awesome-service', // اسم الخدمة
});
sdk.start();
console.log('Tracing initialized');
// تأكد من إيقاف الـ SDK عند إغلاق التطبيق
process.on('SIGTERM', () => {
sdk.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) => console.log('Error terminating tracing', error))
.finally(() => process.exit(0));
});
// 2. app.js - التطبيق الرئيسي
// يجب استدعاء ملف الإعداد قبل أي كود آخر
require('./tracer.js');
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
// بفضل التتبع التلقائي، سيتم إنشاء Span لهذا الطلب تلقائيًا
res.send('Hello, World!');
});
app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`);
});
لاحظ أننا لم نغير أي شيء في منطق التطبيق نفسه (app.js). كل ما فعلناه هو إضافة ملف الإعداد وتشغيله قبل التطبيق. الآن، كل طلب HTTP وكل إطار عمل مدعوم سيتم تتبعه تلقائيًا. هذا هو سحر الـ Auto-Instrumentation.
نصائح من “الختيار” (مني يعني): دروس من ساحة المعركة
بعد سنوات من استخدام هذه التقنيات، اسمحوا لي أن أقدم لكم بعض النصائح العملية:
- ابدأ بسيطًا ولكن ابدأ الآن: لا تنتظر الكارثة. ابدأ بتطبيق التتبع على خدمة أو خدمتين من الخدمات الأكثر أهمية. الفائدة التي ستجنيها ستكون دافعًا لك لتوسيع النطاق.
- الوسوم (Tags/Attributes) هي كنزك: الـ Trace لوحده جيد، لكن الـ Trace مع سياق غني هو الأفضل. أضف وسومًا مهمة للـ Spans مثل
user.id,tenant.id,order.id. عندما يشتكي مستخدم معين، يمكنك البحث عن كل رحلاته بـuser.idالخاص به. هذا يغير قواعد اللعبة تمامًا. - لا تهمل الأداء (Sampling): تتبع كل طلب في بيئة الإنتاج قد يكون مكلفًا. معظم الأدوات تسمح لك بعملية “أخذ العينات” (Sampling). مثلاً، يمكنك تتبع 10% فقط من الطلبات الناجحة، ولكن 100% من الطلبات التي ينتج عنها خطأ. هذا يعطيك توازنًا جيدًا بين الرؤية والأداء.
- الثالوث المقدس للمراقبة (Observability): التتبع الموزع ليس حلًا سحريًا بمفرده. قوته الحقيقية تظهر عند دمجه مع الركنين الآخرين: المقاييس (Metrics) والسجلات (Logs). الإعداد المثالي هو الذي يسمح لك بالانتقال بسلاسة: ترى ارتفاعًا في معدل الأخطاء (Metric)، فتنتقل إلى الـ Traces الفاشلة لترى أين يقع الخطأ، ثم من الـ Trace المحدد، تقفز إلى السجلات (Logs) الخاصة به لترى التفاصيل الدقيقة.
الخلاصة: من ألغاز إلى قصص مفهومة
في النهاية، التتبع الموزع ليس مجرد أداة تقنية، بل هو تغيير في العقلية. هو الانتقال من ردة الفعل العمياء إلى التحليل الاستباقي. هو ما يحوّل أعطال النظام الموزع من “ألغاز بلا خيوط” إلى “قصص ذات بداية ووسط ونهاية مفهومة”.
الاستثمار في إعداد بنية تحتية جيدة للمراقبة والتتبع قد يبدو جهدًا إضافيًا في البداية، ولكنه سيوفر عليك وعلى فريقك مئات الساعات من البحث المحموم في منتصف الليل. لقد أنقذنا من جحيم البحث في سجلات متفرقة، وأنا على يقين أنه سينقذكم أيضًا.
فلا تنتظروا مكالمة الطوارئ القادمة. ابدأوا رحلتكم مع التتبع الموزع اليوم. مستقبلكم سيشكركم على ذلك. يلا، شدّوا حيلكم! 💪