بنية التخزين المؤقت والمراسلة

معمارية كافكا للمشغّلين

22 دقيقة الدرس 4 من 30

معمارية كافكا للمشغّلين

Apache Kafka هو سجل توزيعي (commit log) يتنكر في هيئة وسيط رسائل. فهم معماريته الداخلية — ليس فقط كيفية استخدامه من منظور المنتج أو المستهلك، بل كيف تُخزَّن البيانات وتُنسَخ وتُنسَّق عبر الكتلة — هو ما يميز المشغّلين القادرين على إبقاء كتلة Kafka سليمة على نطاق واسع عن أولئك الذين يُستدعون في الثالثة صباحًا دون أن يعرفوا من أين يبدؤون. يرسم هذا الدرس المعمارية الداخلية بالكامل: الوسطاء، والمواضيع، والأقسام، والنسخ المتماثلة، والمتحكم — بمستوى من التفصيل يُمكّنك من استيعاب أسباب الفشل في الإنتاج قبل وقوعها.

الوسطاء: وحدة التوسع

الوسيط في Kafka (broker) هو عملية JVM عادية تعمل على مضيف. مهمته تخزين وخدمة مقاطع السجلات للأقسام المخصصة له. تعمل الكتلة الإنتاجية في كبرى شركات التقنية على ما بين 12 ومئات الوسطاء، عادةً على مضيفات مخصصة مع NVMe محلية أو نسخ محسَّنة عالية الإنتاجية. يُعرَّف كل وسيط برقم broker.id، أو في وضع KRaft برقم UUID يؤدي الغرض ذاته.

يخزّن الوسيط بيانات الأقسام على القرص كملفات مقاطع غير قابلة للتغيير تعمل بالإلحاق فقط. كل مقطع عبارة عن زوج: ملف .log يحتوي على بايتات الرسائل الخام، وملف .index يعيّن الإزاحات إلى مواقع البايت. عندما يصل المقطع إلى الحجم المضبوط في log.segment.bytes (الافتراضي 1 غيغابايت)، يُحدَّث إلى مقطع نشط جديد. لا يعيد الوسيط الكتابة فوق المقاطع الموجودة أبدًا — يُلحق فقط ثم يحذف المقاطع القديمة عبر سياسة الاحتفاظ. هذا هو جوهر التصميم الذي يجعل Kafka سريعًا: الإدخال/الإخراج المتسلسل أسرع بمرتبة عن الإدخال/الإخراج العشوائي.

Kafka ليس قاعدة بيانات، لكنه يتصرف كواحدة. كل رسالة تُكتب في قسم تُحفظ بشكل دائم على القرص قبل أن يُقرّ الوسيط للمنتج (عند استخدام acks=all). نموذج التخزين القائم على السجل يعني أن التعافي بعد تعطل هو ببساطة إعادة التشغيل من آخر إزاحة مُودَعة.

إعدادات الوسيط الحيوية التي يجب على كل مشغّل معرفتها:

# /etc/kafka/server.properties — critical broker settings # Disk and storage log.dirs=/data/kafka/logs # Comma-separated list of log dirs log.segment.bytes=1073741824 # Roll segment at 1 GB log.retention.bytes=107374182400 # Retain up to 100 GB per partition log.retention.hours=168 # OR retain for 7 days (whichever first) log.retention.check.interval.ms=300000 # Check every 5 min # Replication default.replication.factor=3 # MINIMUM for production min.insync.replicas=2 # Producers with acks=all need 2 ACKs unclean.leader.election.enable=false # NEVER allow stale replicas to become leader # Performance num.io.threads=8 # I/O threads per log dir num.network.threads=3 # Network handler threads socket.send.buffer.bytes=102400 socket.receive.buffer.bytes=102400 socket.request.max.bytes=104857600 # Max request size (100 MB) # JVM heap — set externally via KAFKA_HEAP_OPTS # KAFKA_HEAP_OPTS="-Xms6g -Xmx6g" # 6 GB, no heap growth at runtime

المواضيع والأقسام والسجل

الموضوع هو تغذية منطقية من السجلات. إنه مساحة أسماء، لا وحدة تخزين. تعيش جميع البيانات الفعلية في الأقسام. عندما تنشئ موضوعًا بـ --partitions 12، تُنشئ 12 سجلًا مستقلًا مرتبًا بالإلحاق فقط موزعة عبر الكتلة. يُخزَّن كل قسم على وسيط واحد بالضبط في أي لحظة (القائد)، مع نسخ على وسطاء إضافيين (المتابعون/النسخ).

يحدد عدد الأقسام الحد الأقصى للتوازي في الاستهلاك: لا يمكن لمجموعة مستهلكين أن يكون لها أكثر من مستهلك نشط واحد لكل قسم. إذا كان لديك 12 قسمًا و20 مستهلكًا في مجموعة، سيكون 8 مستهلكين خاملين. عدد الأقسام دائم في اتجاه الزيادة — يمكنك زيادة الأقسام لكن لا يمكنك إنقاصها، وزيادة الأقسام تكسر الترتيب المبني على المفاتيح للمستهلكين الموجودين. حدد عدد الأقسام بشكل صحيح عند الإنشاء.

# Create a topic with explicit partition and replication settings kafka-topics.sh --bootstrap-server kafka1:9092 \ --create \ --topic orders \ --partitions 24 \ --replication-factor 3 \ --config min.insync.replicas=2 \ --config retention.ms=604800000 \ --config retention.bytes=53687091200 # Describe the topic — shows leader, replicas, ISR per partition kafka-topics.sh --bootstrap-server kafka1:9092 \ --describe --topic orders # Example output (truncated): # Topic: orders Partition: 0 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2 # Topic: orders Partition: 1 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3 # Topic: orders Partition: 2 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1 # Increase partition count (irreversible — plan carefully) kafka-topics.sh --bootstrap-server kafka1:9092 \ --alter --topic orders --partitions 48

النسخ المتماثلة: مجموعة ISR وعقد الدوام

النسخ المتماثلة في Kafka قائمة على الجلب (pull-based): تجلب نسخ المتابعين باستمرار من القائد، لا العكس. لكل قسم وسيط قائد واحد بالضبط وصفر أو أكثر من وسطاء المتابعين. يشكّل هؤلاء مجتمعين مجموعة النسخ. الجزء من النسخ المتزامن مع القائد يُسمى مجموعة النسخ المتزامنة (ISR).

مجموعة ISR هي قلب ضمان الدوام في Kafka. عندما يرسل منتج بـ acks=all، لا يُقرّ القائد الكتابة حتى يحفظ كل وسيط في مجموعة ISR الرسالة. إذا تخلف متابع بأكثر من replica.lag.time.max.ms (الافتراضي 30 ثانية)، يُزال من مجموعة ISR، مما يُدهور مستوى التكرار لديك بصمت.

الثابت الحيوي للمشغّل: min.insync.replicas هو الحد الأدنى. إذا انكمشت مجموعة ISR دون هذه القيمة، سيتلقى المنتجون الذين يستخدمون acks=all خطأ NotEnoughReplicasException ويصبح القسم للقراءة فقط. هذا مقصود: الرفض أفضل من القبول مع دوام غير كافٍ.

# Monitor ISR health — any partition with ISR shorter than replication factor is degraded kafka-topics.sh --bootstrap-server kafka1:9092 --describe \ | awk '/Isr:/ {split($0, a, "Isr: "); split(a[2], isr, ","); \ split($0, r, "Replicas: "); split(r[2], rep, ","); \ if (length(isr) < length(rep)) print "DEGRADED:", $0}' # Better: use kafka-topics.sh built-in filter kafka-topics.sh --bootstrap-server kafka1:9092 \ --describe --under-replicated-partitions # Trigger preferred leader election after a broker restarts kafka-leader-election.sh --bootstrap-server kafka1:9092 \ --election-type preferred --all-topic-partitions
Kafka Cluster Architecture — Brokers, Partitions, Replication, and Controller Kafka Cluster: 3 Brokers, Topic with 3 Partitions (RF=3) Broker 1 ⭐ Controller P0 — LEADER offsets 0 → 9,182,445 P1 — Follower (ISR) fetching from Broker 2 P2 — Follower (ISR) fetching from Broker 3 Broker 2 P1 — LEADER offsets 0 → 8,741,200 P0 — Follower (ISR) fetching from Broker 1 P2 — Follower (ISR) fetching from Broker 3 Broker 3 P2 — LEADER offsets 0 → 7,993,880 P0 — Follower (ISR) fetching from Broker 1 P1 — Follower (ISR) fetching from Broker 2 Partition Leader ISR Follower ⭐ = Active Controller (Broker 1) Follower fetch (pull)
كتلة Kafka مؤلفة من 3 وسطاء مع موضوع واحد و3 أقسام وعامل نسخ 3. يحتوي كل وسيط على قائد قسم واحد ومتابعَين ضمن ISR. يعمل الوسيط 1 كمتحكم نشط.

المتحكم: عقل الكتلة

المتحكم (controller) هو وسيط واحد مُنتخب لإدارة بيانات التعريف الخاصة بالكتلة وتنسيق قيادة الأقسام. في المعمارية القديمة القائمة على ZooKeeper، كان المتحكم يُنتخب عبر عقدة ZooKeeper مؤقتة. في الوضع الحديث KRaft (Kafka 3.3+ إصدار GA، إلزامي في Kafka 4.0+)، أُزيل ZooKeeper كليًا. المتحكم الآن هو مجموعة إجماع قائمة على Raft: مجموعة صغيرة من الوسطاء (عادةً 3 أو 5) تُشكّل نصابًا. سجل البيانات الوصفية — كل إنشاء موضوع، وإعادة تعيين قسم، وتسجيل وسيط، وتغيير ISR — هو قسم Kafka بحد ذاته مُنسوخ عبر هذا النصاب. هذا يجعل تبديل المتحكم يستغرق أقل من ثانية.

مسؤوليات المتحكم:

  • انتخاب القائد: عند فشل قائد قسم، يختار المتحكم النسخة المتزامنة التالية للترقية.
  • تسجيل الوسطاء: عند بدء وسيط أو تعطله، يُسجّل/يُلغي التسجيل مع المتحكم.
  • تغييرات ISR: تُبلّغ الوسطاء المتحكمَ بأحداث انكماش/توسع ISR.
  • إدارة المواضيع والأقسام: جميع العمليات الإدارية تمر عبر المتحكم.
# KRaft mode: configure a 3-node controller quorum # On each controller node, server.properties: process.roles=controller node.id=1 controller.quorum.voters=1@kafka1:9093,2@kafka2:9093,3@kafka3:9093 # On broker-only nodes: process.roles=broker node.id=4 controller.quorum.voters=1@kafka1:9093,2@kafka2:9093,3@kafka3:9093 # Generate cluster UUID and format storage: kafka-storage.sh random-uuid kafka-storage.sh format -t abc123XYZ -c /etc/kafka/server.properties # Verify controller status: kafka-metadata-quorum.sh --bootstrap-controller kafka1:9093 describe --status

توزيع الأقسام والوعي بالرف

يوزع Kafka نسخ الأقسام عبر الوسطاء لموازنة حمل القيادة وتحمّل إخفاقات الوسطاء. في عمليات النشر متعددة المناطق أو الرفوف، تحتاج إلى الوعي بالرف (rack awareness) لضمان أن كل قسم له نسخ في نطاقات إخفاق مادية مختلفة.

اضبط broker.rack على كل وسيط ليطابق منطقة التوافر أو تسمية الرف المادي. عند إنشاء موضوع، سيضمن Kafka أن مجموعة النسخ لكل قسم تمتد عبر رفوف مختلفة، مما يعني أن فقدان منطقة توافرية كاملة لن يُسبب فقدان بيانات.

# Broker in us-east-1a: broker.rack=us-east-1a # Broker in us-east-1b: broker.rack=us-east-1b # Manual partition reassignment after adding brokers: kafka-reassign-partitions.sh --bootstrap-server kafka1:9092 \ --broker-list "1,2,3,4,5" \ --topics-to-move-json-file topics.json \ --generate kafka-reassign-partitions.sh --bootstrap-server kafka1:9092 \ --reassignment-json-file reassignment.json \ --throttle 50000000 \ --execute kafka-reassign-partitions.sh --bootstrap-server kafka1:9092 \ --reassignment-json-file reassignment.json \ --verify
إعادة تعيين الأقسام مكثفة لأعمال الإدخال/الإخراج ومرئية للمشغّل. نقل نسخة قسم يُنسخ كل بايت من بيانات المصدر إلى الوجهة. على موضوع بـ 500 غيغابايت من الاحتفاظ وبدون تحديد للسرعة، يمكن لإعادة التعيين إشباع القرص والشبكة وإؤثير على كمون المنتجين والمستهلكين. استخدم دائمًا --throttle بقيمة تساوي 20-30% من عرض النطاق بين الوسطاء.

أنماط الفشل الرئيسية التي يجب على كل مشغّل معرفتها

انتخاب القائد غير النظيف: إذا كان unclean.leader.election.enable=true وكانت جميع أعضاء ISR معطلة، يمكن لـ Kafka انتخاب نسخة غير متزامنة كقائد — وهذا يُسبب فقدان بيانات. اضبط هذا على false في جميع الكتل الإنتاجية دون استثناء.

عاصفة المتحكم: إعادة تشغيل الوسطاء بسرعة يمكن أن تُطلق انتخابات متحكم متكررة. الأعراض: يرى المنتجون والمستهلكون أخطاء NOT_LEADER_OR_FOLLOWER المتكررة. التخفيف: فرّق إعادات تشغيل الوسطاء بـ 30-60 ثانية على الأقل.

إخفاق دليل السجل: إذا فقد وسيط قرصًا، تُوقَف جميع الأقسام على ذلك الدليل. يدعم Kafka 1.1+ مفهوم JBOD: اضبط عدة مدخلات log.dirs تشير إلى أقراص منفصلة. إخفاق قرص لا يُوقف الوسيط بالكامل — فقط الأقسام الموجودة على ذلك القرص.

استخدم هذه المقاييس الأربعة كخط صحة أساسي لـ Kafka: (1) UnderReplicatedPartitions — يجب أن يكون 0 في الحالة الثابتة. (2) ActiveControllerCount — يجب أن يكون 1 بالضبط. (3) OfflinePartitionsCount — يجب أن يكون 0. (4) RequestHandlerAvgIdlePercent — يجب أن يبقى فوق 30%؛ انخفاضه تحت 20% يُشير إلى أن الوسيط مثقل بـ CPU. اربط هذه المقاييس بمنظومة تنبيه Prometheus الموجودة لديك.

الخلاصة

معمارية Kafka هي مجموعة من المقايضات المتعمدة: ملفات المقاطع بالإلحاق فقط للإنتاجية، والنسخ المتماثلة القائمة على الجلب عبر ISR للدوام دون عرقلة المنتجين، ومتحكم مركزي قائم على Raft في KRaft للبيانات الوصفية المتسقة، وتعيين أقسام يراعي الرف لعزل الأعطال. كل نمط فشل إنتاجي — الانتخابات غير النظيفة، وانكماش ISR، وعواصف المتحكم، وإخفاقات دليل السجل — ينبع مباشرة من هذه القرارات التصميمية. المشغّلون الذين يفهمون المعمارية يقرؤون الإخفاق من المقاييس والسجلات في ثوانٍ. في الدرس التالي ننتقل إلى جانب المنتج والمستهلك: كيف تُجمَّع الكتابات وتُضغط وتُودَّع، وكيف تُنسّق مجموعات المستهلكين تعيين الأقسام.