الگوی طراحی Saga راهی برای مدیریت یکپارچگی دادهها (data consistency) در میان میکروسرویسها در سناریوهای تراکنش توزیع شده (distributed transaction) است. Saga در واقع دنبالهای از تراکنشها است که سرویسها را بهروزرسانی میکند و پیام یا رخدادی را برای راهاندازی تراکنش بعدی منتشر میکند. اگر مرحلهای با شکست مواجه شود، Saga تراکنشهای جبرانی را اجرا میکند که تراکنشهای قبلی را بیاثر میکند.
تراکنش یک واحد منفرد از منطق یا کار موردنظر است که گاهی از چندین عملیات تشکیل شده است. در یک تراکنش، یک رویداد یک تغییر حالت است که برای یک موجودیت اتفاق میافتد و یک فرمان تمام اطلاعات موردنیاز برای انجام یک عملیات یا راهاندازی یک رویداد مربوط به حالت بعدی را در بر میگیرد.
تراکنش باید atomic، یکپارچه(consistent)، ایزوله(isolated) و بادوام(durable) یا بهاختصار (ACID) باشد. تراکنشهای درون یک سرویس از نوع ACID هستند، اما یکپارچگی دادههای بین سرویسهای مختلف به یک راهبُرد مدیریت تراکنشهای در بین این سرویسها نیاز دارد.
در معماریهای چند سرویس(multiservices):
* Atomicity مجموعهای تقسیمناپذیر و تقلیلناپذیر از عملیات است که همگی باید اتفاق بیافتند یا هیچکدام رخ ندهد.
* یکپارچگی(Consistency) به این معنی است که تراکنش دادهها را فقط از یک حالت معتبر به حالت معتبر دیگر میآورد.
* ایزولهسازی(Isolation) تضمین میکند که تراکنشهای موازی همان حالت دادهای را ایجاد میکنند که تراکنشهایی متوالی اجرا و ایجاد میکنند.
* دوام(Durability) تضمین میکند که تراکنشهای واقعی حتی در صورت خرابی سیستم یا قطع برق ثابت باقی میمانند.
یک مدل پایگاهداده بهازای هر میکروسرویس ( database-per-microservice) مزایای بسیاری را برای معماری میکروسرویسها فراهم میکند. کپسولهکردن دادهها در هر حوزه خاص، به هر سرویس اجازه میدهد تا از بهترین نوع ذخیره داده و طراحی استفاده کند و ذخیره داده خود را در صورت لزوم مقیاسدهی یا scale کند و از تأثیرگذاری خرابی سایر سرویسهای دیگر محفوظ مانده شود. بااینحال، اطمینان از یکپارچگی دادهها در دیتابیسهای سرویسهای خاص، چالشهایی را ایجاد میکند.
تراکنشهای توزیعشده مانند پروتکلtwo-phase commit (2PC) به همه عوامل مؤثر در یک تراکنش نیاز دارد تا ادامه انجام تراکنش جدید یا برگشت (roll back) به تراکنش قبلی را به شیوه مناسب انجام دهد. بااینحال، برخی از پیادهسازیهای خاص مانند استفاده از پایگاههای داده NoSQL و message brokerها از این مدل پشتیبانی نمیکنند.
یکی دیگر از محدودیتهای تراکنش توزیع شده، هماهنگی(synchronicity) و دردسترسبودن (availability) ارتباطات بین فرایندی interprocess communication (IPC) است. IPC ارائه شده توسط سیستم عامل به فرایندهای جداگانه این اجازه را میدهد تا دادهها را به اشتراک بگذارند. برای انجام تراکنشهای توزیعشده، همه سرویسهای مشارکتکننده و مؤثر باید در دسترس باشند که این مورد به طور بالقوه system availability را کاهش میدهد. پیادهسازیهای این معماری با IPC به همراه وجود محدودیت در تراکنشها، کاندیدای مناسبی برای الگوی Saga میباشد.
الگوی Saga مدیریت تراکنش را با استفاده از دنبالهای از تراکنشهای محلی(local transactions) فراهم میکند. local transactions، تلاشی atomic است که توسط یک مشارکتکننده در الگوی Saga انجام میشود. هر تراکنش محلی پایگاهداده را بهروزرسانی میکند و پیام یا رویدادی را برای راهاندازی تراکنش محلی بعدی در Saga منتشر میکند. اگر یک تراکنش محلی با شکست مواجه شود، Saga یک سری تراکنشهای جبرانی را اجرا میکند که تغییرات ایجاد شده توسط تراکنشهای محلی قبلی را خنثی میکند.
در الگوهای Saga :
* تراکنشهای قابلجبران (Compensable transactions)، تراکنشهایی هستند که به طور بالقوه میتوانند با تراکنش دیگری با عملکرد معکوس، خنثی شوند.
* تراکنش محوری (pivot transaction) یک نوع نقطه قابل/غیر قابل ادامه در یک Saga است. اگر pivot transaction فعال شود، Saga تا لحظه نهایی تکمیلشدن آن تراکنش در حال اجرا است. pivot transaction میتواند تراکنشی باشد که نه قابلجبران است و نه قابل اجرای مجدد یا میتواند آخرین تراکنش قابلجبران یا اولین تراکنش قابل اجرای مجدد در Saga باشد.
* تراکنشهای قابلتکرار(Retryable transactions)، تراکنشهایی هستند که از تراکنش محوری پیروی میکنند و موفقیت انجام آنها تضمین میشود.
دو رویکرد رایج اجرای Saga وجود دارد، choreography و orchestration. هر رویکرد مجموعهای از چالشها و فناوریهای خاص خود را برای هماهنگکردن workflow دارد.
Choreography برای هماهنگکردن Saga هاست که در آن مشارکتکنندگان در رویدادها(participants exchange) را بدون یک نقطه کنترل متمرکز مبادله میکنند. با choreography، هر تراکنش محلی(local transaction، رویدادهایی را منتشر میکند که تراکنشهای محلی را در سایر سرویسهای دیگر تحریک میکند.
* برای گردش کارهای ساده که به شرکتکنندگان کمی نیاز دارند و به منطق هماهنگکننده خاصی نیاز ندارند، این الگو مناسب و خوب است.
* نیازی به اجرای سرویس و نگهداری اضافی ندارد.
* یک نقطه شکست را معرفی نمیکند، زیرا مسئولیتها بین مشارکتکنندگان Saga توزیع میشود.
* هنگام اضافهکردن مراحل جدید، ممکن است Workflowها کمی گیجکننده شود، زیرا ردیابی اینکه کدام شرکتکنندگان Saga به کدام دستورها گوش میدهند دشوار است.
* خطر وابستگی چرخهای(cyclic dependency) بین شرکتکنندگان Saga وجود دارد؛ زیرا آنها باید دستورات یکدیگر را مورداستفاده قرار دهند.
* تست یکپارچهسازی (Integration testing) دشوار است؛ زیرا همه سرویسها باید برای در حال شبیهسازی یک تراکنش در حال اجرا باشند.
Orchestration راهی برای هماهنگکردن الگو Saga است که در آن یک کنترل کننده متمرکز به مشارکتکنندگان Saga میگوید که چه تراکنشهای محلی را باید اجرا کنند. saga orchestrator تمام تراکنشها را مدیریت میکند و به شرکتکنندگان میگوید که کدام عملیات را بر اساس رویدادها انجام دهند. orchestrator درخواستهای Saga را اجرا میکند، حالات هر تسک را ذخیره و تفسیر میکند و بازیابی شکست را با تراکنشهای جبرانکننده مدیریت میکند.
* این روش برای گردشهای کاری(workflows) پیچیده که شامل بسیاری از مشارکتکنندگان یا مشارکتکنندگان جدیدی است که بهمرورزمان اضافه میشوند، مناسب است.
* زمانی خوب است که بر هر شرکتکننده در هر فرایند مکانیزم کنترل وجود داشته باشد و بر جریان فعالیتها کنترل داشته باشد.
* وابستگیهای چرخهای(cyclical dependencies) را معرفی نمیکند، زیرا orchestrator به طور یکجانبه به شرکتکنندگان Saga وابسته است.
* مشارکتکنندگان Saga نیازی به دانستن دستورات سایر مشارکتکنندگان ندارند. تفکیک واضح نگرانیها(separation of concerns) در واقع business logic برنامه را ساده میکند.
* پیچیدگی طراحی اضافی مستلزم اجرای یک منطق هماهنگکننده است.
* یک نقطه شکست اضافی وجود دارد، زیرا orchestrator گردش کار کامل را مدیریت میکند.
* الگوی Saga ممکن است در ابتدا چالشبرانگیز باشد، زیرا نیاز به روش جدیدی از تفکر در مورد نحوه هماهنگکردن یک تراکنش و حفظ ثبات دادهها برای یک فرایند که چندین میکروسرویس را در بر میگیرد، دارد.
* اشکالزدایی الگوی Saga معمولاً سخت است و با افزایش مشارکتکنندگان، پیچیدگی آن افزایش مییابد.
* دادهها را نمیتوان به عقب بازگرداند یا بهنوعی role back کرد، زیرا شرکتکنندگان Saga تغییراتی را در پایگاهداده محلی خود انجام میدهند.
* پیادهسازی باید بتواند مجموعهای از خرابیهای گذرا را مدیریت کند و برای کاهش اثرات جانبی و اطمینان از ثبات دادهها(data consistency) حالت Idempotence را ایجاد کند. Idempotence به این معنی است که یک عمل را میتوان چندین بار بدون تغییر در نتیجه اولیه تکرار کرد. برای اطلاعات بیشتر، به راهنمای اطمینان از idempotence در هنگام پردازش پیامها و بهروزرسانی وضعیتها مراجعه کنید.
* بسیار مناسب است که با استفاده از خاصیت observability برای مانیتورکردن و ردیابی گردش کارهای Saga استفاده کنید.
* فقدان جداسازی دادهای مشارکتکنندهها، چالش پایداری دادهها را تحمیل میکند. اجرای Saga باید شامل اقدامات متقابل برای کاهش ناهنجاریهای مربوط به این مورد باشد.
ناهنجاریهای زیر میتوانند بدون پیشبینیهای در نظر گرفته شده رخ دهند:
* بهروزرسانیهای ازدسترفته(Lost updates)، زمانی که یک Saga بدون خواندن تغییرات ایجاد شده توسط Saga دیگر دادههای خود را مینویسد.
* خواندن کثیف(Dirty reads)، زمانی که یک تراکنش یا یک Saga بهروزرسانیهای ساخته شده توسط Saga ای را میخواند که هنوز آن بهروزرسانیها را تکمیل نکرده است.
* خواندن فازی/غیر تکرارنشدنی(Fuzzy/nonrepeatable reads,)، زمانی که مراحل مختلف Saga دادههای متفاوتی را میخوانند، زیرا بهروزرسانی دادهها بین موارد خوانده شده رخ میدهد.
اقدامات متقابل پیشنهادی برای کاهش یا پیشگیری از ناهنجاریها عبارتاند از:
* قفل معنایی(Semantic lock)، یک قفل در سطح برنامه که در آن تراکنش قابلجبران Saga از یک سمافور برای نشاندادن بهروزرسانی در حال انجام استفاده میکند.
* بهروزرسانیهای جابهجاییپذیر(Commutative updates) که میتوانند به هر ترتیبی اجرا شوند و نتیجه یکسانی را ایجاد کنند.
* دیدگاه بدبینانه(Pessimistic view): ممکن است یک Saga دادههای کثیف را بخواند، درحالیکه Saga دیگری در حال اجرای تراکنش قابلجبران برای عقب انداختن عملیات است. دیدگاه بدبینانه (Pessimistic view) دوباره ترتیب و نظمدهی Saga را انجام میدهد، بنابراین دادهها در یک تراکنش قابلتجدید (retryable) بهروزرسانی میشوند که امکان خواندن کثیف (dirty read) را از بین میبرد.
* مقدار بازخوانی تأیید میکند که دادهها بدون تغییر هستند و سپس رکورد را بهروزرسانی میکند. اگر رکورد تغییر کرده باشد، مراحل لغو میشود و Saga ممکن است دوباره راهاندازی شود.
* یک نسخه فایل (version file)، عملیات را در یک رکورد بهمحض رسیدن آنها ثبت میکند و سپس آنها را به ترتیب صحیح اجرا میکند.
* بر اساس ارزش و خطرپذیری تجاری مربوط به هر درخواست جهت حالت دینامیک و پویا، معمولاً از مکانیزمهای همزمانی (concurrency) استفاده میکند. درخواستهای کمخطر به نفع Sagaها هستند، درحالیکه درخواستهای با ریسک بالا به نفع تراکنشهای توزیع شده هستند.
در صورت نیاز از الگوی Saga استفاده کنید:
* از ثبات دادهها(data consistency) در یک سیستم توزیع شده بدون اتصال بالا(tight coupling) اطمینان حاصل کنید.
* اگر یکی از عملیات بهصورت زنجیرهای شکست خورد، بهتر است بازگشت (Roll back) یا جبران کنید.
الگوی Saga کمتر مناسب برای موارد زیر است:
* تراکنشهای بههمپیوسته (Tightly coupled)
* تراکنشهای جبرانی (Compensating transactions) که در مشارکتکنندگان قبلی رخ میدهد.
* وابستگیهای چرخهای (Cyclic dependencies)
Orchestration-based Saga on Serverless یک مرجع پیادهسازی Saga با استفاده از رویکرد orchestration است که سناریوی انتقال پول را با گردشهای کاری موفق و ناموفق شبیهسازی میکند.
- Distributed data
- Richardson, Chris. 2018: Microservices Patterns. Manning Publications.
الگوهای زیر نیز ممکن است هنگام اجرای این الگو مفید باشند:
* Choreography بهجای تکیه بر یک نقطه کنترل مرکزی، هر یک از اجزای سیستم را در فرایند تصمیمگیری (decision-making) درباره جریان کاری (workflow) یک تراکنش تجاری را شرکت میدهد.
* تراکنشهای جبرانی(Compensating transactions) کار انجام شده توسط یک سری مراحل را خنثی میکند و در نهایت در صورت شکست یک یا چند مرحله یک عملیات ثابت را تعریف میکند. برنامههای میزبانی شده در محیط ابری که فرایندها و گردشهای کاری پیچیده را پیادهسازی میکنند، اغلب از این مدل یکپارچگی تدریجی(eventual consistency) پیروی میکنند.
* الگوی Retry به یک برنامه اجازه میدهد تا هنگام تلاش برای اتصال به یک سرویس یا شبکه، با تلاش مجدد(retry) کردن روی عملیات ناموفق، خرابیهای گذرا را مدیریت کند. retry میتواند پایداری برنامه را بهبود بخشد.
* قطعکننده مدار (Circuit breaker) خطاهایی را که در هنگام اتصال به یک سرویس یا منبع remote بازیابی میشود، به زمان متغیری نیاز دارد. Circuit breaker میتواند پایداری و انعطافپذیری یک application را بهبود بخشد.
* Health endpoint monitoring، این مورد بررسیهای عملکردی را در برنامهای اجرا میکند که ابزارهای خارجی میتوانند از طریق endpointها و در فواصل زمانی منظم به آن دسترسی داشته باشند. مانیتور کردن Health endpoint میتواند به تأیید درستی عملکرد برنامهها و سرویسها کمک کند.