در این مقاله قصد داریم تا Message Queue های نرم افزاری را بیشتر بشناسیم. استفاده از Message Queue ها امروزه بسیار پررنگ تر شده است و نقش مهم تری پیدا کرده است.
فلسفه این Message Queue ها همان مفهوم Producer و Consumer است. مهم ترین هدف Message Queue ها در صنعت نرم افزار، ایجاد ارتباط بین سیستم های نرم افزاری مختلف به صورت آسنکرون یا غیرهمزمان است. در ادامه بیشتر با جزییات آن ها آشنا می شویم.
یادگیری مفهموم Message Queue را با ذکر یک مثال شروع می کنیم.
شاید در فرودگاه ها دیده باشید که وقتی قبل از سوار شدن به هواپیما، به کانتر فرودگاه (یا همام بخشی که چمدان ها را از شما تحویل می گیرند) می روید، یک کارمند نشسته است که چمدان شما را وزن کشی کرده و یک برچسب که حاوی مقصد و زمان تحویل است روی آن گذاشته و به یک ریل در حال حرکت که مابقی کانتر ها نیز به آن دسترسی دارند می فرستد. سپس چمدان شما به یک قسمتی می رود که دیگر آن را نمی بینید!
می توان حدس زد که آن طرف این ریل متحرک هم کارمندان هواپیمایی های مختلف نشسته اند که برچسب چمدان ها را نگاه می کنند و مقصد را از روی آن می خوانند و با توجه به مقصد چمدان ها، آن ها را به باربری انتقال می دهند تا به هواپیمای مربوطه برده شوند.
حال ارتباط مثال فوق را با Message Queue برسی می کنیم. در مثال فوق چمدان ها حکم Message و ریلی که بین کانتر ها و کارمندان هواپیمایی ها مشترک و در جریان است Queue است و هر کدام از کانتر ها همان Application ها هستند! به همین سادگی! در نرم افزار هم ممکن است تعداد زیادی Application با هم در ارتباط باشند و بخواهند به یکدیگر پیام بفرستند. همچنین وقت نیز برای آنان ازشمند است. مثلا در مثال فوق، چرا خود مسافران نمی روند چمدان هایشان را به کارمندان هواپیمایی تحویل بدهند؟! پاسخ واضح است، چون ممکن است اگر تعداد مسافران خیلی زیاد شود وقت آن ها تلف شود و همچنین ازدحام به وجود بیاید و سردرگم بشوند. اما به کمک ریل، چمدان خود را تحویل داده و به کارهای دیگر می رسند. گرچه در همان کانتر هم باید پشت یک صف معمولا خلوت و کوتاه بایستند اما این صف کجا و آن صف کجا!
پس با کاربرد Message Queue آشنا شدیم و فهمیدیم که از Message Queue ها، ایجاد و نگهداری برنامه ها را ساده می کند و باعث کاهش پیچیدگی می شود. البته استفاده از آن اجباری نیست، ولی وقتی که مقیاس نرم افزار بزرگ می شود و تعداد سیستم ها بالا می رود و زمان نیز ارزش پیدا می کند، استفاده نکردن از آن می تواند تبعاتی را در پی داشته باشد (از جمله افزایش پیچیدگی و کاهش بهره وری). مثلا وقتی می خواهیم سوار اتوبوس بشویم تا به یک شهر دیگر برویم، چمدانمان را مستقیم به راننه تحویل می دهیم! چون تعداد مسافران محدود است و کار سریع راه می افتد، اما در فرودگاه تعداد مسافران و پرواز ها زیاد است. اگر نخواهیم از صف استفاده کنیم، راه های دیگر مانند صدا زدن از راه دور (RPC) یا استفاده از پایگاه داده مشترک (Shared Database) هم وجود دارد.
حال که با ضرورت Message Queue ها و کلیات نحوه کارکرد آن ها آشنا شدیم، وقت آن است که به جزییات آن بپردازیم. در صنعت نرم افزار، Message Queue های مختلفی ساخته شده اند (مثل Rabbit MQ و Kafka) که هدف اصلی تمام آن ها همین ایجاد ارتباط بین چند برنامه یا سرویس مختلف در بهینه ترین زمان ممکن است. اما اگر بخواهیم ریز تر شویم، هر کدام از آن ها یک سری قابلیت دارند که در مثال های فوق به آن ها اشاره نشد. مثلا ممکن است یک سرویس بخواهد یک پیام را به چند برنامه دیگر بفرستد (یعنی مثلا یک چمدان به چند هواپیما برود!)، این چگونه ممکن است؟ آیا Message Queue های نرم افزاری از این قابلیت ها پشتیبانی می کنند؟ پاسخ "بله" است. این مثال یکی از نمونه های قابلیت های فراوان Message Queue های نرم افزاری بود.
در ادامه لازم است تا ابتدا با چند مفهوم تخصصی آشنا شویم، شرح جزییات برخی از این قابلیت های فراوان و جالب برویم. توجه داشته باشید ما Message Queue ای که به عنوان مرجع انتخاب کرده ایم، RabbitMQ است. گرچه این مفاهیم در همه Message Queue ها مشترک است، اما شاید نام هایی که برای عناوین گذاشته شده اند متفاوت باشد.
ابتدا با چند مفهوم آشنا می شویم:
تولید کننده (Producer): کسی که پیامی را ارسال می کند. به آن Sender هم گفته می شود.
صف (Queue): جایی که پیام ها به آن جا می روند. صف حاوی پیام ها است.
مصرف کننده (Consumer): کسی که پیامی را دریافت می کند. به آن Receiver هم گفته می شود.
توجه داشته باشید که یک برنامه، می تواند هم تولید کننده و هم مصرف کننده باشد. در شکل فوق، P یا همان Producer یک پیام را در صف یا Queue قرار داده و C یا همان Consumer، پیام را بر میدارد (مصرف می کند).
همانطور که پیش تر گفته شد ممکن است Producer بخواهد 2 پیام را برای 2 تا Consumer مختلف ارسال کند. مثلا پیام شماره 1 برای C1 و پیام شماره 2 برای C2. این امر نیز به راحتی به کمک ارسال این دو پیام به صف قابل انجام است:
همانطور که مشخص است، تولید کننده دو پیام به صف می فرستد و یکی از این پیام ها به C1 و یکی دیگر به C2 می رسد.
حال اگر بخواهیم "یک" پیام را به چند مصرف کننده بفرستیم تکلیف چیست؟ توجه کنید که اگر یک پیام را در یک صف قرار دهیم و یک مصرف کننده آن را بردارد، آن پیام دیگر در صف موجود نخواهد بود و مصرف کننده دیگری نمی تواند به آن دسترسی داتشه باشد. در اینجاست که باید از تبادل گر یا Exchange کمک بگیریم که در ادامه با آن آشنا می شویم.
تبادل گر (Exchange): یک واسط بین تولید کننده و صف است که قادر است تا پیامی که تولید کننده می دهد را بین یک تا چند صف (بسته به سیاستی که به او گفته می شود) تقسیم کند.
حال به کمک Exchange، تولید کننده می تواند یک پیام را به چند مصرف کننده برساند:
دایره آبی رنگ که با X مشخص شده همان Exchange است که نقش واسط را دارد. تولید کننده یک پیام به Exchange می فرستد و می گویید که آن را به تمام صف هایی که به آن متصل هستی ارسال کند. Exchange هم آن پیام را به دو صف قرمز رنگ ارسال کرده و این پیام هم به دست C1 و هم به دست C2 می رسد.
اما ممکن است تولید کننده به جای اینکه بخواهد پیام را به تمام صف ها ارسال کند، بخواهد پیام را فقط به تعدادی از صف هایی که Exchange به آن متصل است ارسال کند. اکثر Message Queue های معتبر از این امر پشتیبانی میکنند و اصطلاحا به این کار Routing یا مسیریابی گفته می شود. مثلا تولید کننده می گوید پیام قرمز رنگ را به صف شماره 1 ارسال کن و پیام آبی رنگ را به صف شماره 2 ارسال کن. در این جا است که Exchange باید مسیریابی انجام دهد تا مسیر درست را برای هر پیام تشخیص دهد. مسیریابی چند نوع دارد که انواع آن را در ادامه بررسی می کنیم.
انواع مسیریابی:
ارسال به همه (type = fanout): وقتی تولید کننده پیامی را به Exchange ارسال می کند، Exchange آن پیام را در تمام صف هایی که به آن متصل هستند قرار میدهد (مانند مثال فوق)
ارسال به برخی از صف ها بر اساس کلید (type = direct): پیام ها بر اساس کلید یا binding key آن ها، در صف مربوطه قرار می گیرند. با ذکر یک مثال به کمک کل زیر این مفهوم را بهتر درک می کنیم:
ابتدا تولید کننده به هر پیامی که می خواهد آن را به Exchange بفرستد، یک کلید یا Routing Key اختصاص می دهد. در شکل فوق این کلید می تواند یکی از مقادیر "orange" یا "black" یا "green" را داشته باشد. سپس وقتی این پیام به Exchange می رسد، در آن جا بر اساس همان کلید به یک صف ارسال می شود. مثلا Exchange نگاه می کند که اگر کلید یک پیام برابر با "orange" باشد، آن را به صف Q1 می فرستد و اگر "black" یا "green" باشد، آن را به صف Q2 ارسال می کند.
ارسال به برخی از صف ها بر اساس الگو (type = topic): در این نوع مسیریابی، پیام ها براساس الگوی شان به صف ها ارسال می شوند. برای آشنایی با الگو ها لازم است تا با دو قاعده زیر آشنا شویم:
عبارت * : حرف * در متن الگو به این معنی است که * می تواند با فقط یک کلمه جایگزین شود.
عبارت # : حرف # در متن الگو به این معنی است که # می تواند با یک یا بی نهایت کلمه جایگزین شود.
با ذکر یک مثال با مفاهیم فوق بهتر آشنا می شویم. شکل زیر را در نظر بگیرید:
با ذکر چند مثال، شکل فوق را بهتر درک خواهیم کرد. اگر یک پیام دارای Routing Key برابر مقادیر زیر باشد، به صف هایی که جلوی آن ها نوشته شده می روند:
مقدار کلید برابر quick.orange.rabbit: به Q1 و Q2 می رود. به Q1 می رود چون کلمه وسط آن orange است و به Q2 می رود چون کلمه آخر آن rabbit است.
مقدار کلید برابر lazy.orange.elephant: به Q1 و Q2 می رود. به Q1 می رود چون کلمه وسط آن orange است و به Q2 می رود چون کلمه اول آن lazy است (بقیه اش مهم نیست)
مقدار کلید برابر quick.orange.fox: فقط به Q1 می رود چون کلمه وسط آن orange است و شروط رفتن به Q2 را ندارد چون نه بخش آخر آن rabbit است و نه بخش اول آن Lazy است.
مقدار کلید برابر lazy.brown.fox: فقط به Q2 می رود چون کلمه اول آن lazy است. شرط رفتن به Q1 را ندارد چون کلمه وسط آن orange نیست.
مقدار کلید برابر quick.brown.fox: به هیچ کدام نمی رود چون شرط لازم را ندارد.
مقدار کلید برابر lazy.green.rabbit: به Q2 می رود چون هر دو شرط را داراست. دقت کنید که دو بار وارد صف نمی شود و فقط یک بار در صف قرار می گیرد.
همانطور که دیدیم، می توانیم بسته به سیاستی که تعریف می کنیم، یک پیام را به دست ممصرف کنندگان برسانیم. الگو های دیگری هم وجود دارند که لیست کامل جزییات آن ها در این لینک موجود است. به عنوان مثال می توان پیام ها را براساس پارامتر های مختلفی فیلتر کرد، یا می توان آن ها را رمز نگاری کرد. همچنین می توان برای پیام هایی که به هر دلیلی به گیرنده نرسیده اند (مثلا الگویشان با الگوی هیچ صفی مطابقت نداشته) استراتژی هایی تعریف کرد. می توان یک ددلاین برای رسیدن هر پیام به مقصد تعریف کرد و ... . به طور کلی 65 الگو دراجزای مختلف سیستم (نه فقط Exchange) داریم که در این مقاله با برخی از آن ها آشنا شدیم.
نمای کلی این اجزا و الگوها در شکل زیر مشخص است:
برای اطلاعات بیشتر به این لینک بروید.
یکی از ابزار هایی که با آن مثال ها را دیدیم ابزار RabbitMQ بود. اما ابزار های دیگر مانند Kafka هم وجود دارند که در ادامه آن ها را خواهیم دید . با جزییات هر کدام آشنا خواهیم شد:
ابزار RabbitMQ:
یک ابزار متن باز محبوب است. برای دیدن سایت این ابزار به این لینک بروید. این ابزار نسبتا ساده است و یادگیری مستندان آن آسان است. از معایب آن می توان به این مورد اشاره کرد که سرعت آن کمی کم است. (مخصوصا وقتی حجم پیام ها زیاد می شود).این ابزار از پروتوکل های معروف مثل AMQP و STOMP و MQTT و غیره هم پشتیبانی می کند.
ابزار Kafka:
ابزار Kafka را Apache توسعه داده است و یک ابزار کامل با قابلیت های بسیار زیاد است. شروع کار و یادیری مستندات آن به نسبت سخت تر است اما سرعت بهتر و قابلیت های بیشتر از جمله مزایای ان است. برای دیدن سایت این ابزار به این لینک بروید.
ابزار های دیگری مانند: MuleSoft Anypoint Platform و IBM MQ و Azure Scheduler نیز وجود دارند که هر کدام مزایا و معایب خودشان را دارند.
[1] وبسایت https://www.rabbitmq.com/getstarted.html
[2] وبسایت https://www.cloudamqp.com/blog/what-is-message-queuing.html
[3] وبسایت https://www.simplilearn.com/kafka-vs-rabbitmq-article?source=frs_left_nav_clicked
[4] وبسایت https://www.instaclustr.com/blog/rabbitmq-vs-kafka
[5] وبسایت https://kafka.apache.org
[6] وبسایت https://en.wikipedia.org/wiki/Message_queue
[7] وبسایت https://www.cloudamqp.com/blog/what-is-message-queuing.html
[8] وبسایت https://aws.amazon.com/message-queue
[9] وبسایت https://www.enterpriseintegrationpatterns.com/patterns/messaging
این مطلب، بخشی از تمرینهای درس معماری نرمافزار در دانشگاه شهیدبهشتی است