ارتباط مایکرو سرویس های لاراولی با RabbitMQ

ارتباط مایکرو سرویس های لاراولی با RabbitMQ
ارتباط مایکرو سرویس های لاراولی با RabbitMQ

وقتی از معماری یکپارچه (Monolith) استفاده می‌کنیم، توسعه دادن پروژه های بزرگ دست و پاگیر می‌شود. به عنوان یک توسعه دهنده لاراول ، این نکته کاملا چالش برانگیز است; زیرا لاراول به ما یک محیط توسعه کاملا فول استک را ارائه می دهد که در آن می توانیم با Front-end و همچنین Back-end در همان پروژه کار کنیم ، به عبارت دیگر یک برنامه یکپارچه (Monolithic application) داشته باشیم. اما اگر بخواهیم از معماری مایکرو سرویس ها بهره ببریم، مراحل کار به چه شکل خواهد بود؟ و ارتباط سرویس های ما به چه شکل برقرار می‌شود؟ در این مقاله با هم بررسی می‌کنیم.

قبل از آنکه ادامه دهیم، لازم است تا به چند نکته دست و پا گیر در مورد معماری یکپارچه اشاره کنیم. در این معماری چون تمام عملکرد ها (functionalities) داخل یک پروژه هستند، حتی با کوچکترین تغییراتی باید اپلیکیشن را دوباره دپلوی کنیم، که ممکن است روی کل برنامه تأثیر بگذارد. در مقیاس بالا نگهداری چنین پروژه های بزرگی، مشکل تر می‌شود. در اینجاست که معماری مایکرو سرویس ها به کمک ما می‌آید و می‌توانیم عملکرد ها را به کامپوننت ها یا اجزای مستقل تفکیک کنیم..

مسلما معماری مایکرو سرویس ها مناسب هر پروژه ای نیست.. اگر شما در حال خواندن این مقاله هستید، من فرض را بر این گرفته ام که پیشاپیش می‌دانید محل صحیح استفاده از این معماری کجاست و همچنین با نقاط ضعف و قوت آن آشنایی کامل دارید. پس بدون اتلاف وقت سراغ توضیح دادن یکی از قسمت های اصلی در این معماری می‌رویم.

هنگام کار با این معماری باید زیرساختی را برای ارتباطات داخلی سرویس های خود مهیا کنیم. ۳ روش مطرحی که برای این مهم می‌توان استفاده کرد عبارتند از:

  • وب سرویس های REST یا (Representational State Transfer)
  • کانال RPC یا (Remote Procedure Call)
  • مسیج بروکر ها (ابزارهایی مانند Apache Kafka ، RabbitMQ و غیره)


ما در این مقاله قصد داریم استفاده از مسیج بروکر RabbitMQ را در معماری مایکرو سرویس ها بررسی کنیم. یک "مسیج بروکر" به عنوان یک واسطه برای سرویس های ما عمل می‌کند ، پیام ها را از یک سرویس (Publisher یا تولید کننده) دریافت می کند و آنها را برای انجام کار به دیگر سرویس ها (Subscriber یا مصرف کننده) تحویل می دهد. (در مقالات مختلف ممکن است این ۲ بخش Producer و Consumer نیز خوانده شوند)

خوبی استفاده از RabbitMQ این است که پیام های ما را داخل یک صف قرار می‌دهد. پس اگر Subscriber مشغول باشد و یا ارتباط آن با شبکه قطع شده باشد، پیام ما در حافظه موقت RabbitMQ ذخیره می‌شود و هر زمان Subscriber دوباره آنلاین شود، پیام از حافظه موقت به مصرف کننده تحویل داده می‌شود.

مطالبی که بررسی خواهیم کرد، شامل موارد زیر هستند:

  • هدف
  • سرویس ابری AMQP
  • راه اندازی پروژه لاراولی
  • اجرا کردن جاب Ping
  • اجرا کردن جاب User
  • بررسی بهبود ها



هدف

بیایید با نتیجه آنچه در این مقاله خواهیم ساخت شروع کنیم. ما باید ۲ سرویس مجزا آماده کنیم. یکی از آنها به عنوان مصرف کننده یا Consumer عمل می کند و به هر پیام دریافتی گوش می دهد. و دیگری به عنوان تولید کننده ، Publisher و یا ناشری عمل می کند که برخی پیام ها را منتشر می‌کند.

RabbitMq Services Test
RabbitMq Services Test


همانطور که مشاهده می کنید ، ترمینال سمت راست به عنوان تهیه کننده / ناشر فعالیت می کند. ابتدا یک جاب به نام پینگ از طریق یک کامند در کنسول اجرا می‌شود ، و سپس جاب دیگری به نام یوزر اجرا شده و اطلاعات کاربر را از سرویس ۱ به سرویس ۲ منتقل می‌کند. پس وقتی مصرف کننده اطلاعاتی را از ناشر دریافت می‌کند، می‌تواند از آن اطلاعات در سیستم خودش استفاده کند.

مسلما برای استفاده از RabbitMQ باید این ابزار را روی سرور و یا محیط توسعه خود نصب کنید، اگر قبلا این ابزار رو نصب کرده اید که هیچ و اگر هم نه، در پایان این مقاله به راحتی RabbitMQ را با استفاده از داکر راه اندازی می‌کنیم.


سرویس ابری AMQP

علاوه بر نصب RabbitMQ روی سیستم لوکال یک راه استفاده دیگر هم داریم و آن استفاده از سرویس ابری AMQP به عنوان یک سرویس است که میتوانید از وب سایت CloudAMQP.com بهره ببرید. استفاده از این سیستم بسیار ساده است. کافیه ابتدا ثبت نام کنید و یک Instance برای خودتون بسازید. پس از طی این مراحل یک آدرس هاست، یوزر و پسورد و یک vhost دریافت می‌کنید که میتونید بلافاصله ازش استفاده کنید

cloudAMQP
cloudAMQP

این سرویس همچنین امکان استفاده از ابزار مدیریت RabbitMQ رو به شما میده، که می‌تونید از طریقش کانال های مختلفی بسازید و کانکشن ها و صف ها رو مدیریت کنید.

cloudAMQP Manager
cloudAMQP Manager



راه اندازی پروژه لاراولی

نصب فریم‌ ورک لاراول رو بار ها با هم بررسی کردیم.. پس نصب ۲ پروژه مجزای لاراولی رو به عهده خودتون می‌گذارم. پس از نصب لاراول احتیاج داریم تا پکیج کارآمد vyuldashev/laravel-queue-rabbitmq رو که توسط Valdimir Yuldashev توسعه داده شده رو روی هر دو پروژه نصب کنیم:

Installing RabbitMQ package
Installing RabbitMQ package

حال فایل config/queue.php را باز کرده و یک درایور جدید برای RabbitMQ تعریف کنید.

Define a new Queue Connection
Define a new Queue Connection

حالا باید چند متغیری که در تنظیمات بالا تعریف شد را در فایل ENV بگذارید و مقدار دهی کنید. همچنین باید کلید QUEUE_CONNECTION را برابر با نام کانکشنی که در بالا ساختیم (rabbitmq) ، قرار بدهید.

Environment file Changes
Environment file Changes

تنها نکته باقیمانده در این بخش این است که این تنظیمات را باید روی هر ۲ پروژه لاراولی اعمال کنید.



اجرا کردن جاب Ping

در این قسمت ۲ جاب مختلف میسازیم تا عملکرد هر ۲ سیستم را تست کنیم. دقت کنید که هر کدام از جاب ها باید در داخل هر ۲ پروژه ساخته شوند. (که علتش رو توضیح می‌دهم) پس ابتدا دستور زیر رو در هر ۲ پروژه اجرا کنید:

Make the Ping job
Make the Ping job

در سرویس Publisher (سرویس ۱) بدنه این جاب خالی است و داخل متد handle هیچ کدی وجود نداره:

Handle method in Ping job (Service 1)
Handle method in Ping job (Service 1)

و در سرویس Subscriber (سرویس ۲) با استفاده از دستور اکو داریم دریافت شدن یک رویداد (Event) رو شبیه سازی می‌کنیم:

Handle method in Ping job (Service 2)
Handle method in Ping job (Service 2)

این در حالیست که در واقع داریم این رویداد رو از طریق سرویس ۱ اجرا و فراخوانی می‌کنیم. حال برای اینکه در سرویس ۲ بتوانیم به رویدادهای سرویس ۱ گوش کنیم، کافی است دستور زیر را در ترمینال سرویس ۲ اجرا کنیم

Artisan Consume Command
Artisan Consume Command

حالا که سرویس ۲ آماده است، میتوانیم جاب PingJob رو از طریق سرویس ۱ اجرا (Dispatch) کنیم.

راه اول دیسپچ کردن این جاب استفاده از Tinker و اجرای مستقیم این دستور هست:

Dispatching the Ping job
Dispatching the Ping job

و راه دوم دیسپچ کردن این جاب ساخت یک کامند Artisan و اجرای آن در کنسول هست:

PingJob Command
PingJob Command

حالا میتونید با اجرای دستور زیر در کنسول جاب رو اجرا کنید:

 Running PingJob Command
Running PingJob Command



اجرا کردن جاب User

جاب قبلی مثال بسیار ساده ای بود، اما ما همچنین میتونیم داده ها رو به جاب ها پاس بدهیم و در سرویس Subscriber از اون داده ها استفاده کنیم. (کاری که در این جاب انجام خواهیم داد) پس در هر ۲ پروژه دستور زیر رو اجرا می‌کنیم:

Make the User job
Make the User job

درست مثل جاب قبلی، بدنه متد handle جاب ما در سرویس ۱ خالی است و تنها کاری که انجام میدیم، پاس دادن داده ها از طریق متد سازنده کلاس هست:

Handle method in User job (Service 1)
Handle method in User job (Service 1)

حالا در سرویس ۲ داخل متد handle داده ها رو به فرمت Json تبدیل میکنیم و اکو می‌کنیم...

Handle method in User job (Service 2)
Handle method in User job (Service 2)

<پرانتز>

این جا (متد handle)‌ نقطه ای از کدهای ماست که میتونه اتفاق های جالبی بیفته، مثلا:

  • یه سری داده توی دیتابیس ذخیره یا آپدیت بشه.
  • یا یک Event داخلی در اپلیکیشن Trigger بشه و زنجیره ای از دستورات اجرا بشند.
  • یا اینکه از مایکرو سرویس های دیگری استفاده کرد و سایر کارها رو به عهده اونها گذاشت.

<پرانتز/>


بسیار خوب، ادامه میدیم. برای اجرای جابی که ساختیم ، این بار احتیاج داریم تا از طریق متد سازنده اطلاعات کاربر رو در سرویس ۱ به جاب پاس بدیم. (به فرض اینکه حداقل ۱ کاربر توی دیتابیس داشته باشیم) پس از محیط Tinker استفاده میکنیم و دستورات زیر رو اجرا میکنیم:

Dispatching the User job
Dispatching the User job

توجه داشته باشید که در سرویس ۲ باید کامند rabbitmq:consume اجرا شده باشه.. نکته دیگه اینکه در کد بالا داده ای (متغیر user) رو که به کلاس جاب پاس میدیم از نوع آرایه هست; اما چرا؟

اگر آبجکت یک user رو پاس بدیم (مثلا با id شماره ۲) و چنین رکوردی داخل سرویس ۲ موجود نباشه، با یک اکسپشن روبرو میشیم چون که داده ها نمی‌تونند unserialize بشند. پس بهتره که داده ها رو به شکل آرایه پاس بدیم.

همون طور که برای جاب اول یک دستور Artisan ساختیم، این کار رو برای جاب دوم هم انجام میدیم، تا بتونیم از کنسول این جاب رو اجرا کنیم..

حالا میتونیم داده ها رو به راحتی به مایکرو سرویس هامون ارسال کنیم.

RabbitMq Services Test
RabbitMq Services Test



بررسی بهبود ها

  1. هندلر های کاستوم

شاید این سوال براتون مطرح بشه که چرا باید یک جاب رو در هر ۲ سرویس بسازیم؟ مشکل اینه که اگر ما جابی رو در سرویس Publisher دیسپچ کنیم و اون جاب در سرویس Consumer موجود نباشه، با اکسپشن روبرو میشیم چون اون کلاس/آبجکت در اون پروژه موجود نیست.

اما یک راه حل هم برای این موضوع وجود داره. اگر سری به فایل config/queue.php و بخش درایور rabbitmq بزنید، این امکان وجود داره که از کلاس جاب کاستوم خودتون استفاده کنید. (هر رویداد یا پیامی که به سرویس گیرنده برسه، توسط کلاس جاب کاستوم خودتون مدیریت میشه)

Custom Job Handler
Custom Job Handler

برای استفاده از کلاسهای جاب کاستوم، می‌تونید به مستندات پکیج در گیت هاب مراجعه کنید.


۲. صف های سفارشی

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

RabbitMQ Custom Queues
RabbitMQ Custom Queues

اگر می خواهید برای یک مایکرو سرویس مشخص کنید که یک صف خاص، به عنوان مثال یک صف سفارشی را هدف قرار دهد ، کلید زیر را به فایل ENV اضافه کنید.

Custom Queue Environment
Custom Queue Environment

سپس سرویس Consumer شما تنها به پیامی که روی صف سفارشی ارسال شود، گوش می‌دهد. حال برای ارسال پیام از سرویس Publisher روی یک صف سفارشی می‌توان از متد onQueue(‘queuename’) استفاده کرد.

Dispatching a job on a specific queue
Dispatching a job on a specific queue


۳. داکرایز کردن RabbitMQ

ساده ترین روش اجرای RabbitMQ (مخصوصا در محیط توسعه) استفاده از داکر است. کافی است داکر روی سیستم شما نصب باشد، سپس می‌توانید به راحتی RabbitMQ را با دستور زیر بالا بیاورید:

Dockerizing RabbitMQ
Dockerizing RabbitMQ

حال کافی‌ است به پورت 15672 روی localhost مراجعه کنید و پنل مدیریت صف های RabbitMQ را ببینید.


۴. استفاده از Laravel Horizon

اگر با Horizon آشنا باشید، میدانید که این پنل برای مدیریت صف های مربوط به Redis استفاده می‌شود، نکته جالب که شاید ندانید این که این پنل به راحتی با RabbitMQ نیز سازگار است. تنها کاری که باید انجام دهید افزودن کلید زیر به فایل ENV است.

Laravel Horizon
Laravel Horizon