Skip to content

فصل ۴ — صف پیام توزیع‌شده (Distributed Message Queue)

خلاصهٔ تحلیلی — این متن چکیدهٔ مفاهیم فصل به زبان فارسی است، نه ترجمهٔ واژه‌به‌واژه.

صف پیام چهار فایدهٔ اصلی دارد: جداسازی اجزا، مقیاس‌پذیری مستقل producer و consumer، دسترس‌پذیری بهتر (خرابی یک جزء بقیه را متوقف نمی‌کند) و کارایی از راه ارتباط ناهمگام. نکتهٔ مرزبندی: Kafka و Pulsar در واقع «پلتفرم استریم رویداد» هستند نه صف پیام سنتی (مثل RabbitMQ)، اما مرز این دو در حال محو شدن است. طراحی این فصل صف پیامی با قابلیت‌های استریم است: نگهداری دادهٔ طولانی، مصرف تکراری پیام و حفظ ترتیب — اگر مصاحبه روی صف سنتی متمرکز باشد، طراحی بسیار ساده‌تر می‌شود.

گام ۱ — نیازمندی‌ها

  • پیام‌های متنی در حد کیلوبایت؛ مصرف تکراری و مصرف به‌ترتیبِ تولید؛ نگهداری داده دو هفته؛ semantics تحویل قابل پیکربندی (at-most-once / at-least-once / exactly-once).
  • غیرکارکردی: throughput بالا یا تأخیر کم به‌تناسب کاربرد، مقیاس‌پذیری، و پایداری داده روی دیسک با replication.

گام ۲ — طراحی سطح بالا

مدل‌های پیام‌رسانی

  • Point-to-point: هر پیام فقط توسط یک consumer مصرف می‌شود؛ در صف‌های سنتی پیام پس از ack حذف می‌شود.
  • Publish-subscribe: پیام به یک topic می‌رود و همهٔ گروه‌های مشترک آن را می‌گیرند.

طراحی ما pub/sub را با topic و point-to-point را با consumer group شبیه‌سازی می‌کند.

Topic، پارتیشن و Broker

topic به پارتیشن‌ها تقسیم می‌شود که روی سرورهای broker پخش‌اند. هر پارتیشن یک صف FIFO است و جایگاه پیام در آن offset نام دارد. پیامِ دارای key با هش به پارتیشن ثابتی می‌رود (ترتیب برای همان key حفظ می‌شود)؛ بی‌key تصادفی توزیع می‌شود.

Consumer group: هر گروه offset مخصوص خودش را دارد و می‌تواند چند topic را دنبال کند. مصرف موازی خوب است، اما ترتیب داخل پارتیشن را می‌شکند؛ قید کلیدی: در هر گروه، هر پارتیشن فقط به یک consumer داده می‌شود. اگر consumer های گروه بیش از پارتیشن‌ها باشند، بعضی بیکار می‌مانند. با گذاشتن همهٔ consumer ها در یک گروه، همان point-to-point به دست می‌آید.

اجزای معماری

  • Broker: نگهدارندهٔ پارتیشن‌ها (data storage).
  • State storage: نگاشت پارتیشن-consumer و آخرین offset مصرف‌شدهٔ هر گروه.
  • Metadata storage: پیکربندی topic ها (تعداد پارتیشن، retention، طرح توزیع replica).
  • Coordination service (ZooKeeper/etcd): کشف سرویس (کدام broker زنده است) و انتخاب رهبر — یکی از broker ها به‌عنوان controller فعال پارتیشن‌ها را تخصیص می‌دهد.

گام ۳ — بررسی عمیق

سه تصمیم بنیادی برای کارایی

  1. ساختار روی-دیسک ترتیبی: دیسک چرخشی فقط در دسترسی تصادفی کند است؛ با دسترسی ترتیبی و RAID به صدها MB/s می‌رسد و کش تهاجمی دیسک در OS هم به کمک می‌آید.
  2. قرارداد دادهٔ بدون تغییر: پیام از producer تا broker تا consumer بدون mutation عبور می‌کند تا کپی‌های پرهزینه حذف شوند.
  3. Batching در همه‌جا: producer، broker و consumer همگی دسته‌ای کار می‌کنند؛ I/O کوچک دشمن throughput است.

ذخیره‌سازی داده

دیتابیس (رابطه‌ای یا NoSQL) برای الگوی «write-heavy + read-heavy + فقط append ترتیبی» مناسب نیست. انتخاب درست WAL (write-ahead log) است: فایل فقط-افزودنی با offset صعودی، تقسیم‌شده به سگمنت‌ها — پیام جدید فقط به سگمنت فعال می‌رود؛ سگمنت‌های غیرفعال فقط خوانده و در سررسید retention حذف می‌شوند.

ساختار پیام

key، value، topic، partition، offset، timestamp، size، CRC — پیام با سه‌تایی (topic، partition، offset) یافت می‌شود. key با «kv-store key» فرق دارد: یکتا و حتی اجباری نیست و حامل اطلاعات کسب‌وکار است.

جریان producer

روتینگ به‌صورت لایهٔ جدا hop و تأخیر اضافه می‌کند و batching را نادیده می‌گیرد؛ راه بهتر این است که مسیریابی + بافر داخل کتابخانهٔ کلاینت producer باشد: تأخیر کمتر، منطق انتخاب پارتیشن دلخواه، و ارسال دسته‌های بزرگ. اندازهٔ batch مبادلهٔ کلاسیک throughput-تأخیر است.

جریان consumer — push یا pull؟

push تأخیر کم دارد اما consumer کند را غرق می‌کند و نرخ را broker تعیین می‌کند. pull انتخاب رایج است: consumer نرخ مصرف را کنترل می‌کند، برای batch تهاجمی مناسب است و مشکل «poll خالی» با long polling حل می‌شود.

Rebalancing گروه consumer

یک coordinator با heartbeat اعضای گروه را رصد می‌کند. با پیوستن عضو جدید، خروج عضو یا crash (قطع heartbeat)، coordinator از همه می‌خواهد دوباره join شوند، یکی را رهبر گروه انتخاب می‌کند، رهبر طرح تقسیم پارتیشن‌ها را می‌سازد و coordinator آن را به اعضا ابلاغ می‌کند.

State و Metadata

state مصرف (offset ها): خواندن/نوشتن پرتکرار اما کم‌حجم، تصادفی و نیازمند سازگاری ← KV store مثل ZooKeeper (کافکا بعداً offset ها را به خود broker ها منتقل کرد). metadata کم‌تغییر و کم‌حجم با نیاز سازگاری بالا ← ZooKeeper. با سپردن این دو به ZooKeeper، خود broker فقط دادهٔ پیام را نگه می‌دارد.

Replication و ISR

هر پارتیشن مثلاً ۳ replica روی broker های مختلف دارد؛ producer فقط به رهبر می‌نویسد و follower ها از او pull می‌کنند. طرح توزیع replica ها را controller می‌سازد و در metadata ذخیره می‌کند.

ISR (in-sync replicas): replica هایی که عقب‌ماندگی‌شان از رهبر کمتر از آستانهٔ پیکربندی است. offset «committed» یعنی همهٔ ISR ها آن را دارند. تنظیم ack مبادلهٔ دوام-کارایی را در دست کاربر می‌گذارد:

ackمعنیکاربرد
allهمهٔ ISR ها دریافت کنندقوی‌ترین دوام، کندترین
1فقط رهبر ذخیره کندتأخیر کم؛ خطر از دست رفتن هنگام سقوط فوری رهبر
0بدون انتظار و بدون retryکمترین تأخیر؛ مناسب متریک/لاگ

مصرف از رهبر انجام می‌شود (سادگی طراحی؛ اتصال‌ها به لطف قید «یک پارتیشن-یک consumer» محدودند)؛ در سناریوی چند-دیتاسنتری خواندن از نزدیک‌ترین ISR قابل بحث است.

مقیاس‌پذیری اجزا

  • Producer/Consumer: افزودن instance؛ گروه‌ها از هم ایزوله‌اند و rebalancing خرابی‌ها را جذب می‌کند.
  • Broker: با سقوط یک broker، controller طرح توزیع تازه می‌سازد و replica های جدید از رهبران catch-up می‌کنند. نکات: replica های یک پارتیشن نباید هم‌نود باشند؛ برای افزودن broker، controller موقتاً بیش از حد مجاز replica می‌سازد و پس از هم‌گام شدنِ نودِ جدید، زائد را حذف می‌کند (بدون خطر از دست رفتن داده).
  • پارتیشن: افزایش ساده است (دادهٔ قدیمی سر جایش می‌ماند و پیام‌های جدید بین همه پخش می‌شوند). کاهش پیچیده‌تر است: پارتیشن decommission شده تا پایان retention فقط خوانده می‌شود و بعد حذف و rebalance انجام می‌شود.

Semantics تحویل داده

  • At-most-once: ack=0 و بدون retry؛ commit آفست قبل از پردازش ← ممکن است پیام گم شود اما تکرار نمی‌شود (متریک‌ها).
  • At-least-once: ack=1/all با retry؛ commit آفست بعد از پردازش موفق ← گم نمی‌شود اما ممکن است تکرار شود؛ با کلید یکتا در پیام می‌توان سمت مصرف de-dup کرد.
  • Exactly-once: دشوارترین و پرهزینه‌ترین؛ برای کاربردهای مالی که تکرار پذیرفتنی نیست و پایین‌دست idempotent نیست.

قابلیت‌های پیشرفته

  • فیلتر پیام: ساختن topic جداگانه برای هر مصرف‌کننده اتلاف منابع و coupling است؛ فیلتر سمت consumer ترافیک هدر می‌دهد. راه‌حل: فیلتر سمت broker اما فقط بر اساس تگ‌های metadata پیام (بدون باز کردن payload که ممکن است رمز شده باشد).
  • پیام تأخیری/زمان‌بندی‌شده: پیام ابتدا در storage موقت broker می‌ماند و در سررسید به topic تحویل می‌شود؛ زمان‌سنجی با سطوح تأخیر از پیش تعریف‌شده (سبک RocketMQ) یا hierarchical time wheel. کاربرد نمونه: بستن سفارش پرداخت‌نشده پس از ۳۰ دقیقه.

نکات کلیدی فصل

  • append-only log + دسترسی ترتیبی دیسک + batching = دستور پخت throughput بالا با سخت‌افزار ارزان.
  • قید «هر پارتیشن حداکثر یک consumer در هر گروه» هم ترتیب را حفظ می‌کند و هم مدل point-to-point را می‌سازد.
  • ack قابل پیکربندی و ISR، مبادلهٔ دوام-تأخیر را به تصمیم صریح کاربر تبدیل می‌کنند.
  • تفکیک سه نوع storage (داده، state، metadata) بر اساس الگوی دسترسی، هر کدام را با ابزار مناسب خودش حل می‌کند.

یادداشت‌های مترجم

  • اصطلاحات broker، partition، offset، WAL، ISR و ack لاتین حفظ شده‌اند.