علی سفیدموی
علی سفیدموی
خواندن ۷ دقیقه·۴ روز پیش

همیشه از مدل ارتباطی sync استفاده نکنید!

سلام به همگی :)

خب بعد از مدت ها تصمیم گرفتم با یک موضوع جذاب، به نوشتن ادامه بدم. راستش دلم برای حسِ خوبِ نوشتن براتون، تنگ شده بود و فرصتی هم پیدا شد که بنویسم :)

تا حالا شده بازی تلفن رو انجام بدید؟ حتما در جمع خانواده و دوستان این بازی رو قبلا انجام دادید. بازی تلفن به این صورته که یک نفر یک جمله رو پیش خودش میسازه و به طوری که بقیه متوجه محتوای جمله نشن، اون رو در گوش نفر کناری اعلام میکنه. این فرایند تا جایی ادامه پیدا میکنه که به نفر آخر برسه و اون جمله رو بلند اعلام کنه. با فرض این که محتوای جمله، به طور صحیح به نفر آخر رسیده باشه، همه افراد، منتظر نفر و یا نفرات قبل قبل برای دریافت جمله (پیام) هستن و به نحوی معطل (بلاک) میشن.

ارتباط همگام (Synchronous communication)، به ارتباطی گفته می شود که یک سرویس قبل از اقدام، منتظر پاسخ سرویس دیگر می ماند.

در حالی که این مدل ارتباطی گاهی اوقات به خوبی کار می کنه، اما تکیه همیشگی بر ارتباط همگام برای هر نوع تعامل بین میکروسرویس ها انتخاب مناسبی نیست. چرا؟ بیاین در موردش یکم جدی تر و البته فنی تر صحبت کنیم.


چالش های ارتباط همگام

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

چالش های ارتباط Synchronous
چالش های ارتباط Synchronous

تاخیرِ فزاینده

همانطور که گفتیم، در یک مدل همگام، یک سرویس درخواستی را به سرویس دیگر می فرستد و منتظر پاسخ می ماند. اگر چندین سرویس با این روش به هم متصل بشوند، با تاخیر زیادی مواجه خواهیم شد.

به عنوان نمونه، اگر سرویس A سرویس B را فراخوانی کند، که خودِ B ، سرویس C را فراخوانی می کند، زمان پاسخ نهایی برابر با مجموع زمان های تاخیر هر فراخوانی خواهد شد. در سناریوهایی که سرویس ها از نظر جغرافیایی توزیع شده یا تحت بار سنگین هستند، این تاخیر به یک bottleneck تبدیل می شود.

ایجاد downtime و خرابی های سلسله وار

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

اگر سرویس B در دسترس نباشد، سرویس A نمی تواند درخواست های خود را پردازش کند. اینجا اصطلاحاً به دلیل tight coupling که بین دو سرویس وجود دارد، عملکرد کل سیستم را شکننده می کند. به بیان دیگر یک نوع وابستگی ضمنی در اینجا به وجود آمده است که در آن برای این که کل سیستم دچار اختلال نشود باید هر دو سرویس A و B عملیاتی و در حال پاسخ دهی در لحظه باشند.

هزینه بیشتر

ارتباط همگام، برای پاسخ دهی real-time به درخواست ها نیازمند نگهداری و مراقبت است.

برای پاسخگویی در زمان های پیکِ درخواست، باید منابع سخت افزاری بیشتری تامین کنیم، که منجر به استفاده غیر بهینه از منابع، در طول زمان های خارج از پیک می شود.

البته به طور خاص برای این چالش راه حل هایی مانند Autoscaling ارائه شده است که نمونه هایی از آن در Autoscaling در کوبرنتیز و یا AutoScalingGroup در OpenStack به کار گرفته شده است.


جایگزین هایی برای ارتباط همگام

روش های گوناگونی برای مدل ارتباطی Synchronous وجود دارد که در ادامه به معرفی بعضی از آن ها می پردازیم :

مدل Asynchronous Request-Response

در مدل ارتباط غیرهمگام، بر خلاف مدل همگام، فراخواننده سرویس، درخواست را ارسال می کند اما با این که منتظر پاسخ است، بلاک نمی شود. به عنوان مثال، یک میکروسرویس Payment می تواند درخواست "Process Payment" را در یک صف قرار دهد. میکروسرویس "Billing" درخواست را به صورت غیرهمگام پردازش می کند و یک پیام "Payment Processed" را در response queue قرار می دهد.

یک الگوی رایج برای پیاده سازی این روش، استفاده از message queue (صف پیام)، مانند Kafka و یا RabbitMQ است.

Asynchronous Request-Response
Asynchronous Request-Response

نحوه عملکرد:

  • سرویس sender ، یک request را در صفِ پیام قرار می دهد.
  • سرویس receiver ، درخواست را به صورت غیرهمگام پردازش کرده و response را در صف قرار میدهد.
  • سرویس sender ، به راحتی response را برمیدارد.

مزایا:

  • به دلیل فرآیند non-blocking ، پاسخگویی بهتر می شود.
  • نیازی به عملیاتی بودن سرویس ها به صورت در لحظه و همزمان نیست، چرا که سرویس ها اصطلاحاً loosely couple بوده و از یکدیگر مستقل اند.
  • صف پیام مانند بافر عمل کرده که هنگام ترافیک شدید، کمک کننده خواهد بود.

مدل Event-Driven Architecture

در یک معماری Event-Driven، سرویس ها به وسیله publish/subscribe کردن به event ها ارتباط برقرار می کنند. به جای فراخوانی مستقیم سرویس ها به یکدیگر، سرویس ها event هایی را منتشر (publish) می کنند که سرویس های دیگر آن ها را مصرف می کنند.

به عنوان مثال، زمانی که یک رخداد "OrderPlaced" به وسیله سرویس ثبت سفارش منتشر می شود، سرویس انبارداری موجودی خود را آپدیت میکند و همچنین سرویس Notification یک ایمیل تایید سفارش ارسال می کند. این عملیات ها بدون ارتباط مستقیم سرویس ها با یکدیگر انجام می شود.

Event-Driven Architecture
Event-Driven Architecture

نحوه عملکرد:

  • سرویس ها event ها (مانند "OrderPlaced" یا "PaymentProcessed") را به سمت یک message broker مانند Kafka ، منتشر می کنند.
  • سرویس های مرتبط با event منتشر شده به آن event ها اصطلاحاً subscribe میکنند و عملیات مربوطه را انجام می دهند.

مزایا:

  • استقلال سرویس ها از یکدیگر
  • به دلیل این که سرویس ها در لحظه نیازی به در دسترس بودن یکدیگر ندارند، تاب آوری (resiliency) و مقیاس پذیری (scalability) بالاتری فراهم می شود.
  • در این حالت، اضافه کردن فانکشنالیتی جدید ساده تر خواهد بود. در واقع بدون این که publisher را تغییر بدهیم، میتوانیم سرویس های جدیدی ایجاد کنیم که به راحتی به event های موجود subscribe کنند.


ایجاد حد و مرز خوش-تعریف برای سرویس ها

زمانی که سرویس ها با استفاده از اصل مهم Single Responsibility و همچنین با حد و مرز مشخص و بدون overlap اضافی تعریف و پیاده سازی شوند، به طور طبیعی نیاز به ارتباط بین سرویس ها کاهش پیدا می کند.

فرض کنید یک سرویس User داخل برنامه تعریف شده است که همزمان به احراز هویت و داده کاربرها رسیدگی می کند. در اینجا بهتر است سرویس User به دو سرویس UserProfile و Authentication تقسیم شود. این کار باعث کاهش تعامل های اضافی شده و همینطور سبب افزایش قابلیت نگهداری و مقیاس پذیری هر کدام از این سرویس ها می شود.

Well-Defined Service Boundaries
Well-Defined Service Boundaries


مدل Bulk Data Transfer

برای سناریوهایی که نیازمند جابه جایی حجم زیادی از دیتا هستند، به جای استفاده از درخواست های synchronous، باید از bulk data transfer استفاده کنیم. به عنوان مثال، برای یک سرویس که وظیفه Reporting را بر عهده دارد و لاگ های تراکنش پرداخت را از سرویس Payment میگیرد، به جای آن که برای دریافت report ، به هر تراکنش کوئری بزند، از این مکانیزم استفاده می کند.

Bulk Data Transfer
Bulk Data Transfer

نحوه عملکرد:

  • سرویس ها به صورت دوره ای از طریق batch job و یا data replication داده ها را بین یکدیگر منتقل میکنند.
  • این عملیات ها به صورت asynchronously و خارج از زمان پیکِ ترافیکی انجام می شود.

مزایا:

  • کاهش بار سرویس ها هنگام انجام دیگر عملیات های real time
  • بهبود بهره وری کلی سیستم


مدل Local Copy Caching

اغلب اوقات، فراخوانی های مکرر بین سرویس ها می تواند با مکانیزم caching و یا نگهداری کپی محلی، جایگزین شود. برای مثال سرویس Product به جای این که هر بار جزئیات محصول را از سرویس Catalog فراخوانی کند، میتواند جزئیات محصولات پر بازدید را cache کند.

Caching Local Copy
Caching Local Copy

نحوه عملکرد:

  • استفاده از راهکارهای caching مانند Redis یا Memcached برای داده هایی که فراخوانی بیشتری دارند
  • مکانیزمی برای انتقال state بین سرویس ها ایجاد می شود تا از فراخوانی های مکرر جلوگیری شود. همچنین اگر داده اصلی تغییر پیدا کرد کپی محلی قبلی معتبر نبوده و کافیست با فراخوانی داده جدید، کپی محلی به روز رسانی شود.

مزایا:

  • کاهش تاخیر دسترسی به داده به دلیل نگهداری کپی محلی
  • هنگامی که به طور مثال سرویس Catalog از دسترس خارج شود، کپی محلی که در سرویس Product کَش شده است می تواند بازگردانده شود و مدت کوتاهی برای بازیابی سرویس Catalog زمان بخرد.


سخن پایانی - نکات مهم برای به کارگیری راهکارهای معرفی شده :

  • دقت در استفاده و در نظر گرفتن شرایط : در همه سناریوها ارتباط async معنی ندارد. چنان که در همه سناریوها ارتباط sync معنی پیدا نمی کند. با تحلیل trade-off ها روشی که با نیازمندی های شما همخوانی دارد را استفاده کنید.
  • مانیتورینگ و دیباگ : از ابزارهای distributed tracing مانند Jaeger یا Zipkin برای مانیتور کردن جریان های async و عیب یابی مشکلات استفاده کنید.
  • تنزل دادن سرویس ها به آرامی : هنگام رخداد خرابی در سیستم، مکانیزم های fallback و retry را به صورت graceful و به آرامی اجرا کنید.
  • در نظر گرفتن Idempotency : مطمئن شوید که عملیات های مورد نظر شما به صورت Idempotent اجرا می شوند و با پیام های duplicate به صورت صحیح برخورد می شود تا یک عملیات که تنها یک بار لازم است اجرا شود، دو بار و بیشتر اجرا نشود.


این هم از مطلب امروز در مورد راهکارهای مختلف ارتباطی بین میکروسرویس ها!

امیدوارم براتون مفید بوده باشه😇


مهندسی نرم افزارسیستم دیزاینبرنامه نویسیbackendsoftware engineering
توسعه دهنده نرم افزار، مشتاق یادگیری و ماجراجو ;)
شاید از این پست‌ها خوشتان بیاید