مبانی معماری نرم افزار - بخش چهارم

در بخش سوم، به بررسی faultها پرداختیم و عنوان کردیم که faultها بر روی خیلی از quality attributeها تاثیر گذار هستند.در این بخش، مساله resiliency و سیستم های resilient را بیشتر مورد بحث قرار می دهیم.در چند سال اخیر، مفاهیمی مثل resiliency در ادبیات طراحان نسبت به گذشته بیشتر شده است و دلیل آن، تغییر الگو در طراحی می باشد که حاصل بزرگ شدن مقیاس نرم افزارها است. ساختار نرم افزارها در گذشته عمدتا بصورت یک یا چند ساختار monolithic بود و در غالب یک instance به آن ها نگاه می شد. با فراگیر شدن cloud computing، مقیاس پروژه ها شروع به بزرگ شدن کرد و در حوزه resiliency چندین بحث مطرح شد.اولین موضوع این بود که، مباحث high-availability در فضای monolithic و on premise بودند به این معنا که سرورها در اختیار شرکت ها بود و معمولا رویکرد به سمت تکنولوژی های high-availability و در سطح زیر ساخت کار می کرد. برای مثال replicate کردن دیتابیس یا application server. با Cloud-Ready شدن applicationها، طراح ها به سمت معماری های distributed سوق داده شدند.در فضای جدیدی که مطرح شد، بحث های جدیدی در مورد high-availability شکل گرفت.اولین موضوع این بود. زمانی که ما به سمت Cloud-Ready شدن رفتیم، موضوعاتی مثل high-availability در داخل architecture و design نرم افزار قرار گرفت. با این هدف که اگر یکی از سرویس ها از مدار خارج شود، نرم افزار side-effect آن را کم کند و حتی برای بازگرداندن آن به مدار تلاش کند. بنابراین رویکرد نرم افزار در این سال ها، نگاه دیگری به high-availability داشته است و این موضوع effort را برای طراحان بیشتر کرده است. در حوزه resiliency، درک درست از Business Context یک مساله مهم است. به این دلیل که failure را به عنوان محرک resiliency می شناسیم و زمانی که اتفاق می افتد باید در context یک کسب و کار به آن رسیدگی شود. به معدل زمان بین failureها، اصطلاحا Mean time between failures گفته می شود و به معدل زمان بعد از failure که سیستم برای recover شدن نیاز دارد، Mean time to recovery گفته می شود.در حوزه resiliency، با فرض اتفاق افتادن failure، هدف کاهش time to recovery و یا کاهش side effect آن است.برای پاسخ دادن به failure، باید آن را شناسایی کنیم. در این رابطه دو موضوع Detection و Recognize وجود دارد که بار معنای آن ها متفاوت است. زمانی که failure اتفاق می افتد، ابتدا باید recognize کنیم و ماهیت آن را تعریف کنیم. به این معنا که failure کجا و به چه شکل اتفاق می افتد. و detection در مرحله بعد اتفاق می افتد. برای مثال در مکانیزم Circuit Breaker، ابتدا failure را پیش بینی می کنیم و بعد با پیاده سازی این مکانیزم، response مناسب آن failure را نشان می دهیم.یکی از feed back loopهای مهم در طراحی سیستم های resilient، شناسایی failureهای جدید است. دسته ای دیگر از responseها وجود دارند که ایده کلی آن ها محدود کردن تاثیر failureهایی است که اتفاق می افتند. برای مثال بخشی از سیستم کار نکند یا با مشخصه کیفی پایین تری کار کند. در فضای Isolate کردن مساله های sync و async، به طور کلی Isolate کردن رفتارهای sync نسبت به رفتارهای async سخت تر است. برای مثال در حالت sync، کامپوننت A یک response به کامپوننت B می زند و منتظر پاسخ می ماند. این دو کامپوننت دارای runtime coupling هستند و اگر یکی از کامپوننت ها دچار مشکل شود، ارتباط هم دچار مشکل می شود. اما در حالت Async نیاز به runtime coupling نیست.در ارتباطات Async، ممکن است موضوع بروز نبودن داده ها مطرح شود. اگر حالت Normal Operation را در نظر بگیریم به این معنا که مشکلی وجود نداشته باشد، در حالت Async، با فرض اینکه تغییرات از کامپوننت B به کامپوننت A در قالب message اطلاع رسانی می شود، یک گپ زمانی وجود خواهد داشت. اما این گپ زمانی در حالت sync نیز وجود دارد. یعنی زمانی که کامپوننت A به کامپوننت B درخواست ارسال می کند، ممکن است در این فاصله داده ای تغییر کند. مساله زمانی است که normal operation نباشد و به واسطه failure، رابطه دچار اختلال می شود. بنابراین در این environment، در حالت async داده بروزی نداریم و در حالت sync به طور کلی داده ای نداریم. بنابراین در sync، اگر داده ای نداشته باشیم عملیاتی هم انجام نمی دهیم و محاسبه اشتباهی نیز انجام نمی شود. اما در async، چون داده داریم و داده ها بروز نیستند، بنابراین ممکن است محاسبات اشتباهی انجام شود. این مساله کاملا به context شما بستگی دارد. ممکن است بروز بودن داده ها برای شما اهمیتی نداشته باشد.رفتار async نسبت به رفتار sync به طور کلی رفتار ایزوله تری است.استفاده از راه حل هایی مثل Cache کردن یا رفتارهای پیش فرض، می توانند ما را ایزوله کنند و تاثیر قطع شدن سرویس ها را کم کنند.دسته ای از مساله ها وجود دارند که در آن ها از طریق Protect، الزاما دنبال رفع مشکل نیستیم و صرفا می خواهیم که مشکل تشدید نشود. در Timeout به عنوان یکی از روش های protect کردن، برای مثال کامپوننت A، کامپوننت B را صدا می زند و B جوابی برنمی گرداند. timeout فرضا بیست ثانیه منتظر می ماند و بیشتر از آن منتظر نمی ماند و خطا می دهد.در روش Circuit Breaker به عنوان یکی دیگر از روش های protect کردن، ارتباط بین دو سرویس A و B را در نظر بگیرید که سرویس B یا قطع می شود و یا دیر جواب می دهد و به failure تبدیل می شود. بنابراین زمانی که policy تعیین شده در circuit breaker نقض شود، با قطع کردن جریان از تشدید شدن اختلال جلوگیری می کند. مکانیزم circuit breaker بر روی یک state machine تشکیل شده از Closed, Open, Half-Open می باشد. closed به معنی وصل بودن ارتباط است. open به معنی قطع بودن ارتباط است و half-open زمانی است که ما در حالت open هستیم و دنبال چک کردن وضعیت جدید ارتباط هستیم.