انعطاف پذیری در نرم افزار به توانایی یک برنامه کاربردی برای ادامه عملکرد روان و قابل اعتماد، حتی در مواجهه با مشکلات یا شکست های غیرمنتظره اشاره دارد. در پروژههای فینتک، تابآوری به دلایل مختلفی از اهمیت ویژهای برخوردار است. اولاً، شرکت ها موظف به رعایت الزامات نظارتی هستند و تنظیم کننده های مالی بر انعطاف پذیری عملیاتی برای حفظ ثبات در سیستم تأکید می کنند. علاوه بر این، گسترش ابزارهای دیجیتال و اتکا به ارائه دهندگان خدمات شخص ثالث، مشاغل فین تک را در معرض تهدیدات امنیتی شدید قرار می دهد. انعطافپذیری همچنین به کاهش خطرات قطعی ناشی از عوامل مختلف مانند تهدیدات سایبری، بیماریهای همهگیر یا رویدادهای ژئوپلیتیکی، حفاظت از عملیات اصلی کسبوکار و داراییهای حیاتی کمک میکند.
با الگوهای انعطافپذیری، مجموعهای از بهترین شیوهها و استراتژیها را درک میکنیم که برای اطمینان از اینکه نرمافزار میتواند در برابر اختلالات مقاومت کند و عملکرد خود را حفظ کند، طراحی شدهاند. این الگوها مانند شبکههای ایمنی عمل میکنند و مکانیسمهایی را برای رسیدگی به خطاها، مدیریت بار و بازیابی خرابیها فراهم میکنند و در نتیجه تضمین میکنند که برنامهها در شرایط نامطلوب قوی و قابل اعتماد باقی میمانند.
رایجترین استراتژیهای انعطافپذیری عبارتند از bulkhead، cache، backback، retrie و قطع کننده مدار. در این مقاله، من آنها را با جزئیات بیشتر با مثال هایی از مشکلاتی که می توانند به حل آنها کمک کنند، بحث خواهم کرد.
بلوک
اجازه دهید نگاهی به تنظیمات بالا بیاندازیم. ما یک برنامه بسیار معمولی داریم که چندین بکاند پشت سرمان است تا بتوانیم اطلاعاتی را از آن دریافت کنیم. چندین کلاینت HTTP به این backendها متصل هستند. معلوم می شود که همه آنها یک استخر اتصال مشترک دارند! و همچنین منابع دیگری مانند CPU و RAM.
چه اتفاقی میافتد، اگر یکی از پشتیبانها با نوعی مشکل مواجه شود که منجر به تأخیر درخواست بالا میشود؟ به دلیل زمان پاسخ دهی بالا، کل مخزن اتصال به طور کامل توسط درخواست هایی که منتظر پاسخ از backend1 هستند اشغال می شود. در نتیجه، درخواست های در نظر گرفته شده برای backend2 و backend3 سالم نمی توانند ادامه پیدا کنند زیرا استخر تمام شده است. این بدان معناست که خرابی در یکی از باطنهای ما میتواند باعث خرابی کل برنامه شود. در حالت ایدهآل، ما میخواهیم فقط عملکرد مرتبط با بکاند خراب را تجربه کند، در حالی که بقیه برنامهها به طور عادی به کار خود ادامه میدهند.
الگوی بالکد چیست؟
اصطلاح الگوی Bulkhead از کشتی سازی مشتق شده است و شامل ایجاد چندین محفظه جدا شده در یک کشتی است. اگر نشتی در یک محفظه رخ دهد، پر از آب می شود، اما محفظه های دیگر بدون تاثیر باقی می مانند. این ایزوله از غرق شدن کل کشتی به دلیل یک شکست جلوگیری می کند.
چگونه می توانیم از الگوی حفره برای رفع این مشکل استفاده کنیم؟
الگوی Bulkhead را می توان برای جداسازی انواع مختلف منابع در یک برنامه مورد استفاده قرار داد و مانع از تأثیر خرابی یک قسمت بر کل سیستم می شود. در اینجا نحوه اعمال آن برای مشکل خود آمده است:
- جداسازی استخرهای اتصال ما میتوانیم استخرهای اتصال جداگانه برای هر باطن ایجاد کنیم (backend1, backend2, backend3). این تضمین میکند که اگر backend1 زمانهای پاسخ یا خرابی بالایی را تجربه کند، مخزن اتصال آن بهطور مستقل تمام میشود و مخزنهای اتصال برای backend2 و backend3 بیتأثیر باقی میماند. این جداسازی به پشتیبانهای سالم اجازه میدهد تا به پردازش درخواستها به طور عادی ادامه دهند.
- محدود کردن منابع برای فعالیتهای پسزمینه با استفاده از Bulkheads، میتوانیم منابع خاصی را برای فعالیتهای پسزمینه، مانند پردازش دستهای یا وظایف برنامهریزیشده، اختصاص دهیم. این امر از مصرف منابع مورد نیاز برای عملیات بلادرنگ توسط این فعالیت ها جلوگیری می کند. به عنوان مثال، میتوانیم تعداد رشتهها یا استفاده از CPU را که به وظایف پسزمینه اختصاص داده شده است، محدود کنیم و اطمینان حاصل کنیم که منابع کافی برای رسیدگی به درخواستهای دریافتی در دسترس باقی میمانند.
- تنظیم محدودیتها در درخواستهای ورودی همچنین میتوان برای محدود کردن تعداد درخواستهای دریافتی به بخشهای مختلف برنامه، حجرهها را اعمال کرد. به عنوان مثال، ما میتوانیم حداکثر محدودیتی را برای تعداد درخواستهایی که میتوان به طور همزمان برای هر سرویس بالادستی پردازش کرد، تعیین کرد. این مانع از تحت تأثیر قرار دادن هر باطن منفردی بر سیستم میشود و تضمین میکند که سایر بکاندها حتی اگر تحت بار سنگین قرار دارند میتوانند به کار خود ادامه دهند.
درد
بیایید فرض کنیم سیستمهای باطن ما به صورت جداگانه با خطا مواجه میشوند. با این حال، زمانی که یک عملیات شامل پرس و جو از همه این backendها به صورت موازی باشد، هر کدام به طور مستقل می توانند یک خطا را برگردانند. از آنجا که این خطاها به طور مستقل رخ می دهند، احتمال کلی خطا در برنامه ما بیشتر از احتمال خطای هر باطن منفرد است. احتمال خطای تجمعی را می توان با استفاده از فرمول P_total=1−(1−p)^n محاسبه کرد، که در آن n تعداد سیستم های پشتیبان است.
به عنوان مثال، اگر ده پشتیبان داشته باشیم که هر کدام با احتمال خطای p=0.001 (مطابق با SLA 99.9%)، احتمال خطای حاصل به صورت زیر است:
P_total=1−(1−0.001)^10=0.009955
این به این معنی است که SLA ترکیبی ما تقریباً به 99% کاهش مییابد که نشان میدهد چگونه قابلیت اطمینان کلی در هنگام پرسوجویی از چندین پشتیبان به صورت موازی کاهش مییابد. برای کاهش این مشکل، میتوانیم یک کش در حافظه پیادهسازی کنیم.
چگونه می توانیم آن را با کش درون حافظه حل کنیم
یک کش در حافظه به عنوان یک بافر داده با سرعت بالا عمل می کند، داده هایی را که به طور مکرر در دسترس هستند ذخیره می کند و نیاز به واکشی هر بار از منابع بالقوه کند را از بین می برد. از آنجایی که کش های ذخیره شده در حافظه در مقایسه با واکشی داده ها از طریق شبکه 0% احتمال خطا دارند، قابلیت اطمینان برنامه ما را به میزان قابل توجهی افزایش می دهند. علاوه بر این، کش ترافیک شبکه را کاهش می دهد و احتمال خطا را بیشتر کاهش می دهد. در نتیجه، با استفاده از یک کش در حافظه، میتوانیم به نرخ خطای کمتری در برنامه خود در مقایسه با سیستمهای باطن خود دست یابیم. علاوه بر این، کش های درون حافظه، بازیابی اطلاعات سریع تری را نسبت به واکشی مبتنی بر شبکه ارائه می دهند و در نتیجه تأخیر برنامه را کاهش می دهند - یک مزیت قابل توجه.
کش درون حافظه: کش های شخصی سازی شده
برای دادههای شخصیسازیشده، مانند پروفایلهای کاربر یا توصیهها، استفاده از کشهای درون حافظه نیز میتواند بسیار مؤثر باشد. اما ما باید اطمینان حاصل کنیم که همه درخواستهای یک کاربر به طور مداوم به یک نمونه برنامه میروند تا از دادههای ذخیرهشده در حافظه پنهان برای آنها استفاده شود، که به جلسات چسبنده نیاز دارد. اجرای جلسات چسبنده می تواند چالش برانگیز باشد، اما برای این سناریو، ما به مکانیزم های پیچیده نیاز نداریم. تعادل مجدد ترافیک جزئی قابل قبول است، بنابراین یک الگوریتم متعادل کننده بار پایدار مانند هش ثابت کافی است.
علاوه بر این، در صورت خرابی گره، هش کردن مداوم تضمین می کند که فقط کاربران مرتبط با گره شکست خورده تحت تعادل مجدد قرار می گیرند و اختلال در سیستم را به حداقل می رساند. این رویکرد مدیریت کش های شخصی سازی شده را ساده می کند و ثبات و عملکرد کلی برنامه ما را افزایش می دهد.
کش در حافظه: تکثیر داده های محلی
اگر دادههایی که میخواهیم حافظه پنهان کنیم، حیاتی هستند و در هر درخواستی که سیستم ما رسیدگی میکند، استفاده میشود، مانند خطمشیهای دسترسی، طرحهای اشتراک، یا سایر موجودیتهای حیاتی در دامنه ما، منبع این دادهها میتواند نقطه ضعف قابلتوجهی در سیستم ما باشد. برای مقابله با این چالش، یک رویکرد این است که این داده ها را به طور کامل به طور مستقیم در حافظه برنامه خود تکرار کنیم.
در این سناریو، اگر حجم داده در منبع قابل مدیریت باشد، میتوانیم با دانلود یک عکس فوری از این دادهها در ابتدای برنامه، فرآیند را آغاز کنیم. متعاقباً، میتوانیم رویدادهای بهروزرسانی را دریافت کنیم تا اطمینان حاصل کنیم که دادههای ذخیرهشده در حافظه پنهان با منبع همگام میمانند. با اتخاذ این روش، ما قابلیت اطمینان دسترسی به این دادههای مهم را افزایش میدهیم، زیرا هر بازیابی مستقیماً از حافظه با احتمال خطا 0٪ اتفاق میافتد. علاوه بر این، بازیابی داده ها از حافظه بسیار سریع است و در نتیجه عملکرد برنامه ما را بهینه می کند. این استراتژی به طور موثر خطر مرتبط با تکیه بر یک منبع داده خارجی را کاهش می دهد و از دسترسی مداوم و قابل اعتماد به اطلاعات حیاتی برای عملکرد برنامه ما اطمینان می دهد.
پیکربندی قابل بارگیری مجدد
با این حال، نیاز به دانلود دادهها در هنگام راهاندازی برنامه، در نتیجه فرآیند راهاندازی را به تأخیر میاندازد، یکی از اصول «برنامه ۱۲ عاملی» را نقض میکند که از راهاندازی سریع برنامه حمایت میکند. اما، ما نمی خواهیم مزایای استفاده از کش را از دست بدهیم. برای رفع این معضل، اجازه دهید راه حل های بالقوه را بررسی کنیم.
راه اندازی سریع بسیار مهم است، به ویژه برای پلتفرم هایی مانند Kubernetes، که بر مهاجرت سریع برنامه به گره های فیزیکی مختلف متکی هستند. خوشبختانه، Kubernetes میتواند برنامههای با شروع آهسته را با استفاده از ویژگیهایی مانند پروبهای راهاندازی مدیریت کند.
چالش دیگری که ممکن است با آن روبرو شویم، بهروزرسانی تنظیمات در حین اجرای برنامه است. اغلب، تنظیم زمانهای کش یا زمانبندی درخواست برای حل مشکلات تولید ضروری است. حتی اگر بتوانیم به سرعت فایل های پیکربندی به روز شده را در برنامه خود مستقر کنیم، اعمال این تغییرات معمولاً مستلزم راه اندازی مجدد است. با طولانی شدن زمان راهاندازی هر برنامه، یک راهاندازی مجدد متوالی میتواند بهطور قابلتوجهی استقرار اصلاحات را برای کاربران ما به تاخیر بیندازد.
برای مقابله با این، یک راه حل این است که پیکربندی ها را در یک متغیر همزمان ذخیره کنید و یک رشته پس زمینه به طور دوره ای آن را به روز کنید. با این حال، برخی از پارامترها، مانند مهلت زمانی درخواست HTTP، ممکن است نیاز به شروع مجدد HTTP یا سرویس گیرندگان پایگاه داده زمانی که پیکربندی مربوطه تغییر می کند، یک چالش بالقوه است. با این حال، برخی از کلاینتها، مانند درایور Cassandra برای جاوا، از بارگیری مجدد خودکار پیکربندیها پشتیبانی میکنند و این فرآیند را سادهتر میکنند.
پیادهسازی پیکربندیهای قابل بارگیری مجدد میتواند تأثیر منفی زمانهای راهاندازی طولانی برنامه را کاهش دهد و مزایای بیشتری مانند تسهیل اجرای پرچمهای ویژگی ارائه دهد. این رویکرد ما را قادر می سازد تا قابلیت اطمینان و پاسخگویی برنامه را حفظ کنیم و در عین حال به روز رسانی های پیکربندی را به طور موثر مدیریت کنیم.
بازگشت به عقب
حال اجازه دهید نگاهی به مشکل دیگری بیندازیم: در سیستم ما، هنگامی که یک درخواست کاربر با ارسال یک درخواست به پشتیبان یا پایگاه داده پردازش میشود، گاهی اوقات به جای دادههای مورد انتظار، یک پاسخ خطا دریافت میشود. متعاقباً، سیستم ما با یک خطا به کاربر پاسخ می دهد.
با این حال، در بسیاری از سناریوها، نمایش دادههای کمی قدیمی همراه با پیامی که نشان میدهد تاخیر در بازخوانی دادهها وجود دارد، ترجیح داده میشود، نه اینکه کاربر با یک پیام خطای قرمز بزرگ نمایش داده شود.
برای رفع این مشکل و بهبود رفتار سیستم خود، میتوانیم الگوی Fallback را پیادهسازی کنیم. مفهوم پشت این الگو شامل داشتن یک منبع داده ثانویه است که ممکن است حاوی داده هایی با کیفیت یا تازگی کمتر در مقایسه با منبع اولیه باشد. اگر منبع داده اولیه در دسترس نباشد یا خطایی را برگرداند، سیستم میتواند به بازیابی دادهها از این منبع ثانویه بازگردد و اطمینان حاصل کند که به جای نمایش پیام خطا، نوعی از اطلاعات به کاربر ارائه میشود.
دوباره امتحان کنید
اگر به تصویر بالا نگاه کنید، متوجه شباهت بین مشکلی که اکنون با آن روبرو هستیم و مشکلی که در مثال کش با آن مواجه شده ایم، خواهید شد.
برای حل آن، میتوانیم الگویی به نام تلاش مجدد را پیادهسازی کنیم. به جای تکیه بر حافظه نهان، می توان سیستم را طوری طراحی کرد که در صورت بروز خطا، درخواست را مجدداً ارسال کند. این الگوی امتحان مجدد جایگزین ساده تری ارائه می دهد و می تواند به طور موثر احتمال خطا در برنامه ما را کاهش دهد. برخلاف کش، که اغلب به مکانیسم های پیچیده ابطال کش برای مدیریت تغییرات داده نیاز دارد، اجرای مجدد درخواست های ناموفق نسبتاً ساده است. از آنجایی که عدم اعتبار کش به طور گسترده به عنوان یکی از چالش برانگیزترین وظایف در مهندسی نرم افزار در نظر گرفته می شود، اتخاذ یک استراتژی تلاش مجدد می تواند مدیریت خطا را ساده کرده و انعطاف پذیری سیستم را بهبود بخشد.
مدار شکن
با این حال، اتخاذ استراتژی تلاش مجدد بدون در نظر گرفتن پیامدهای بالقوه می تواند منجر به عوارض بیشتر شود.
بیایید تصور کنیم یکی از پشتیبانهای ما با شکست مواجه شده است. در چنین سناریویی، شروع تلاشهای مجدد برای باطن خراب میتواند منجر به افزایش قابل توجهی در حجم ترافیک شود. این افزایش ناگهانی ترافیک ممکن است باطن را تحت الشعاع قرار دهد، خرابی را تشدید کند و به طور بالقوه باعث ایجاد یک اثر آبشاری در سراسر سیستم شود.
برای مقابله با این چالش، تکمیل الگوی امتحان مجدد با الگوی قطع کننده مدار مهم است. قطع کننده مدار به عنوان یک مکانیسم حفاظتی عمل می کند که میزان خطای سرویس های پایین دستی را نظارت می کند. هنگامی که میزان خطا از یک آستانه از پیش تعریف شده فراتر می رود، قطع کننده مدار درخواست های سرویس آسیب دیده را برای مدت زمان مشخصی قطع می کند. در این مدت، سیستم از ارسال درخواستهای اضافی خودداری میکند تا زمان سرویس ناموفق بازیابی شود. پس از بازه زمانی تعیین شده، قطع کننده مدار با احتیاط اجازه می دهد تا تعداد محدودی از درخواست ها عبور کنند و بررسی کنند که آیا سرویس تثبیت شده است یا خیر. اگر سرویس بهبود یافته باشد، ترافیک عادی به تدریج بازیابی می شود. در غیر این صورت، مدار باز می ماند و به مسدود کردن درخواست ها ادامه می دهد تا زمانی که سرویس کار عادی خود را از سر بگیرد. با ادغام الگوی قطع کننده مدار در کنار منطق تلاش مجدد، میتوانیم به طور موثر موقعیتهای خطا را مدیریت کنیم و از اضافه بار سیستم در هنگام خرابی باطن جلوگیری کنیم.
بسته بندی
در نتیجه، با اجرای این الگوهای انعطافپذیری، میتوانیم برنامههای کاربردی خود را در برابر مواقع اضطراری تقویت کنیم، در دسترس بودن بالا را حفظ کنیم و تجربهای یکپارچه را به کاربران ارائه دهیم. علاوه بر این، من می خواهم تأکید کنم که تله متری ابزار دیگری است که نباید هنگام ارائه انعطاف پذیری پروژه نادیده گرفته شود. گزارشها و معیارهای خوب میتوانند کیفیت خدمات را به میزان قابل توجهی افزایش دهند و بینشهای ارزشمندی را در مورد عملکرد آنها ارائه دهند و به تصمیمگیری آگاهانه برای بهبود بیشتر آنها کمک کنند.