المرونة والمراسلة والرصد

التتبع الموزع

18 دقيقة الدرس 8 من 12

التتبع الموزع

عندما يتفرّع طلب HTTP واحد عبر أربع خدمات مصغّرة قبل أن يُعيد الاستجابة، يكاد يكون تشخيص بطء وقت الاستجابة أو خطأ 500 متقطّع أمرًا مستحيلًا بالاعتماد على السجلات العادية. فكل خدمة تكتب أسطر سجلها الخاصة، على ساعتها الخاصة، بتنسيقها الخاص. إن ربطها يدويًا عملية معرّضة للخطأ وبطيئة. يحلّ التتبع الموزع هذه المشكلة بإرفاق معرّف فريد بكل طلب عند نقطة دخوله ونشر هذا المعرّف — تلقائيًا — عبر كل استدعاء لاحق. والنتيجة جدول زمني كامل وسببي يوضّح بالضبط أين أمضى الطلب وقته وأين فشل.

المصطلحات الأساسية

قبل كتابة أي كود من المفيد توضيح المصطلحات:

  • الأثر (Trace) — رحلة الطلب المنطقي الواحد من بدايتها حتى نهايتها. يُعرَّف بمعرّف فريد عالميًا هو traceId.
  • الامتداد (Span) — وحدة عمل مسمّاة ومحدودة بالوقت ضمن الأثر. كل قفزة بين الخدمات وكل استدعاء لقاعدة بيانات وكل عملية مهمة تُمثَّل على شكل امتداد. يعرف الامتداد traceId وهويته spanId وهوية الامتداد الأب parentSpanId.
  • نشر السياق (Context propagation) — الآلية التي تحمل traceId وspanId عبر حدود الخدمات، عادةً كترويسات HTTP مثل traceparent (معيار W3C) أو X-B3-TraceId (تراث Zipkin/Brave).
  • المُصدِّر (Exporter) — المكوّن الذي يرسل الامتدادات المكتملة إلى الخلفية الداعمة (Zipkin أو Jaeger أو جامع متوافق مع OTLP مثل Grafana Tempo).

Micrometer Tracing في Spring Boot 3

استبدل Spring Boot 3 مكتبة Spring Cloud Sleuth القديمة بـ Micrometer Tracing، وهي واجهة محايدة وخفيفة فوق تنفيذات التتبع (Brave/Zipkin أو OpenTelemetry). أضف التالي إلى pom.xml:

<!-- Micrometer Tracing مع جسر Brave --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-brave</artifactId> </dependency> <!-- مُبلِّغ Zipkin — يرسل الامتدادات عبر HTTP إلى خلفية متوافقة مع Zipkin --> <dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-reporter-brave</artifactId> </dependency> <!-- Spring Boot Actuator — يكشف نقاط /actuator والمقاييس --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Brave مقابل OpenTelemetry: كلا الجسرين ينتجان آثارًا متوافقة مع W3C. فضّل micrometer-tracing-bridge-otel إذا كانت مؤسستك تستخدم بالفعل OpenTelemetry Collector، إذ يتحوّل OTLP بسرعة إلى المعيار العالمي. أما Brave فأسهل للبدء وله حجم تبعيات أصغر.

الضبط الأدنى

بمجرد وجود تلك التبعيات على مسار الفئات، تُفعِّل الضبط التلقائي التتبع. اضبطه في application.yml:

management: tracing: sampling: probability: 1.0 # 100 % تتبع؛ اخفضها إلى 0.1 في الإنتاج عالي الحجم spring: zipkin: base-url: http://localhost:9411 enabled: true logging: pattern: level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"

يضخّ نمط التسجيل traceId وspanId في كل سطر سجل تلقائيًا. عند فشل طلب يمكنك نسخ traceId من أي سطر سجل وفتح واجهة Zipkin لرؤية المخطط الانسيابي الكامل.

الأدوات التلقائية

معظم أدوات Spring Boot لا تحتاج كودًا. بمجرد وجود التبعيات:

  • طلبات HTTP الواردة — يبدأ TracingFilter (servlet) أو TracingWebFilter (تفاعلي) أثرًا جديدًا أو ينضم إلى أثر موجود إن وُجدت ترويسة traceparent، ويُغلق الامتداد عند إرسال الاستجابة.
  • استدعاءات HTTP الصادرة عبر RestTemplate أو WebClient — تضيف الضبط التلقائي مُعترِضًا يحقن ترويسات نشر السياق في كل طلب صادر، فتشارك الخدمة اللاحقة تلقائيًا في الأثر ذاته.
  • Spring Data / JDBC — عند وجود spring-boot-starter-data-jpa، تظهر استدعاءات قاعدة البيانات كامتدادات ابن مسمّاة حسب الاستعلام.
  • مُستمعو الرسائل (Kafka، RabbitMQ) — تحمل ترويسات سجل الرسالة سياق الأثر، ويلتقطه أداة المستمع تلقائيًا.
أنشئ كائنات RestTemplate أو WebClient دائمًا عبر البنّاء المُضبَط مسبقًا. إنشاء new RestTemplate() مباشرةً يتجاوز مُعترِض التتبع. بدلًا من ذلك أدخل RestTemplateBuilder (متزامن) أو استخدم WebClient.Builder التلقائي (تفاعلي) — فكلاهما مُهيَّأ مسبقًا مع مرشّح التتبع.
@Configuration public class HttpClientConfig { // صحيح — استخدم البنّاء؛ يُطبَّق مُعترِض التتبع تلقائيًا @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder .connectTimeout(Duration.ofSeconds(3)) .readTimeout(Duration.ofSeconds(5)) .build(); } }

إنشاء امتدادات مخصصة

تغطي الأدوات التلقائية طبقة البنية التحتية. للعمليات التجارية المكلفة أو المعرّضة للفشل — استدعاء واجهة خارجية أو حساب معقد أو بحث في ذاكرة التخزين المؤقت — تريد امتدادًا مخصصًا يُظهر المخطط الانسيابي بالضبط كم استغرق. أدخل Tracer واستخدم الواجهة السلسة:

import io.micrometer.tracing.Span; import io.micrometer.tracing.Tracer; import org.springframework.stereotype.Service; @Service public class PricingService { private final Tracer tracer; private final ExternalPricingClient client; public PricingService(Tracer tracer, ExternalPricingClient client) { this.tracer = tracer; this.client = client; } public BigDecimal fetchLivePrice(String productId) { Span span = tracer.nextSpan() .name("external.pricing.fetch") .tag("product.id", productId) .start(); try (Tracer.SpanInScope ignored = tracer.withSpan(span)) { return client.getPrice(productId); } catch (Exception ex) { span.error(ex); // يعلّم الامتداد كخاطئ في واجهة المستخدم throw ex; } finally { span.end(); // يجب إنهاء الامتداد دائمًا حتى عند الخطأ } } }

بعض النقاط المهمة في هذا النمط:

  • tracer.nextSpan() ينشئ امتدادًا ابنًا من الامتداد النشط الحالي، فيندرج بشكل صحيح في تسلسل الأثر.
  • span.tag() يرفق بيانات وصفية من نوع مفتاح-قيمة تظهر في تفاصيل الامتداد في Zipkin/Jaeger — قيّمة للغاية لتصفية الآثار حسب المنتج أو المستخدم أو المستأجر أو أي بُعد تجاري.
  • span.error(ex) يسجّل الاستثناء ويضع حالة الامتداد كـ ERROR، مما يجعله يبرز فورًا في واجهة التتبع.
  • كتلة finally إلزامية؛ فالامتداد غير المُغلق يُسرّب الذاكرة ولا يصل أبدًا إلى المُصدِّر.

الاعتبارات الأمنية

يمكن للعملاء تزوير ترويسات الأثر. إذا أرسل متصفح أو مُستدعٍ خارجي ترويسة traceparent مصنوعة، ستنضم خدماتك إلى ذلك الأثر — مما قد يُسرّب طبولوجيا الخدمات الداخلية لمهاجم يستطيع ربط بيانات التوقيت. خفّف هذا الخطر بالثقة في سياق الأثر الوارد فقط من المُستدعين الداخليين الموثّقين (مثل الخدمات التي تقدّم شهادة mTLS صالحة أو JWT خاص بالخدمة). عند الحافة (بوابة API)، اشطب ترويسات الأثر الواردة من الإنترنت العام وأعد إصدارها.

كذلك انتبه لما تُرفقه كوسوم للامتداد. الوسم مثل user.id أو request.body يُخزَّن حرفيًا في الخلفية الداعمة للتتبع. عامل نظام التتبع كمخزن للرصد لا للتسجيل، وتجنّب إرفاق البيانات الشخصية أو الأسرار كسمات للامتداد.

استراتيجية أخذ العينات

تتبع 100 % من الطلبات مناسب في التطوير. في الإنتاج بحجم مهم، تصدير كل امتداد إلى خلفية ينشئ تكلفة وتخزينًا غير هيّنين. الاستراتيجيات الشائعة:

  • الاحتمالية (head-based) — أخذ عينة بنسبة ثابتة (مثل 10 %) تُقرَّر عند جذر الأثر. بسيطة وتكلفتها قابلة للتنبؤ. تُضبط بـ management.tracing.sampling.probability=0.1.
  • محدودة المعدل — أخذ عينة لا تزيد عن N أثر في الثانية بصرف النظر عن الحمل. تحمي الخلفية خلال ارتفاعات حركة المرور.
  • القائمة على الذيل (tail-based) — تخزين جميع الامتدادات مؤقتًا والاحتفاظ فقط بالآثار التي تحتوي على خطأ أو تتجاوز حد الاستجابة. تتطلب جامعًا يدعم أخذ عينات الذيل (مثل OpenTelemetry Collector مع معالج tail_sampling). أكثر تعقيدًا تشغيليًا لكنها تلتقط 100 % من الآثار المثيرة للاهتمام دون تكلفة تصدير 100 %.

تشغيل Zipkin محليًا

يمكنك تشغيل نسخة Zipkin في ثوانٍ باستخدام Docker:

docker run -d -p 9411:9411 openzipkin/zipkin

وجّه خدمتك إلى http://localhost:9411، أجرِ عدة استدعاءات HTTP، ثم افتح http://localhost:9411 في المتصفح. اضغط Run Query لرؤية جميع الآثار، ثم اضغط على أي أثر لعرض مخططه الانسيابي. كل امتداد مسمّى باسم الخدمة واسم العملية والمدة وأي وسوم أضفتها.

الخلاصة

يحوّل التتبع الموزع بحر أسطر السجلات المنفصلة إلى جدول زمني منظَّم وبصري لكل طلب. مع Micrometer Tracing وثلاث تبعيات Maven، يُوثّق Spring Boot 3 تلقائيًا جميع حركة HTTP للخادم والعميل واستدعاءات قاعدة البيانات ومستمعي الرسائل. أضف امتدادات مخصصة للعمليات التجارية المهمة باستخدام واجهة Tracer، ارفق وسومًا ذات معنى، أغلق الامتدادات دائمًا في كتلة finally، واختر استراتيجية أخذ عينات توازن بين الرؤية والتكلفة. في الدرس القادم ستتعلّم كيف تُكمّل الآثار بمقاييس ولوحات صحية لإكمال صورة الرصد.