ویرگول
ورودثبت نام
حسن سعادت
حسن سعادتیه مهندس نرم افزار کنجکاو، عاشق حل مسئله و ساده کردن مفاهیم پیچیده
حسن سعادت
حسن سعادت
خواندن ۹ دقیقه·۳ ماه پیش

مفهوم Message Broker به زبان آدم!

قبل از اینکه مفهوم مسیج بروکر رو بخونیم باید روش های ارتباط بین سرویس ها رو بدونیم.

1- Sync

ارتباط مستقیم سرویس ها از طریق پروتکل ‌HTTP یا gRPC.

سرویس مبدا request میزنه به سرویس مقصد و منتظر پاسخ میمونه.

چالش ها:

  • این زمان انتظار میتونه خیلی بالا باشه و تایم اوت بشه!

  • نرخ ارسال درخواست میتونه از نرخ پردازش اون بیشتر باشه و سرویس مقصد دهنش سرویس بشه!

  • اگه یه سرویس بخواد یه پیام رو به چنتا سرویس برسونه چیکار کنه؟

  • اگه تعداد سرویس ها بالا باشه و بخوایم چنتا زوج از این سرویسا رو به هم وصل کنیم؟ خر تو خر میشه

  • سرویس ها به هم وابسته میشن! coupling داریم

  • اگه یه مسیج بخوایم بره اول سرویس ۱ بعد اون پردازش کنه بده سرویس ۲ بعد اون بده سرویس ۳ همچین پایپ لاینی رو چطوری پیاده سازی کنیم؟ حالا پیاده کردیم چجوری دیباگش کنیم!!

2- Async

ارتباط غیر مستقیم سرویس ها

سرویس ها پیام هاشونو میدن به یه کسی به اسم مسیج بروکر / Message Broker و دیگه فراموشش میکنن حالا هر کی میخواد از پیامه استفاده کنه به خودش مربوطه!

تعریف Message Broker

ابزاریه که پیام ها رو بین سرویس ها منتقل می کنه.

  • به تولید کننده پیام Producer / Publisher میگیم

  • به مصرف کننده پیام Consumer / Subscriber میگیم

خب این چه کمکی میکنه؟ باعث میشه اینا رو بتونیم داشته باشیم:

1- Decoupling

وابستگی سرویس ها به هم کم میشه. نیاز نیست چیز زیادی از هم دیگه بدونن

2- Scalability

وقتی لود سیستم بالا میره میتونیم consumer ها به صورت horizontally اسکیل کنیم و تعدادشونو ببریم بالا.

  • اگه سرویس ها مستقیم به هم وصل بودن بالا رفتن لود producer مساوی بود با بالا رفتن لود consumer و از یه جا به بعد حتی با افزایش ریسورس consumer نمیتونست پابه پای producer پیام ها رو پردازش کنه و سیستم رو کند میکرد.

  • اگه consumer کرش میکرد producer پیام ها رو کجا میفرستاد؟ باید وایمیساد تا اون برگرده به سیستم

  • الزاما همیشه لود سیستم بالا نیست که اسکیل کنیم ممکنه تایم های خاصی از روز لود بره بالا اونوقت نمیتونستیم ریسورس ها رو بهینه تخصیص بدیم به consumer (یا همیشه ریسورس کم داشت یا زیاد)

3- Resilience / Reliability

مسیج بروکر یه سری مکانیزم / قابلیت فراهم می کنه که پیام ها حفظ بشن و به دست consumer برسن مثل:

  • persistence

  • retry & acknowledgement

  • fault tolerance / replication

  • load handling / backpressure

در این صورت حتی اگه مثلا consumer کرش کنه مسیج ها حفظ میشن و سیستم پایدار میمونه و بقیه چیزا.

3- Monitoring

مسیج بروکر میتونه ابزارها و متریک های در اختیارمون قرار بده که باهاش سیستم رو مانیتور کنیم و بگردیم دنبال باگ ها و مشکلات پرفورمنسیش.

انواع انتقال پیام Async

سه مدل انتقال پیام آسنکرون داریم. این دسته بندی بر اساس Distribution Pattern یا الگوی توزیع پیام ها هستش.

1- Point to Point (one-to-one)

توی این مدل هر پیام دقیقا فقط به یک consumer میرسه و یکبار پردازش میشه به خاطر همین میگن یک به یک.

یک به یک بودن به این معنا نیست که دقیقا یه producer و یه consumer وجود داره! میتونیم چنتا producer داشته باشیم و یه consumer و یا بر عکس و یا چنتا producer و چنتا consumer! صرفا پیام ها به یک consumer تحویل داده و پردازش میشه.

این مدل با ساختار داده ی Queue/صف پیاده سازی میشه (پیش فرض FIFO) اما فقط یه صف معمولی نیست! بلکه مسیج بروکر امکانات دیگه ای مثل load balancing - persistence - retry - acknowledgement هم در اختیارمون قرار میده.

این صف میتونه custom بشه که Priority Queue یا صف با اولویت هم ساپورت کنه.

مثال ملموسش میشه Django + Celery + Redis/RabbitMQ که تیکه هایی از کد بک اند میشن producer ها و ورکرهای Celery میشن consumer های ما.

  • ردیس این مدل رو با Redis List و کامندهای RPUSH/LPUSH و BRPOP/BLPOP هندل میکنه

  • ربیت خودش اصلا Queue-oriented عه

  • کافکا این رو با مفهوم consumer group شبیه سازی می کنه وقتی که فقط یه کانسیومر توی گروپ باشه (چرا میگیم شبیه سازی چون در صف واقعی پیام ها پس از مصرف حذف میشن ولی توی کافکا حذف نمیشه صرفا offset کانسیومر به جلو میره)

2- Pub/Sub (one-to-many)

پابلیشر پیام ها رو به یک Topic(در کافکا) / Exchange(در ربیت) میفرسته و همه کانسیومرهایی که اون تاپیک رو Subscribe کردن میتونن پیام ها رو بخونن. publisher و subscriber کاملا مستقل از هم هستند و loosely coupling داریم.

چرا یک به چند شد؟ یک پیام به چند مصرف کننده میرسه و میتونه توسط همشون پردازش بشه.

کاربرد: push notification

  • ردیس این مدل رو با استفاده از Redis channel پیاده می کنه

  • ربیت با استفاده از نوع خاصی از Exchange به اسم fanout

  • کافکا میتونه با تعریف چنتا consumer group که هر کدوم یه کانسیومر داشته باشن این رو شبیه سازی کنه (بازم شبیه سازی چون پیام ها واقعا حذف نمیشن)

3- Streaming (one-to-many-with-replay)

اینم مثل قبلی یه producer توی یه تاپیک مینویسه و کانسیومرهایی که میخوان میان متصل میشن و میخونن ازش اما تفاوتش اینه که پیام ها توی یه ساختار log/stream ذخیره میشن و بعدا قابلیت replay دارن. توی مدل pub/sub اگه یه کانسیومر بعدا وصل بشه به پیامای قبلی دسترسی نداره صرفا پیام ها رو از موقعی که اومده میبینه ولی توی استریم میتونه پیامای قبلی رو هم بخونه. همچنین توی ساختار pub/sub معمولا پیام ها volatile هستن ینی پس از مصرف کامل حذف میشن(تا زمان کوتاهی فقط نگهداری میشن) اما در استریم روی دیسک persist میشن (همیشه یا به اندازه retention time تعریف شده). به علاوه ترتیب پیام ها در استریم حفظ میشه اما الزاما در pub/sub حفظ نمیشه.

کاربرد: event sourcing - log processing

  • ردیس‌ با Redis stream این مدل رو پیاده سازی میکنه

  • ربیت با صف هایی که قابلیت persist + ack دارن میتونه زورشو بزنه ولی بازم replay نداره و نمیتونه

  • کافکا اصلا کارش همیشه

مسئله Delivery Semantics / Guarantees یا ضمانت تحویل

وقتی یه پیام بین producer و consumer جابجا میشه سه مدل مختلف ضمانت تحویل رسیدن پیام داریم:

1- At most once

یک پیام حداکثر یکبار به مصرف کننده تحویل داده میشه به این معنی که ممکنه اصلا به مقصد نرسه(گم بشه)!

مزیت: latency پایین و throughput بالا

عیب:‌ امکان گم شدن پیامها

کاربرد: ارسال metric های مانیتورینگ که حجم کمی data loss قابل پذیرشه مثلا ایونت های telemetry

2- At least once

یک پیام حداقل یکبار به مقصد میرسه به این معنی که ممکنه چندبار تکراری هم برسه! ولی هیچ پیامی گم نباید بشه.

مزیت: هیچ پیامی گم نمیشه

عیب: امکان وجود پیامهای تکراری

کاربرد: سیستم های پرداخت (به شرطی که عملیات پرداخت idempotent باشه و تکراری ها رو ignore کنه)

3. Exactly once

یک پیام دقیقا یکبار پردازش بشه نه گم بشه و نه تکراری داشته باشیم. سخت ترین مدل از نظر پیاده سازی

مزیت: مطمئن ترین مدل تحویل

عیب: پیاده سازی پیچیده - پرفورمنس پایین

کاربرد:‌ سیستم های مالی حساس بانکی یا trading

تکنیک های Reliability

مسیج بروکرها برای اینکه بهشون اطمینان کنیم که پیامهای سرویس ها رو بسپریم دستشون یه سری تکنیک پیاده سازی کردن که از دیتاها محافظت کنن و انتقالشون رو تضمین کنن، یه سری تکنیک هم در سطح اپلیکیشن هستش که باید خودمون پیاده سازی کنیم.

به اولی میگیم Broker level reliability techniques ینی تکنیک های سطح بروکر و به دومی میگیم application level reliability techniques.

Broker Level Reliability Techniques

1. Persistence / Durability

اولین چیزی که از مسیج بروکرا میخوایم اینه که وقتی کرش میکنن یا ریستارت میشن پیام های ما نابود نشه!

(البته این بسته به میزان حیاتی بودن دیتا داره اگه خیلی حیاتی نباشه که بذار نابود شه)

به این منظور بروکرا میان دیتا رو روی دیسک ذخیره میکنن.

  • ردیس به صورت دیفالت persist نمیکنه و اختیاریه. میتونه با استفاده از مکانیزم های RDB snapshotting یا AOF (append only file) دیتا رو روی دیسک persist کنه.

  • ربیت هم با یه سری فلگ میتونی تنظیمش کنی که persist کنه. باید message و queue و exchange رو به عنوان durable تعریف کنی.

  • کافکا اصلا خوراکش همینه به صورت پیش فرض برات persist میکنه. البته بهتره retention policy ست کنی تا دیسک با دیتای قدیمی / بدردنخور هدر نره یا پر نشه.

2- Acknowledgement (Ack / Nack)

کانسیومر اعلام میکنه که تونست پیام رو پردازش کنه (ack) یا نتونست (nack در ربیت).

  • ردیس در حالت pub/sub روی مود fire & forget هست و ack نداره. در حالت stream داره

  • ربیت دوتا مود auto ack و manual ack داره که اولی به محض رسیدن پیام حذفش میکنه(این خطریه) و مود دوم اگه کانسیومر نتونست پردازش کنه(مثلا کرش کنه) پیامه برمیگرده توی صف. یه nack هم داره.

  • کافکا دو مرحله ack داره از producer به broker و از consumer به broker. از پرودیوسر به بروکر سه تا مود داره یه مود acks=0 که میشه همون fire & forget. یه مود acks=1 که رپلیکای لیدر باید تایید بده و یه مود acks=all که همه رپلیکاها باید تایید بدن(که پیام دریافت و ذخیره شده). از کانسیومر به بروکر هم کانسیومر بعد از پردازش پیام میتونه commit کنه اینجوری offset اش میره جلو ولی اگه کرش کنه و نتونه commit کنه دور بعدی که میاد بالا پیامه رو میبینه دوباره

3- Retry & Dead Letter Queue (DLQ)

یه پیام بعد از چندبار ریترای و پردازش ناموفق میره به یه صفی به اسم DLQ که بعدا بررسی بشه که مشکل چی بوده.

  • ردیس pub/sub نداردش. Redis Stream هم نداره ولی میشه دستی پیادش کرد.

  • ربیت به صورت native داردش و میتونی یه DLX (Dead letters Exchange) تعریف کنی پیاما بره اون تو

  • کافکا هم به صورت native نداره ولی میتونی یه تاپیک DLQ تعریف کنی پیاما رو بریزی اون تو

4- Replication

میتونیم دیتای بروکر رو رپلیکیت کنیم که در صورت کرش کردن یه نود بقیه نودها جاشو بگیرن.

  • ردیس stand-alone نداره همچین ویژگی ای باید از Redis Sentinel یا Redis cluster استفاده بشه.

  • ربیت با Mirrored Queues اینکارو میکنه.

  • کافکا به صورت کلاستری پیاده میشه. پارتیشن ها روی بروکرهای مختلف رپلیکیت میشن.

5- Transactions / Exactly once Semantics (EOS)

یه پیام دقیقا یکبار پردازش بشه نه گم بشه نه تکراری داشته باشیم.

  • ردیس به صورت native نداره باید در سطح اپلیکیشن idempotency رو پیاده کنیم.

  • ربیت هم مثل ردیسه دقیقا.

  • کافکا به صورت native داره اما پیچیده است.

6- Backpressure & Flow Control

وقتی consumer نمیرسه پیاما رو پردازش کنه بروکر میاد سرعت producer رو کم میکنه.

  • ردیس pub/sub نداره ولی Redis Stream به صورت محدود داره.

  • ربیت داره.

  • کافکا داره خود کانسیومر سرعتش رو تنظیم میکنه.

Application Level Reliability Techniques

1- Idempotency

تعریف عملیات idempotent:‌ عملیاتی که با چندبار انجام دادنش نتیجه نهایی یکسان باشه.

مثال:

-- idempotent

set user_balance = 100;

-- non-idempotent

set user_balance += 100;

idempotency تضمین میکنه که اگه یه پیام چندبار پردازش بشه مشکلی پیش نیاد. مثلا دوبار از حساب کاربر پول کسر نشه یا دوبار نتونه خرید انجام بده و ... .

وقتی به صورت at least once داریم از بروکر استفاده می کنیم پای این وسط میاد و باید حواسمون بهش باشه و پیام های تکراری رو در سطح اپلیکیشن هندل کنیم.

2- Deduplication Logic

حذف پیامای تکراری مثلا با استفاده از unique id یا شناسه تراکنش.

3- Consistency Check

چک کردن این که وضعیت سیستم پس از پردازش پیام درست باشه مثلا موجودی کاربر منفی نباشه.

message brokerredisapache kafkarabbitmq
۶
۱
حسن سعادت
حسن سعادت
یه مهندس نرم افزار کنجکاو، عاشق حل مسئله و ساده کردن مفاهیم پیچیده
شاید از این پست‌ها خوشتان بیاید