منتجو Kafka ومستهلكوه في بيئة الإنتاج
منتجو Kafka ومستهلكوه في بيئة الإنتاج
يتطلب تشغيل Kafka على نطاق واسع التحكم الدقيق في كيفية كتابة المنتجين وقراءة المستهلكين. الركائز الأربع لهذا الدرس — دلالات الإقرار، والإنتاج غير القابل للتكرار، ومجموعات المستهلكين، ومراقبة التأخر — تحكم كل قرار موثوقية وإنتاجية على المسار الحرج. هنا تكسب المجموعة التي "تعمل في الاختبار" ثقة الإنتاج أو تفقدها.
إقرارات المنتج (acks)
يتحكم إعداد acks في عدد النسخ المتماثلة من الوسيط التي يجب أن تؤكد الكتابة قبل أن يعتبرها العميل ناجحة. توجد ثلاث قيم عملية:
acks=0— أرسل وانسَ. يرسل المنتج ويستمر دون أي ضمان للمتانة. مناسب فقط للبيانات عالية الحجم المتسامحة مع الفقد (تتبع النقرات، مقاييس ping).acks=1— يُقرّ قائد القسم. أسرع منall، لكن إذا فشل القائد قبل نسخ الأتباع، تُفقد الرسالة بصمت. شائع في الكود القديم؛ تجنبه لأي شيء مالي أو مرتبط بالتدقيق.acks=all(أوacks=-1) — ينتظر القائد حتى تُقرّ جميع النسخ المتزامنة (ISR). بالاقتران معmin.insync.replicas=2على الوسيط، هذا هو الحد الأدنى للمتانة على مستوى الإنتاج. يضيف حوالي 5–15 ملي ثانية من زمن الاستجابة مقارنة بـacks=1، وهي تكلفة تستحق الدفع دائمًا تقريبًا.
acks=all لا يوفر أي حماية إذا كان min.insync.replicas=1 (الإعداد الافتراضي للوسيط). اضبط دائمًا min.insync.replicas=2 (أو 3 لمواضيع RF=3 التي تحمل بيانات مرتبطة بـ SLO). عدم التطابق هذا هو أحد أكثر تكوينات فقدان البيانات الصامتة شيوعًا في مجموعات Kafka الإنتاجية.
المنتجون غير القابلون للتكرار (Idempotent Producers)
إعادة المحاولات الشبكية دون idempotence تُنشئ تكرارات. عند تعيين enable.idempotence=true، يُعيّن الوسيط لكل منتج PID (معرّف المنتج) ويتتبع رقم تسلسليًا متصاعدًا لكل قسم. إذا تلقى الوسيط دفعة مُعاد إرسالها سبق أن التزم بها، يزيل تكرارها بصمت — دلالة exactly-once ضمن جلسة منتج واحدة، دون أي تكلفة للمعاملات.
تتطلب Idempotence: acks=all، وmax.in.flight.requests.per.connection <= 5، وretries > 0. يُطبّق عميل Kafka هذه المتطلبات تلقائيًا عند ضبط enable.idempotence=true؛ لا تتجاوزها بشكل فردي وإلا سيرفض الوسيط تسجيل المنتج.
transactional.id + initTransactions()). تضيف المعاملات زمن استجابة ملحوظًا — احتفظ بها لتدفقات الدفع والمخزون ودفاتر الأستاذ.
مجموعات المستهلكين وتوزيع الأقسام
مجموعة المستهلكين هي وحدة الاستهلاك المتوازي في Kafka. يشترك كل مستهلك في المجموعة في نفس الموضوع (الموضوعات)؛ يُعيّن منسق المجموعة (وسيط) كل قسم لمستهلك واحد بالضبط. يمنح هذا التوسع الأفقي: موضوع من 60 قسمًا يمكن تصريفه بواسطة ما يصل إلى 60 مستهلكًا في نفس المجموعة في وقت واحد. المستهلكون الزائدون عن عدد الأقسام يجلسون خاملين — خطأ شائع في الإفراط في التوفير.
إعادة التوازن: الأسباب والتكلفة والتخفيف
تُشغَّل إعادة التوازن عند تغيير عضوية المجموعة: ينضم مستهلك أو يتعطل أو يفشل في إرسال النبضات خلال session.timeout.ms. خلال إعادة التوازن المتحمسة (الافتراضي قبل الإصدار 2.4)، يُسقط جميع المستهلكين جميع الأقسام ويُعيدون التعيين من الصفر — توقف كامل يوقف الاستهلاك على مستوى المجموعة لثوانٍ إلى عشرات الثواني في المجموعات الكبيرة.
تخفيفات الإنتاج، بترتيب التأثير:
- إعادة التوازن التعاونية اللاصقة — اضبط
partition.assignment.strategy=CooperativeStickyAssignor. يُلغى فقط الأقسام التي تحتاج إلى الانتقال؛ يستمر الباقي في المعالجة. متوفر منذ Kafka 2.4 والافتراضي المقبول الوحيد للخدمات الجديدة. - عضوية المجموعة الثابتة — عيّن
group.instance.idثابتًا (مثل اسم الـ pod). يحتفظ الوسيط بتعيين قسم المستهلك لمدةsession.timeout.msبعد قطع الاتصال قبل تشغيل إعادة التوازن. إعادة تشغيل الـ pod في عمليات النشر المتدرجة لم تعد تسبب إعادة ترتيب كاملة للمجموعة. - ضبط مهلات النبضات/الجلسة — الافتراضي
session.timeout.ms=45000طويل جدًا لاكتشاف الفشل السريع. الضبط الشائع في الإنتاج:session.timeout.ms=15000،heartbeat.interval.ms=4000،max.poll.interval.ms=300000. ارفعmax.poll.interval.msإذا استغرقت معالجة دفعة واحدة دقائق بشكل مشروع (استدلال ML، كتابات بطيئة إلى قاعدة البيانات).
التزامات الإزاحة: اليدوية مقابل التلقائية
enable.auto.commit=true يُلتزم بالإزاحات كل auto.commit.interval.ms (الافتراضي 5000 ملي ثانية) بغض النظر عما إذا كان تطبيقك قد نجح في معالجة تلك الرسائل. التعطل بين الالتزام التلقائي وإكمال المعالجة يعني تخطي تلك الرسائل بصمت عند إعادة التشغيل. هذا هو السبب الأساسي لفقدان البيانات في تطبيقات مستهلك Kafka.
مع الالتزامات اليدوية، استخدم الالتزام بعد المعالجة: استدعِ commitSync() أو commitAsync() فقط بعد نجاح الكتابة الأسفل (upsert في قاعدة البيانات، استدعاء API أسفل، إلخ). للدلالة exactly-once من البداية إلى النهاية، الخيار الوحيد الموثوق هو معاملات Kafka أو الكتابات الأسفل غير القابلة للتكرار مع الالتزامات اليدوية.
commitAsync اليدوي مع احتياطي متزامن عند الفشل، مقترنًا بكتابات أسفل غير قابلة للتكرار (upsert بمعرف الحدث). هذا أبسط من معاملات Kafka ويتعامل بشكل أنيق مع حالة 99.99% من التكرارات الأحادية الرسالة.
تأخر المستهلك: أهم مقياس تشغيلي
تأخر المستهلك هو الفرق بين أحدث إزاحة في قسم (إزاحة نهاية السجل) والإزاحة الأخيرة الملتزم بها لمجموعة مستهلكين. يُقاس بـ عدد الرسائل، وليس بالوقت — تأخر 50,000 بمعدل 10,000 رسالة/ثانية يعني 5 ثوانٍ من الأعمال المتراكمة؛ نفس التأخر بمعدل 100 رسالة/ثانية يعني 8 دقائق.
إشارات التأخر الرئيسية وعتباتها (اضبطها وفق SLO):
- تأخر متنامٍ — معدل الاستهلاك < معدل الإنتاج. وسّع المستهلكين (حتى عدد الأقسام) أو زد
max.poll.records. - تأخر ثابت غير صفري — المستهلك يواكب البث المباشر لكن لا يُصفّي الأعمال المتراكمة أبدًا. يحدث عادةً بسبب موجة لم تتعافَ منها بالكامل، أو إعادة تشغيل مستهلك تأخر.
- ارتفاع مفاجئ في التأخر — إعادة توازن جارية، تعطل مستهلك، أو تبعية أسفل بطيئة. تنبيه فوري إذا تجاوز ما يعادل 60 ثانية.
على نطاق واسع، يُكشف تأخر كل قسم عبر JMX exporter ويُعرض في Grafana (انظر درس المراقبة). اضبط عتبتَي تنبيه: تحذير عند ما يعادل 60 ثانية من التأخر، وحرج عند نقطة خرق SLO. اربط التنبيه الحرج مباشرةً بدورة المناوبة — مجموعة مستهلكين تتوقف عن الالتزام بالإزاحات في خط أنابيب المدفوعات هي حادثة P1.
تجميع الأمر: قائمة التحقق الإنتاجية
- اضبط
acks=all+min.insync.replicas=2على جميع المواضيع المرتبطة بـ SLO. - مكّن
enable.idempotence=trueعلى جميع المنتجين افتراضيًا؛ أضف المعاملات فقط حيثما تطلب الذرية عبر أقسام متعددة. - استخدم
CooperativeStickyAssignorوالعضوية الثابتة في المجموعة لجميع خدمات المستهلك طويلة الأمد. - عطّل الالتزام التلقائي؛ التزم بالإزاحات فقط بعد المعالجة الأسفل الناجحة.
- تنبيه على سرعة تأخر المستهلك (وليس فقط العدد المطلق) واربط التنبيهات الحرجة بالمناوبة.
- اختبر سلوك إعادة التوازن في الاختبار: أوقف pod مستهلك في منتصف الدفعة وتحقق من عدم فقدان أي رسائل أو تخطيها بشكل دائم.