سلام به همگی :)
خب بعد از مدت ها تصمیم گرفتم با یک موضوع جذاب، به نوشتن ادامه بدم. راستش دلم برای حسِ خوبِ نوشتن براتون، تنگ شده بود و فرصتی هم پیدا شد که بنویسم :)
تا حالا شده بازی تلفن رو انجام بدید؟ حتما در جمع خانواده و دوستان این بازی رو قبلا انجام دادید. بازی تلفن به این صورته که یک نفر یک جمله رو پیش خودش میسازه و به طوری که بقیه متوجه محتوای جمله نشن، اون رو در گوش نفر کناری اعلام میکنه. این فرایند تا جایی ادامه پیدا میکنه که به نفر آخر برسه و اون جمله رو بلند اعلام کنه. با فرض این که محتوای جمله، به طور صحیح به نفر آخر رسیده باشه، همه افراد، منتظر نفر و یا نفرات قبل قبل برای دریافت جمله (پیام) هستن و به نحوی معطل (بلاک) میشن.
ارتباط همگام (Synchronous communication)، به ارتباطی گفته می شود که یک سرویس قبل از اقدام، منتظر پاسخ سرویس دیگر می ماند.
در حالی که این مدل ارتباطی گاهی اوقات به خوبی کار می کنه، اما تکیه همیشگی بر ارتباط همگام برای هر نوع تعامل بین میکروسرویس ها انتخاب مناسبی نیست. چرا؟ بیاین در موردش یکم جدی تر و البته فنی تر صحبت کنیم.
اگر فقط از ارتباط همگام استفاده کنیم، با رشد مقیاس پروژه و یا محصول در حال توسعه، دچار یک سری چالش خواهیم شد:
همانطور که گفتیم، در یک مدل همگام، یک سرویس درخواستی را به سرویس دیگر می فرستد و منتظر پاسخ می ماند. اگر چندین سرویس با این روش به هم متصل بشوند، با تاخیر زیادی مواجه خواهیم شد.
به عنوان نمونه، اگر سرویس A سرویس B را فراخوانی کند، که خودِ B ، سرویس C را فراخوانی می کند، زمان پاسخ نهایی برابر با مجموع زمان های تاخیر هر فراخوانی خواهد شد. در سناریوهایی که سرویس ها از نظر جغرافیایی توزیع شده یا تحت بار سنگین هستند، این تاخیر به یک bottleneck تبدیل می شود.
زمانی که یک سرویس به طور همگام به سرویس دیگر وابسته است، هرگونه downtime یا خرابی در یک سرویس می تواند کل سیستم را در بر بگیرد.
اگر سرویس B در دسترس نباشد، سرویس A نمی تواند درخواست های خود را پردازش کند. اینجا اصطلاحاً به دلیل tight coupling که بین دو سرویس وجود دارد، عملکرد کل سیستم را شکننده می کند. به بیان دیگر یک نوع وابستگی ضمنی در اینجا به وجود آمده است که در آن برای این که کل سیستم دچار اختلال نشود باید هر دو سرویس A و B عملیاتی و در حال پاسخ دهی در لحظه باشند.
ارتباط همگام، برای پاسخ دهی real-time به درخواست ها نیازمند نگهداری و مراقبت است.
برای پاسخگویی در زمان های پیکِ درخواست، باید منابع سخت افزاری بیشتری تامین کنیم، که منجر به استفاده غیر بهینه از منابع، در طول زمان های خارج از پیک می شود.
البته به طور خاص برای این چالش راه حل هایی مانند Autoscaling ارائه شده است که نمونه هایی از آن در Autoscaling در کوبرنتیز و یا AutoScalingGroup در OpenStack به کار گرفته شده است.
روش های گوناگونی برای مدل ارتباطی Synchronous وجود دارد که در ادامه به معرفی بعضی از آن ها می پردازیم :
در مدل ارتباط غیرهمگام، بر خلاف مدل همگام، فراخواننده سرویس، درخواست را ارسال می کند اما با این که منتظر پاسخ است، بلاک نمی شود. به عنوان مثال، یک میکروسرویس Payment می تواند درخواست "Process Payment" را در یک صف قرار دهد. میکروسرویس "Billing" درخواست را به صورت غیرهمگام پردازش می کند و یک پیام "Payment Processed" را در response queue قرار می دهد.
یک الگوی رایج برای پیاده سازی این روش، استفاده از message queue (صف پیام)، مانند Kafka و یا RabbitMQ است.
در یک معماری Event-Driven، سرویس ها به وسیله publish/subscribe کردن به event ها ارتباط برقرار می کنند. به جای فراخوانی مستقیم سرویس ها به یکدیگر، سرویس ها event هایی را منتشر (publish) می کنند که سرویس های دیگر آن ها را مصرف می کنند.
به عنوان مثال، زمانی که یک رخداد "OrderPlaced" به وسیله سرویس ثبت سفارش منتشر می شود، سرویس انبارداری موجودی خود را آپدیت میکند و همچنین سرویس Notification یک ایمیل تایید سفارش ارسال می کند. این عملیات ها بدون ارتباط مستقیم سرویس ها با یکدیگر انجام می شود.
زمانی که سرویس ها با استفاده از اصل مهم Single Responsibility و همچنین با حد و مرز مشخص و بدون overlap اضافی تعریف و پیاده سازی شوند، به طور طبیعی نیاز به ارتباط بین سرویس ها کاهش پیدا می کند.
فرض کنید یک سرویس User داخل برنامه تعریف شده است که همزمان به احراز هویت و داده کاربرها رسیدگی می کند. در اینجا بهتر است سرویس User به دو سرویس UserProfile و Authentication تقسیم شود. این کار باعث کاهش تعامل های اضافی شده و همینطور سبب افزایش قابلیت نگهداری و مقیاس پذیری هر کدام از این سرویس ها می شود.
برای سناریوهایی که نیازمند جابه جایی حجم زیادی از دیتا هستند، به جای استفاده از درخواست های synchronous، باید از bulk data transfer استفاده کنیم. به عنوان مثال، برای یک سرویس که وظیفه Reporting را بر عهده دارد و لاگ های تراکنش پرداخت را از سرویس Payment میگیرد، به جای آن که برای دریافت report ، به هر تراکنش کوئری بزند، از این مکانیزم استفاده می کند.
اغلب اوقات، فراخوانی های مکرر بین سرویس ها می تواند با مکانیزم caching و یا نگهداری کپی محلی، جایگزین شود. برای مثال سرویس Product به جای این که هر بار جزئیات محصول را از سرویس Catalog فراخوانی کند، میتواند جزئیات محصولات پر بازدید را cache کند.
این هم از مطلب امروز در مورد راهکارهای مختلف ارتباطی بین میکروسرویس ها!
امیدوارم براتون مفید بوده باشه😇