توی این مقاله میخواهیم با هم در مورد بهترین الگوهای طراحی صف، بهینه کردن روابط بین Exchangeها، مدیریت بهتر RabbitMQ و همچنین اشتباهات مرسوم توی این حوزه با هم صحبت کنیم. بخشی از این مقاله حاصل خوندن مقالهها و دیدن ویدئویهای مرتبط با مدیریت RabbitMQ و بخشی از اون هم ماحصل تجربههای خودم از کار با RabbitMQ است.
قبل از خوندن این مقاله اگر دوست داشتید میتونید دو جلسه کارگاه آموزش RabbitMQ رو در اینجا و اینجا ببنید.
الگوهای طراحی Exchangeها
داشتن تنها یک Exchange خارجی در هر پروژه: Exchangeها در RabbitMQ میتونند به صورت خارجی External و یا به صورت داخلی Internal باشند. امکان دسترسی خارجی به Internal Exchangeها در RabbitMQ وجود نداره. معمولا برای هر پروژه خوبه که از یک Exchange خارجی استفاده کنیم و مابقیExchangeها، صفها و روابط بین اینها رو پشت اون Exchange خارجی مخفی کنیم. با این کار طراحی صفها رو کپسوله میکنیم و تغییرات Exchangeها و روابطمون بینشون یک مسئلهی داخلی محسوب میشه و تغییراتشون موجب تغییر توی اپلیکیشنهای خارجی نمیشه.
استفاده از Topic Exchangeها و عدم استفاده از Header Exchangeها: در RabbitMQ ما چهار نوع Exchange اصلی داریم: Fanout، Direct، Topic و Header. وظیفهی Exchange Fanout ارسال کپی پیام به تمام صفهای متصل به اونه و این کار رو بدون هیچگونه مسیریابی و یا Filtering پیام انجام میده. Exchange Direct امکان ارسال پیام رو با Route مشخص فراهم میکنه. Exchange Topic میتونه علاوه بر Routing معمولی از Wildcard Routing هم پشتیبانی میکنه و در نهایت Header Exchangeها مسیریابی رو با آرایهای از Key و Valueها انجام میده. از بین این چهار نوع اصلی بیشتر فشار و بار رو برای مسیریابی Header Exchangeها ایجاد میکنند و توی دیتا با حجم بالا خودش میتونه یک گلوگاه توی سیستم باشه. معمولا ما به مسیریابی در هنگام ارسال پیام در RabbitMQ نیاز داریم و توی سیستمهای پیشرفته کمتر از ارسال پیام بدون مسیریابی استفاده میکنیم. به همین دلیل کاربرد Fanout میتونه محدود باشه. در عوض Topic Exchange همهی قابلیتها Direct Exchange رو در داخل خودش داره و از طرف با پشتیبانی از مسیریابی همراه با Wildcard میتونه ما رو توی مسیریابی بهینه در پروژهمون یاری کنه. کل یک پروژهی پیچیده رو با دهها Exchange و صدها صف میشه با Topic Exchange به راحتی مدیریت کرد و بهینهترین و پچیدهترین الگوی روابط رو هم به راحتی با این Exchange پیاده کرد.
استفاده از Alternate Exchange: یک پیام به هر دلیلی از جمله اشتباه در طراحی سیستم و یا استفاده از Routing نامناسب در Client ممکنه که در RabbitMQ به مقصد نرسه. در حالت معمول در صورتی که یک پیغام به مقصد نرسه اون پیغام lost میشه و ما برای همیشه اون پیغام رو از دست میدیم. موقعی که داریم یک Exchange رو تعریف میکنیم میتونیم به RabbitMQ بگیم که اگر اون Exchange نتونست کارش رو به درستی انجام بده و به هر دلیلی پیغام به مقصد نرسید اون پیغام رو به یک Exchange دیگه تحویل بده. در اصل این Exchange کارش رسیدگی به امور پیغامهای از دست رفته است. به طور مثال توی پروژه ما میتونیم یک Exchange داشته باشیم و این Exchange رو به عنوان Alternate Exchange تمامی Exchangeهای پروژهمون تعریف کنیم. مرسومه که نوع این Exchange از نوع Fanout باشه و یک صف بهش متصل باشه تا همیشه بدون توجه به Routing اولیه پیغامهای مسیریابی نشده رو داخل صف متصل به خودش قرار بده. اون وقت هر چند وقت یک بار میتونیم سراغ این صف بریم و این پیغامها رو بررسی کنیم تا دلیل به مقصد نرسیدنشون رو متوجه بشیم.
الگوهای طراحی Queueها
استفاده از Lazy Queueها در زمان مناسب: به طور پیش فرض برای کسب Performance بهتر در RabbitMQ همهی پیامها در رم نگه داری میشه. وقتی که حجم هر پیامتون بالاست و تعدادی زیادی پیام رو میخواهید توی صف نگهداری و مدیریت کنید، به سادگی رم سیستم پر میشه و به مشکل کمبود فضا میخورید. هر وقت تعداد بالای پیام و یا پیامهای با حجم سنگین داشتید حتما قابلیت Lazy Mode صفی که باهاش کار میکنید رو فعال کنید. با استفاده از این قابلیت پیامها توی هارد ذخیره میشه و در صورت نیاز از هارد بازیابی میشه و در اختیار مصرفکنندهگان پیام قرار داده میشه.
استفاده محدود از Priority Queueها: در RabbitMQ این امکان فراهم شده که ما صفهایی داشته باشیم که پیامهای داخل اونها به ترتیب اولویت تعیین شده مرتب بشه. حداقل اولویت یک صف عدد 0 و حداکثر اون میتونه تا عدد 255 تعیین بشه. هر بار که پیام جدیدی به صف میرسه RabbitMQ این پیام جدید رو با پیامهای قبلی مقایسه میکنه تا اون رو در جای مناسب قرار بده تا ترتیب اولویت بین پیامها حفظ بشه. بدیهیه که توی تعداد بالای پیام و اولویتهای متفاوت این موضوع روی سیستم بار و فشار زیادی میاره. عدد بهینه حداکثر داشتن ده اولویت توی یک صفه و البته بهینهترین کار اینه که صفهای متفاوت برای مدیریت اولویت پیامهای مختلفتون داشته باشید تا هر بار RabbitMQ مجبور به مرتب سازی صف نشه.
حذف خودکار صفهای اضافی: در RabbitMQ امکانات متفاوتی برای مدیریت صفهای یکبار مصرف وجود داره. از جمله صف Exclusive که بعد از بسته شدن Connection به طور خودکار حذف میشه و یا صفهایی که وقتی دیگه مصرف کنندهای بهشون متصل نیست، خودشون رو حذف میکنند. هر وقت که صف رو به طور موقت میخواستید استفاده کنید از این امکانات استفاده کنید و بدون دلیل RabbitMQ رو شلوغ نکنید.
خالی نگه داشتن صفها: از صفها به عنوان انبارهی داده استفاده نکنید. ماهیت RabbitMQ انتقال و مسیریابی دیتاست. برای نگهداری دیتاها میتونید از دیتابیسها یا ابزارهای دیگه موجود استفاده کنید. وقتی که صفهاتون بیش از حد حجیم هستند در صورتی که سیستم Reboot بشه زمان بیشتری لازمه تا تمام اطلاعات دوباره توی Ram بارگذاری بشه. تا جایی که میتونید برای صفها و پیغامهاتون حداکثر طول عمر تعیین کنید. همچنین میتونید حداکثر تعداد پیغام و حداکثر حجم برای صفتون تعیین کنید. در نهایت میتونید برای پیغامهای حذف شدهتون صف دیگهای در نظر بگیرید تا به صورت خودکار داخل اون صف ریخته بشند.
استفاده بهینه از Connectionها و Channelها
عدم باز و بسته کردن Connection به ازای هر پیام: برای اتصال به RabbitMQ ما در ابتدا باید یک Connection رو باز کنیم. Connection یک ارتباط واقعی TCP/IP بین Client و نرمافزار RabbitMQ است. بر روی این Connection ما میتونیم یک تا بینهایت ارتباط مجازی داشته باشیم. در RabbitMQ این ارتباط مجازی رو Channel میگن. باز و بسته کردن Connectionها مخصوصا وقتی از SSL/TLS استفاده میکنیم برای سیستم هزینهبر و زمانبره. بهینهتر اینه که ما Connectionهامون رو توی طول برنامه تا جایی که نیازشون داریم باز نگه داریم و در صورت عدم استفادهی طولانی مدت اونها رو ببندیم. در نهایت خوبه که Channel برا ارسال و یک Channel برای دریافت داشته باشیم.
عدم اشتراک گذاری Channelها بین Threadهای مختلف: در اکثر کتابخونههایی که سمت Client برای RabbitMQ نوشته شده، برای رسیدن به Performance بیشتر Channelها رو Thread Safe ننوشتند. به خاطر این موضوع به هر Thread چنل خاص خودش رو باید داشته باشه و از به اشتراک گذاری Channelها بین Threadهای مختلف خودداری کنید.
روشهای بهینه دریافت پیام
استفاده از روش Push به جای استفاده از روش Pull: به دو صورت میتونیم دیتا رو از RabbitMQ بخونیم. اولین روش استفاده از Push است. با استفاده از این روش هر وقت پیغامی داخل صفی که ما بهش گوش میدیم درج میشه، از طرف RabbitMQ یک نوتیفیکیشن برای ما ارسال میشه و ما میتونیم اون پیغام رو دریافت کنیم. در روش Pull این Client هست که هر چند مدت یک بار از RabbitMQ میپرسه که آیا دیتای جدیدی داره یا نه؟ به طور پیش فرض برای خوندن اطلاعات صف از روش Push استفاده کنید. این روش کارآمدتر و بهینهتر از روش Pull است. تنها در صورتی از روش Pull استفاده کنید که سیستمهاتون به صورت غیر همزمان با هم متصل هستند. مثلا یک صف برای ذخیره لاگ دارید و سیستم لاگتون هر شب ساعت دوازده شب به این صف سر میزنه. در این سناریو میتونید از روش Pull استفاده کنید و پس از خوندن کل صف، ارتباطتون رو قطع کنید.
تنظیم کردن QoS: به طور پیشفرض وقتی به یک صف متصل هستید و دارید اون صف رو Consume میکنید، هر پیغامی به صف ارسال بشه به طور خودکار همون لحظه این پیغام به کلاینت ارسال میشه. با تنظیم کردن پارامترهای QoS میتونید مشخص کنید که تا وقتی پیغام قبلی رو تعیین وضعیت نکردید پیغام جدیدی بهتون ارسال نشه و پیغامهای جدید در صف نگهداری بشن. اینجوری اگر سیستمون یکدفعه کرش کنه تنها یک پیغام باید بازیابی بشه و دوباره به صف برگرده. البته با توجه به سناریوی خودتون میتونید هر بار تعداد بیشتری پیام دریافت کنید و به صورت دستهای به پیغامها رسیدگی کنید اما حتما بر اساس نیازتون Qos رو تنظیم کنید.
فرستادن Auto-acknowledgements رو متوقف کنید: موقعی که دارید تنظیمات دریافت پیام رو توی Client انجام میدید میتونید به RabbitMQ بگید که پس از ارسال پیام به شما به صورت خودکار اون پیغام رو از داخل صف حذف کنه. مشکل کار اینجاست که اگر در میانهی راه پیغام به هر دلیلی به شما نرسه یا اینکه شما نتونید به درستی پیغام رو پردازش کنید، اون پیغام برای همیشه از دست رفته و قابل بازیابی نیست. به جای فرستادن Auto act خودکار، در ابتدا پیغام رو دریافت کنید و در صورت پردازش صحیح، پیغام act رو برای RabbitMQ بفرستید. در این صورت اگر هر اتفاقی برای پیام بیافته و به صورت صحیح پردازش نشه، RabbitMQ اون رو به صف بر میگردونه و پیغام از دست نمیره.
سیستم RabbitMQ رو میتونید با ابزار پرمتئوس مانیتور و مدیریت کنید. هم همیشه نسخهی Erlang و RabbitMQ خودتون رو بروز نگه دارید و در اتنها امیدوارم که این مقاله براتون مفید واقع شده باشه و اگر نکتهای به نظرتون میرسه خوشحال میشم که باهام در میون بزارید.