أساسيات البث مع Kafka
أساسيات البث مع Kafka
تناولت الدروس السابقة في هذا البرنامج التعليمي أنماط المرونة — قواطع الدارة وإعادة المحاولة والحواجز — التي تحمي الخدمة من فشل التبعيات. افترضت تلك الأنماط نموذج الطلب/الاستجابة المتزامن. يُدخل Apache Kafka نموذجًا مختلفًا جذريًا: البث الحدثي (event streaming). بدلًا من انتظار المُستدعي للردّ، يُلحق المُنتِج حدثًا ثابتًا بسجل دائم، ويقرأ مستهلك واحد أو أكثر من ذلك السجل بشكل مستقل وبالسرعة التي يناسبه.
يُقدّم هذا الدرس التجريدات الأساسية لـ Kafka ويوضح كيفية إنتاج الرسائل واستهلاكها من تطبيق Spring Boot 3 باستخدام Spring Cloud Stream — التجريد على مستوى الإطار الذي يتيح لك تبديل وسطاء الرسائل دون إعادة كتابة منطق الأعمال.
لماذا Kafka؟
تحذف قوائم الانتظار التقليدية (RabbitMQ وActiveMQ) الرسالة فور استهلاكها بنجاح. أما Kafka فمختلف: إذ يُمثّل سجلًا موزعًا مُكرَّرًا (distributed replicated commit log). تُحتفظ بالرسائل لفترة قابلة للضبط (ساعات أو أيام أو أسابيع) بغضّ النظر عن قراءتها. يمنحك هذا خصائص يصعب تحقيقها بقوائم الانتظار:
- إعادة التشغيل: يمكن لمستهلك جديد البدء من أول السجل وإعادة بناء الحالة.
- التوزيع على نطاق واسع: يمكن لآلاف مجموعات المستهلكين المستقلة قراءة الموضوع ذاته دون أي تنسيق.
- دلالات التنفيذ مرة واحدة بالضبط (بإعداد دقيق): تتيح المعاملات والمنتجون المتساوون ضمان معالجة الحدث مرة واحدة تمامًا حتى في حالات الفشل.
- ضمانات الترتيب: الرسائل داخل قسم واحد مُرتَّبة ترتيبًا صارمًا.
المفاهيم الأساسية لـ Kafka
قبل كتابة أي كود تأكد من وضوح المصطلحات:
- الموضوع (Topic): سجل مُسمَّى يُلحَق به فقط. فكّر فيه كفئة لأحداثك (مثل
order-placedوpayment-processed). - القسم (Partition): يُقسَّم الموضوع إلى قسم واحد أو أكثر لتحقيق التوازي. كل قسم سجل مستقل مُرتَّب.
- الإزاحة (Offset): الموضع التسلسلي للرسالة داخل القسم. لا يُزيل Kafka الرسائل افتراضيًا؛ يتتبع المستهلكون إزاحتهم الخاصة.
- المُنتِج (Producer): يكتب السجلات في موضوع. يختار القسم الذي يكتب فيه (تناوب أو تجزئة المفتاح أو مخصص).
- مجموعة المستهلكين (Consumer group): مستهلك واحد أو أكثر يتشاركون معرف مجموعة. يُوزّع Kafka الأقسام على أعضاء المجموعة بحيث يستهلك كل قسم عضو واحد في آنٍ واحد. يمكن لمجموعات متعددة قراءة الموضوع ذاته بشكل مستقل.
- الوسيط (Broker): خادم Kafka. تحتوي المجموعة على وسطاء متعددين للتكرار وتوزيع الحمل.
نظرة عامة على Spring Cloud Stream
يُغلّف Spring Cloud Stream (SCS) عميل Kafka خلف تجريد رابط (binder). يعمل كود تطبيقك مع حبوب دالية — Supplier وFunction وConsumer من java.util.function. يُعيّن SCS هذه إلى مواضيع Kafka عبر الإعداد مما يتيح تبديل الرابط (إلى RabbitMQ مثلًا) دون لمس منطق الأعمال.
أضف رابط Kafka إلى pom.xml:
يُوازن BOM الخاص بـ Spring Cloud (المُدار عبر الأصل أو dependencyManagement) الإصدار مع إصدار Spring Boot. لـ Spring Boot 3.x استخدم Spring Cloud 2023.x.
تعريف منتج باستخدام Supplier
تُستطلَع حبة Supplier<T> من قِبل الإطار وفق جدول زمني وتُرسَل قيمتها المُعادة كرسالة. هذا مفيد للمصادر القائمة على الاستطلاع (القراءة من قاعدة بيانات أو توليد أحداث نبضات قلب). للإنتاج المدفوع بالأحداث استخدم StreamBridge بدلًا من ذلك (موضح بعد مثال المستهلك).
اربطها بموضوع Kafka في application.yml:
<اسمالدالة>-in-0 (مدخل) و<اسمالدالة>-out-0 (مخرج). الرقم 0 هو الفهرس للدوال متعددة المدخلات/المخرجات. تحقق دائمًا من اسم الارتباط أو حدده صراحةً عبر spring.cloud.function.definition.
تعريف مستهلك باستخدام java.util.function.Consumer
تستقبل حبة Consumer<T> الرسائل من موضوع مدخلها المرتبط:
تعيين group أمر بالغ الأهمية في الإنتاج. بدونه يُنشئ SCS مجموعة مجهولة عند كل إعادة تشغيل فتبدأ خدمتك دائمًا بالقراءة من أحدث إزاحة — تُفقد الأحداث الغائبة خلال فترة التوقف بشكل دائم.
الإنتاج الإلزامي باستخدام StreamBridge
عندما تحتاج إلى إرسال رسالة استجابةً لطلب HTTP أو محفز آخر (وليس وفق جدول زمني)، أدرج StreamBridge:
مفاتيح الرسائل والتقسيم
يضمن Kafka الترتيب فقط داخل قسم واحد. إذا كنت تحتاج إلى معالجة جميع أحداث الطلب ذاته بالترتيب، يجب أن تصل جميعها إلى القسم ذاته. يتحقق ذلك بتعيين مفتاح رسالة. يكشف SCS عن هذا عبر رأس KafkaHeaders.MESSAGE_KEY:
يجزّئ Kafka المفتاح لتحديد القسم، لذا تصل جميع الأحداث التي تشترك في orderId ذاته إلى القسم ذاته وتُعالج بترتيب وصولها.
معالجة الأخطاء ومواضيع الرسائل الميتة
إذا رمى مستهلك استثناءً يُعيد SCS المحاولة افتراضيًا (قابل للضبط عبر maxAttempts). بعد استنفاد المحاولات يمكن إرسال الرسالة إلى موضوع الرسائل الميتة (DLT) بدلًا من إسقاطها بصمت:
اعتبارات الأمان
يجب أن تُطبّق مجموعات Kafka في الإنتاج المصادقة والتشفير. الأسلوب الأكثر شيوعًا هو SASL/SCRAM عبر TLS:
حمّل بيانات الاعتماد دائمًا من متغيرات البيئة أو مدير أسرار (Vault أو AWS Secrets Manager). كلمة مرور بنص عادي في application.yml مُلتزَم بها في مستودع الكود خطيرة بالقدر ذاته من كلمة مرور قاعدة بيانات مكشوفة.
بيئة التطوير المحلية مع Docker Compose
أطلق مجموعة Kafka بسيطة للتطوير المحلي بملف docker-compose.yml واحد:
بدلًا من ذلك استخدم وضع KRaft الأحدث (Kafka دون Zookeeper) المتاح في صور Confluent cp-kafka 7.4+ بحاوية واحدة.
الخلاصة
يجعل نموذج سجل Kafka الدائم القابل لإعادة التشغيل أساسًا لمعماريات الخدمات المصغرة المدفوعة بالأحداث. يتيح لك Spring Cloud Stream إنتاج رسائل Kafka واستهلاكها عبر حبوب Spring المألوفة — Supplier وConsumer وStreamBridge — مع ربط الطوبولوجيا بالكامل عبر الإعداد. الأنماط الواجب إتقانها فورًا هي: اضبط دائمًا مجموعة مستهلكين group، واستخدم مفاتيح الرسائل لضمانات الترتيب، وضبط موضوع رسائل ميتة حتى لا يُفقد أي حدث بصمت. يتناول الدرس القادم التتبع الموزع الذي يصبح ضروريًا بمجرد تدفق الأحداث بشكل غير متزامن عبر الخدمات.