SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor

NATS أم RabbitMQ أم Kafka على خادم VPS واحد؟

قارن ضمانات التسليم وتكلفة الذاكرة والقرص وسلوك إعادة التشغيل، وتعلّم فحص التراكم بالأوامر، ومتى يكون Postgres الخيار الأفضل على VPS واحد.

الإجابة المختصرة لخادم واحد

تتعلق قائمة انتظار الرسائل على VPS واحد بضمانات التسليم، لا بالسرعة. في خادم واحد، نادراً ما يكون وسيط الرسائل عنق الزجاجة، لأن شفرتك التطبيقية وقاعدة بياناتك والقرص الوحيد لديك تصل إلى حدودها أولاً. اختر الأداة التي يمكنك قبول سلوكها عند الفشل، ثم قِس أداء الخادم الموجود لديك فعلياً.

فيما يلي أربعة خيارات، بالترتيب الذي ينبغي لمعظم القراء دراستها به.

  • استخدم قاعدة البيانات التي تشغّلها بالفعل. يُعد Postgres مع SELECT ... FOR UPDATE SKIP LOCKED قائمة انتظار مهام عملية، ولا يضيف عملية جديدة تحتاج إلى مراقبتها.
  • استخدم RabbitMQ عندما تكون كل رسالة وحدة عمل يجب تأكيد استلامها، وإعادة محاولتها عدداً محدوداً من المرات، ثم نقلها إلى مكان يستطيع الإنسان فحصه.
  • استخدم NATS عندما تكون الرسائل أحداثاً تتفاعل معها عدة أجزاء من نظامك. فعّل JetStream للأحداث التي يجب أن تبقى بعد إعادة التشغيل.
  • استخدم Kafka عندما تتحدث أداة لاحقة فقط ببروتوكول Kafka. في خادم واحد، يكاد هذا يكون السبب الوحيد المتبقي.

يشرح باقي هذا الدليل المنطق وراء هذه الخيارات: تكلفة كل خيار من الذاكرة والقرص على VPS صغير، وسلوكه عند إعادة تشغيل الخادم، والأمر الدقيق الذي يعرض لك التراكم قبل أن يلاحظه المستخدمون.

ما الذي يعنيه ضمان التسليم فعلياً

التسليم مرة واحدة كحد أقصى يعني أن الوسيط يسلّم الرسالة ثم يتوقف عن تتبعها. إذا لم يكن أي مستهلك متصلاً، أو تعطل مستهلك أثناء تنفيذ العمل، تضيع الرسالة ولا يُبلّغ أحد بذلك.

التسليم مرة واحدة على الأقل يعني أن المستهلك يرسل إقراراً (ack) بعد نجاح العمل. إلى أن يصل هذا الإقرار، يحتفظ الوسيط بالرسالة وسيعيد تسليمها. إعادة التسليم هي سبب وجوب جعل معالجاتك idempotent: يجب ألا تؤدي معالجة الرسالة نفسها مرتين إلى خصم المبلغ من البطاقة مرتين. لا يوفّر لك الوسيط التسليم مرة واحدة بالضبط من البداية إلى النهاية. يتحقق ذلك باستخدام مفتاح فريد في قاعدة بياناتك أنت.

إعادة التشغيل خاصية منفصلة. تحذف قائمة الانتظار الرسالة بعد الإقرار بها. أما السجل فيحتفظ بها طوال فترة الاحتفاظ، لذلك يمكن لمستهلك جديد البدء من البداية وقراءة السجل الكامل. Kafka وNATS JetStream سجلات. RabbitMQ قائمة انتظار. ويؤثر هذا الفرق في تصميم البنى أكثر مما يؤثر معدل النقل.

النقل إلى قائمة الرسائل الميتة هو ما يحدث للرسالة التي يستمر فشل معالجتها. من دونه، ستدخل الرسالة المسببة للمشكلة في حلقة لا نهائية، وستبدو الحلقة كأنها عامل مشغول لا كأنها عطل.

ابدأ بـPostgres ودَع وسيط الرسائل يثبت جدواه

تتكون معظم أعباء العمل ذات التطبيق الواحد من بضعة آلاف من المهام الخلفية يومياً. يكفي لذلك جدول واحد.

CREATE TABLE job (
  id        bigserial   PRIMARY KEY,
  payload   jsonb       NOT NULL,
  run_after timestamptz NOT NULL DEFAULT now(),
  attempts  int         NOT NULL DEFAULT 0
);
CREATE INDEX job_ready_idx ON job (run_after, id);

يطالب عامل بمهمة واحدة داخل معاملة.

BEGIN;
SELECT id, payload
  FROM job
 WHERE run_after <= now()
 ORDER BY id
   FOR UPDATE SKIP LOCKED
 LIMIT 1;
-- run the work, then remove the row
DELETE FROM job WHERE id = $1;
COMMIT;

FOR UPDATE SKIP LOCKED هي جوهر العملية. فهي تقفل الصف الذي تُعيده وتتجاوز أي صف قفلته معاملة أخرى مسبقاً، لذلك لا يطالب عاملان بالمهمة نفسها. إذا تعطل عامل، يلغي Postgres معاملته، ويُحرَّر القفل، ويصبح الصف مرئياً للعامل التالي. تحصل بذلك على تسليم مرة واحدة على الأقل، وإعادة المحاولة عبر زيادة attempts، وجدول للرسائل الميتة، وكل ذلك بالاعتماد على المتانة التي تدفع تكلفتها أصلاً. يصبح تراكم المهام استعلاماً واحداً: SELECT count(*) FROM job WHERE run_after <= now();

متى يتوقف هذا الأسلوب عن العمل؟ كل مطالبة وكل حذف عملية كتابة، لذلك يؤدي ارتفاع معدل المهام إلى ترك إصدارات صفوف ميتة، ويُعد جدول الطابور الحالة الكلاسيكية التي يتجاوز فيها التضخم قدرة autovacuum على اللحاق به. تجعل المهام الطويلة الوضع أسوأ، لأن المعاملة المفتوحة طوال مدة العمل تؤخر أيضاً أفق vacuum لقاعدة البيانات بأكملها. يضيف الاستطلاع تأخيراً، كما أن LISTEN مع NOTIFY يزيلان الاستطلاع، لكنهما لا يزيلان عمليات الكتابة. عندما يصبح جدول المهام أكثر جداولك نشاطاً، أو تحتاج خدمة ثانية إلى الأحداث نفسها، انقل العمل إلى خارج قاعدة البيانات. يتداخل هذا القرار مع طريقة نشر قاعدة البيانات نفسها، لذلك حدّد ما إذا كانت قاعدة البيانات تعمل في Docker أم على المضيف قبل إضافة وسيط رسائل بجانبها.

Redis هو الخيار الآخر الذي قد تكون تشغّله بالفعل. توفّر Redis Streams مجموعات مستهلكين مع XADD وXREADGROUP، وقائمة معلّقة لكل مجموعة، وXAUTOCLAIM لاستعادة العمل من مستهلك تعطل. وهو صغير وسريع. لكن الجانب الذي يجب مراعاته بصدق على خادم واحد هو الآتي: مع إعداد appendfsync everysec الشائع، قد يؤدي انقطاع الطاقة إلى فقدان نحو ثانية واحدة من عمليات الكتابة. هذا مناسب لإبطال ذاكرة التخزين المؤقت، لكنه غير مناسب للمدفوعات. إذا كان تطبيقك عملية واحدة مبنية حول SQLite في بيئة الإنتاج على VPS، فسيعمل نمط المطالبة والحذف نفسه، مع أن SQLite لا يملك بديلاً عن SKIP LOCKED، وأن كل عامل ينتظر دوره عند قفل الكتابة الوحيد.

توجيه الموضوعات في NATS core من دون تخزين

docker run -d --name nats \
  -p 4222:4222 -p 127.0.0.1:8222:8222 \
  nats:2.14 -m 8222

اعتباراً من August 2026، الإصدار الحالي من سلسلة الخادم هو 2.14. يفعّل -m 8222 منفذ مراقبة HTTP، وهو معطّل افتراضياً ولا يستخدم المصادقة، لذلك اربطه بـlocalhost كما سبق.

يعمل Core NATS بأسلوب التسليم مرة واحدة كحد أقصى، ولا يخزّن أي بيانات. يرسل الناشر رسالة إلى موضوع مثل orders.created، ويحصل كل مشترك يتطابق عامل التصفية لديه على نسخة منها. إذا لم يكن هناك أي مشترك، تُسقط الرسالة ولا يرى الناشر أي خطأ، لأن مهمة الناشر تنتهي عندما يقبل الخادم البايتات. تجعل مجموعة الطوابير، أي عدة مشتركين يتشاركون اسم مجموعة واحداً، الخادم يختار عضواً واحداً لكل رسالة. يوزّع ذلك العمل من دون تخزين طابور.

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

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

curl -s http://localhost:8222/varz | jq '.slow_consumers, .connections, .in_msgs, .out_msgs'

تعني قيمة slow_consumers التي تستمر في الارتفاع أن الرسائل تُسقط، لذلك أنشئ تنبيهاً عند ارتفاعها بدلاً من قراءتها مرة واحدة. يناسب Core NATS الرسائل التي تنتهي قيمتها سريعاً، مثل مقياس، أو تحديث للحالة، أو إبطال ذاكرة تخزين مؤقت سيحل الحدث التالي محله على أي حال.

NATS JetStream: التدفقات الدائمة وإعادة التشغيل ضمن العملية نفسها

JetStream ليس منتجاً ثانياً. بل هو نظام فرعي في الملف التنفيذي نفسه، ويُفعَّل عبر خيار واحد.

docker run -d --name nats \
  -p 4222:4222 -p 127.0.0.1:8222:8222 \
  -v nats-data:/data \
  nats:2.14 -js -sd /data -m 8222

يحدّد -sd /data دليل التخزين. إذا حذفته، يخزّن JetStream بياناته ضمن /tmp، وهو دليل لا يوفر أي استمرارية فعلية كما يوحي اسمه. أنشئ تدفقاً باستخدام CLI، المتوفر ضمن صورة nats-box.

docker run --rm -it --network host natsio/nats-box:latest \
  nats stream add ORDERS \
    --subjects 'orders.>' \
    --storage file \
    --retention limits \
    --max-age 72h \
    --max-bytes=1073741824 \
    --discard old \
    --defaults

لكل حدّ من هذه الحدود فائدة على خادم صغير. يحدّد --storage file ما يبقى بعد تعطل النظام، لأن تدفق الذاكرة لا يبقى. يقيّد --max-bytes=1073741824 التدفق إلى 1 GiB محسوبة بعدد البايتات المكتوبة، بينما يحذف --discard old أقدم الرسائل عند بلوغ الحد بدلاً من رفض عمليات الكتابة الجديدة. إذا تركت الحد دون ضبط، فقد يملأ ناشر واحد خارج عن السيطرة القرص، وعندها تتوقف قاعدة البيانات أيضاً لأنهما يشتركان في القرص نفسه.

يحتفظ المستهلك الدائم بموضعه الخاص داخل التدفق، ويحافظ عليه بعد إعادة التشغيل. اضبط --max-deliver على المستهلك حتى تتوقف إعادة تسليم الرسالة التي تفشل دائماً بعد عدد محدد. عندما تستنفد الرسالة عدد مرات التسليم، ينشر JetStream إشعاراً على $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>. والاشتراك في هذا الموضوع هو الطريقة التي تنشئ بها مسار الرسائل الميتة الذي يوفّره RabbitMQ كميزة جاهزة. أما هنا فعليك تنفيذ ذلك بنفسك.

لعرض التراكم، شغّل nats stream report لعرض أعداد الرسائل المخزنة، وnats consumer report ORDERS لعرض الإقرارات المعلّقة والرسائل غير المعالجة لكل مستهلك. العدد غير المعالج هو الذي يجب إنشاء تنبيه له. يمكنك عرض استهلاك القرص باستخدام du -sh مقابل دليل التخزين، ويستمر في الازدياد إلى أن يزيل حد الاحتفاظ الرسائل القديمة.

RabbitMQ: أكِّد كل رسالة، وانقل الرسائل الفاشلة إلى طابور منفصل

docker run -d --name rabbitmq \
  -p 5672:5672 -p 127.0.0.1:15672:15672 \
  -v rabbitmq-data:/var/lib/rabbitmq \
  rabbitmq:4-management

اعتباراً من August 2026، السلسلة الحالية هي 4.3. المنفذ 5672 هو AMQP (بروتوكول وضع الرسائل المتقدم في قوائم الانتظار)، والمنفذ 15672 هو واجهة الإدارة. أبقِ الواجهة على localhost، واصل إليها عبر نفق SSH.

أنشئ قوائم الانتظار مع ضبط الوسيطة x-queue-type على quorum؛ ولا تزال القيمة الافتراضية هي classic. تكون قوائم انتظار Quorum دائمة دائماً، وتكتب البيانات إلى القرص قبل تنفيذ أي إجراء آخر. لذلك، تحصل على سلوك واضح واحد على عقدة واحدة، بدلاً من مجموعة من خيارات التخزين الدائم والمؤقت. حدّد الهدف الخاص بالرسائل الميتة باستخدام policy.

docker exec rabbitmq rabbitmqctl set_policy DLX ".*" \
  '{"dead-letter-exchange":"my-dlx", "dead-letter-routing-key":"my-routing-key"}' \
  --apply-to queues --priority 7

تُنقل الرسالة إلى قائمة الرسائل الميتة لأربعة أسباب: يرفضها المستهلك باستخدام basic.reject أو basic.nack مع ضبط requeue على false، أو تنتهي مدة TTL (مدة البقاء) الخاصة بالرسالة، أو تتجاوز قائمة الانتظار حد الطول، أو تتجاوز حد تسليم قائمة انتظار Quorum. تكون قيمة هذا الحد 20 افتراضياً منذ RabbitMQ 4.0. لذلك، يعيد المعالج الذي يرمي استثناءً ويرسل nack المحاولة عشرين مرة، ثم يسلّم الرسالة إلى dead letter exchange بدلاً من الدخول في حلقة.

تُفاجئ الذاكرة بعض المستخدمين في RabbitMQ على VPS صغير. تبلغ قيمة high watermark الافتراضية 0.6 من ذاكرة RAM المتاحة. وعندما تتجاوز العقدة هذه القيمة، يحظر RabbitMQ كل اتصال ينشر رسائل. لا يتلقى تطبيقك خطأً. بل يتلقى عملية نشر لا تعود أبداً، وهذا يظهر في شفرة التطبيق كأنها معلّقة. يطبع سجل بدء التشغيل الرقم الذي حسبته العقدة:

Memory high watermark set to 1024 MiB (1073741824 bytes) of 8192 MiB (8589934592 bytes) total

يمنع إنذار القرص الناشرين بالطريقة نفسها عندما تنخفض المساحة الحرة عن 50 MB افتراضياً. وتضيف قوائم انتظار Quorum حساباتها الخاصة: تقدّر الوثائق الحاجة إلى 32 bytes على الأقل من البيانات الوصفية داخل الذاكرة لكل رسالة، أي نحو 1 MB لكل 30,000 رسالة، وتوصي بذاكرة RAM تعادل ثلاثة أضعاف حجم write-ahead log الفعلي على الأقل. تكون قيمة حد WAL الافتراضية 512 MiB، ولذلك تتطلب هذه التوصية وحدها 1.5 GB. على خادم بسعة 2 GB، خفّضها في rabbitmq.conf بدلاً من افتراض أن القيمة الافتراضية ستناسبه.

raft.wal_max_size_bytes = 64000000
vm_memory_high_watermark.relative = 0.5

يتكون التراكم من رقمين، ويبيّن الزوج نوع الفشل الذي حدث.

docker exec rabbitmq rabbitmqctl list_queues name messages messages_ready messages_unacknowledged

تنتظر messages_ready مستهلكاً. وقد سُلّمت messages_unacknowledged ولم يجرِ تأكيدها. يعني ارتفاع عدد الرسائل غير المؤكدة مع ثبات عدد الرسائل الجاهزة أن العمال استلموا المهام وتوقفوا عن إكمالها. وهذا خطأ مختلف عن قائمة انتظار متأخرة فحسب.

Kafka على خادم واحد، ومتى يتوقف ذلك عن كونه منطقياً

KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
bin/kafka-storage.sh format --standalone -t $KAFKA_CLUSTER_ID -c config/server.properties
bin/kafka-server-start.sh config/server.properties

هذا هو دليل البدء السريع لـKafka 4.3.1، وهو الإصدار الحالي حتى August 2026، ويعمل في وضع KRaft ‏(Kafka Raft، وحدة التحكم المضمّنة التي حلّت محل ZooKeeper في Kafka 4.0). والمكافئ باستخدام الحاوية هو apache/kafka:4.3.1.

يعيّن برنامج البدء export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" عندما لا تعيّنه بنفسك، لذلك يحجز الـbroker ذاكرة Java heap بحجم 1 GB قبل أن يخزّن رسالة واحدة، ويتوقع وجود RAM حرة إضافية لذاكرة page cache التي يقرأ منها. على VPS بسعة 2 GB، سيتنافس تطبيقك مع JVM على الذاكرة المتبقية.

الاحتفاظ هو المفاجأة التالية. القيمة الافتراضية لـlog.retention.hours هي 168، أي سبعة أيام، والقيمة الافتراضية لـlog.retention.bytes هي -1، ما يعني عدم وجود حد للحجم إطلاقاً. يحتفظ Kafka بالرسائل طوال فترة الاحتفاظ، سواء قرأها كل consumer أم لم يقرأها. هذه هي الميزة التي جئت من أجلها، لكنها على قرص صغير واحد تصبح أيضاً سبب الفشل. لذلك عيّن حداً بالحجم بالبايت لكل topic قبل أن تكتشف ذلك.

والآن الجزء الصريح. يعني وجود broker واحد أن replication factor يساوي 1، ولذلك تُترجم acks=all إلى عملية fsync واحدة على قرص واحد. تحصل على متانة جهاز واحد، مع كلفة تشغيل JVM broker ووحدة تحكم. تمنحك partitions التوازي عبر brokers غير موجودة لديك. وتبقى replication وrack awareness وبقية ميزات الأسطول غير مستخدمة. يوفّر لك JetStream إعادة التشغيل الدائمة نفسها على الخادم نفسه، وبجزء من الذاكرة. لا يزال هناك سببان يبرران استخدام Kafka هنا: أن تتحدث أداة لاحقة فقط ببروتوكول Kafka، مثل التقاط تغييرات البيانات باستخدام Debezium أو أداة تحميل تحليلات، أو أن تعيد إنشاء بنية إنتاجية على نطاق مصغر. التخطيط للتوسع إلى cluster يعني التخطيط لشراء مزيد من الأجهزة. وحتى ذلك الحين، تظل المفاضلة نفسها كما في تشغيل k3s على عقدة واحدة، إذ تدفع تعقيد cluster مقابل موثوقية عقدة واحدة.

يُسمّى تراكم الرسائل في Kafka تأخر المستهلك.

bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group

اقرأ العمود LAG، وهو ناتج LOG-END-OFFSET ناقص CURRENT-OFFSET لكل partition. يشير ارتفاع التأخر في partition واحدة بينما تبقى partitions الأخرى ثابتة إلى عدم توازن في key، لأن جميع الرسائل ذات key نفسه تصل إلى partition نفسها، ويتعامل معها consumer واحد بمفرده.

ما الذي يحدث عند إعادة تشغيل الخادم

يفقد Core NATS كل ما هو قيد المعالجة ويعود للعمل فوراً، لأنه لا يوجد شيء يحتاج إلى الاسترداد. يعيد JetStream تحميل التدفقات ومواضع المستهلكين من دليل التخزين، لذلك يستأنف المستهلكون العمل من الإزاحة التي كانوا عندها. يسترد RabbitMQ قوائم انتظار quorum من القرص، بينما تختفي قوائم الانتظار المؤقتة الكلاسيكية وأي رسالة نُشرت من دون وضع التسليم الدائم. يعيد Kafka تشغيل مقاطع السجل عند بدء التشغيل. وبعد إيقاف تشغيل غير سليم، قد يستغرق فحص الاسترداد هذا دقائق على قرص صغير قبل أن يقبل الوسيط اتصالاً.

هناك أمران يستحقان الضبط مرة واحدة. امنح الحاوية سياسة إعادة تشغيل (restart: unless-stopped)، أو فعّل وحدة systemd، حتى يعود الوسيط بعد إعادة تشغيل النظام بسبب ترقية kernel من دون تدخل منك. ثم عالج ترتيب التشغيل. إذا أصبح الوسيط جاهزاً بعد تطبيقك بعشرين ثانية، فسيرفض الاتصالات الأولى، وقد تنهي بعض مكتبات العملاء عملها بدلاً من إعادة المحاولة. اربط بدء التطبيق بجاهزية الوسيط باستخدام فحوصات صحة Compose التي تؤخر تشغيل الخدمة التابعة حتى يصبح الوسيط جاهزاً.

التكلفة على VPS الخاص بك، بالقياس لا بالاقتباس

أرقام معدل النقل المنشورة مقاسة على عتاد لا تملكه، وعادةً ما يكون خادماً متعدد الأنوية مزوداً بوسيط تخزين NVMe محلي. تعامل معها باعتبارها حداً أعلى، وقِس الأداء على خادمك.

docker stats --no-stream
free -m
sudo du -sh /var/lib/docker/volumes/*/_data

شغّل هذه الاختبارات عندما يكون الوسيط خاملاً، ثم شغّلها مرة أخرى تحت حركة المرور الفعلية لديك. الفرق بين النتيجتين هو الرقم الذي يحدد ما إذا كان الوسيط يستطيع العمل بجانب تطبيقك. للحصول على حد تقريبي لمعدل النقل، استخدم مولّد الحمل الخاص بكل مشروع بدلاً من الاعتماد على منشور في مدونة شخص آخر: nats bench pub test --msgs 100000 --clients 2 لـNATS، وbin/kafka-producer-perf-test.sh لـKafka، وPerfTest لـRabbitMQ. يؤدي تشغيل مولّد الحمل على VPS نفسه إلى قياس الوسيط ومولّد الحمل معاً. وهذا مقبول ما دمت توضّح ذلك عند الإبلاغ عن الرقم.

ينطبق حد أعلى واحد على جميع هذه الخيارات. ينتظر كل خيار دائم هنا تنفيذ fsync، لذلك يحدد القرص الحد الأقصى على VPS يستخدم تخزيناً متصلاً عبر الشبكة، ولن يؤدي تبديل الوسطاء إلى تغيير هذا الحد.

ثلاثة أحمال عمل وقائمة انتظار الرسائل التي يحتاج إليها كل منها

  1. مهام خلفية لتطبيق ويب واحد، مثل إرسال البريد الإلكتروني، أو تغيير حجم الصور، أو تسليم webhooks. ابدأ باستخدام Postgres وSKIP LOCKED. انتقل إلى RabbitMQ مع قوائم انتظار quorum عندما تحتاج إلى إقرارات لكل رسالة، وحدّ للتسليم، وقائمة dead letter queue يمكنك فحصها من دون كتابة هذه المنطق بنفسك، أو عندما يصبح جدول المهام أكثر جداول قاعدة البيانات انشغالاً.
  2. أحداث تتفاعل معها عدة خدمات داخلية، حيث تُستبدل الرسالة المفقودة سريعاً برسالة أحدث. استخدم Core NATS، مع subjects بوصفها مخطط التوجيه، وqueue groups عندما تحتاج إلى مشاركة العمل. أضف تدفق JetStream إلى مجموعة subjects المحدودة التي يجب أن تبقى بعد إعادة التشغيل، واترك بقية الأحداث في الذاكرة.
  3. سجل أحداث يقرأ منه المستهلكون بدءاً من البداية، لاستخدامه كسجل تدقيق، أو لإعادة بناء read model، أو لتغذية التحليلات لاحقاً. استخدم JetStream مع تخزين الملفات وحدّ صريح بالبايت. اختر Kafka فقط عندما تتطلب أداة لاحقة بروتوكول Kafka، وتقبّل ذاكرة JVM heap ثمناً لهذا التوافق.

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

FAQ

هل يمكنني تشغيل Kafka على VPS بسعة 2 GB؟

سيبدأ التشغيل، لكن الموارد ستكون محدودة. يضبط bin/kafka-server-start.sh قيمة KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" عندما لا تغيّرها، لذلك تحجز JVM مقدار 1 GB قبل تخزين أي رسالة، ويعتمد Kafka على الذاكرة الحرة الإضافية لذاكرة page cache. إذا أضفت تطبيقك وقاعدة بياناتك على الخادم نفسه، فستبدأ باستخدام swap. وستستخدم أيضاً replication factor بقيمة 1، ما يعني أن acks=all يساوي عملية fsync واحدة على قرص واحد. بذلك تتحمل تكلفة تشغيل Kafka دون الاستفادة من نموذج المتانة الخاص به. يوفّر NATS JetStream إمكانية إعادة تشغيل الرسائل بشكل دائم على العتاد نفسه وباستهلاك ذاكرة أقل بكثير.

هل أحتاج إلى message queue إذا كنت أشغّل Postgres بالفعل؟

غالباً لا. يوفّر جدول job مع SELECT ... FOR UPDATE SKIP LOCKED داخل معاملة واحدة تسليماً مرة واحدة على الأقل، وعمالاً متزامنين بأمان، وإعادة المحاولة، وجدولاً للرسائل الفاشلة، من دون خدمة إضافية لمراقبتها، ومع نسخ احتياطية تنشئها بالفعل. تكون مؤشرات الانتقال إلى خدمة مستقلة محددة: يصبح جدول الطابور أثقل حمل كتابة لديك ويتأخر autovacuum، أو تحتفظ المهام طويلة التشغيل بالمعاملات مفتوحة وتمنع vacuum لقاعدة البيانات بأكملها، أو تحتاج خدمة ثانية إلى استهلاك الأحداث نفسها بشكل مستقل.

هل أستخدم NATS JetStream أم RabbitMQ للمهام التي تعمل في الخلفية؟

استخدم RabbitMQ إذا أردت إقراراً لكل رسالة، وحداً للتسليم، وتوجيهاً مدمجاً للرسائل الفاشلة. تكون Quorum queues دائمة دائماً، ويكون حد التسليم الافتراضي 20 بدءاً من RabbitMQ 4.0، كما ترسل policy الرسائل التي استنفدت محاولاتها إلى dead letter exchange يمكنك تفريغه وفحصه. استخدم JetStream إذا كانت الأحداث نفسها تحتاج أيضاً إلى إعادة تشغيلها لاحقاً بواسطة مستهلكين آخرين، لأن stream يحتفظ بالرسائل بعد الإقرار، بينما لا يفعل queue ذلك. في JetStream، تضبط --max-deliver وتبني مسار dead letter بنفسك اعتماداً على $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.> advisory.

كيف أعرف مدى تأخر المستهلكين؟

يوفّر كل broker أمراً لذلك. في RabbitMQ، يفصل rabbitmqctl list_queues name messages messages_ready messages_unacknowledged بين العمل الذي ينتظر مستهلكاً والعمل الذي سُلّم ولم يحصل على ack. في JetStream، يعرض nats consumer report <stream> الرسائل غير المعالجة والإقرارات المعلّقة لكل مستهلك. في Kafka، يطبع kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group <group> عمود LAG لكل partition. لا يحتوي Core NATS على backlog يمكن قراءته لأنه لا يخزّن شيئاً. لذلك راقب عدّاد slow_consumers في http://localhost:8222/varz بدلاً من ذلك. فهو يحسب الاتصالات التي أغلقها الخادم بسبب تأخرها، وهذا يعني فقدان الرسائل.

#nats#rabbitmq#kafka#message-queue#architecture