سجاد غفاریان
سجاد غفاریان
خواندن ۱۰ دقیقه·۳ ماه پیش

اصول طراحی نرم‌افزارها و سیستم‌های مقیاس‌پذیر - بخش دوم

در ادامه‌ی قسمت قبلی که در مورد طراحی برای خودترمیمی (Self-Healing) و ایجاد افزونگی در همه اجزا (Redundancy) صحبت کردیم، توی این مقاله می‌خوایم به سراغ دو اصل دیگه بریم که می‌تونن تأثیر زیادی روی مقیاس‌پذیری و کارایی سیستم داشته باشن: Minimize Coordination و Design to Scale Out.

این اصول بهت کمک می‌کنن تا سیستمی بسازی که نه تنها مقاوم و پایدار باشه، بلکه بتونه به راحتی با رشد نیازمندی‌ها و افزایش ترافیک، خودش رو وفق بده.

لینک بخش اول از این سری مقاله - اصول طراحی نرم‌افزارها و سیستم‌های مقیاس‌پذیر

چرا باید به این اصول توجه کنیم؟

وقتی داری یه سیستم بزرگ و پیچیده می‌سازی، یکی از مشکلات رایجی که باهاش مواجه می‌شی، افزایش وابستگی‌ها و هماهنگی بین بخش‌های مختلفه. هر چه میزان هماهنگی بین کامپوننت‌ها بیشتر بشه، احتمال بروز خطا و کاهش کارایی هم بالاتر میره. در واقع، هماهنگی زیاد، مثل یه ترافیک سنگین توی یه چهارراه شلوغه؛ هر چقدر هم که ماشین‌ها منظم حرکت کنن، کوچک‌ترین مشکل یا تأخیر می‌تونه کل سیستم رو دچار اختلال کنه.

اینجاست که مفهوم Minimize Coordination یا کاهش وابستگی‌ها و هماهنگی بین کامپوننت‌ها مطرح میشه. این اصل بهت میگه که باید تا جایی که ممکنه، هر بخش از سیستم رو مستقل و بدون نیاز به ارتباطات پیچیده طراحی کنی. با این کار، هر کامپوننت می‌تونه به صورت جداگانه مقیاس‌پذیر بشه و حتی اگه یکی از اون‌ها از کار بیفته، بقیه بخش‌ها بدون مشکل به کار خودشون ادامه بدن.

اما کاهش هماهنگی به تنهایی کافی نیست. باید مطمئن بشی که سیستم به راحتی قابل گسترش (Scale) باشه. اینجاست که اصل Design to Scale Out یا طراحی برای مقیاس‌پذیری افقی وارد میشه. این اصل به این معنیه که سیستم رو طوری بسازی که بتونی با افزایش یا کاهش تعداد Instanceها (سرورها، سرویس‌ها یا منابع)، عملکرد و ظرفیتش رو تنظیم کنی، بدون اینکه معماری یا زیرساخت رو تغییر بدی.

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


به حداقل رساندن هماهنگی بین کامپوننت‌ها برای دستیابی به مقیاس‌پذیری (Minimize Coordination)

یکی از چالش‌های بزرگ در طراحی سیستم‌های پیچیده اینه که با افزایش مقیاس، هماهنگی بین کامپوننت‌ها هم بیشتر میشه. این هماهنگی گاهی می‌تونه خودش به عنوان یه مانع جدی بر سر راه مقیاس‌پذیری و کارایی سیستم عمل کنه. اصل Minimize Coordination (به حداقل رسوندن هماهنگی)، به این نکته اشاره داره که هرچه وابستگی‌ها و تعاملات بین بخش‌های مختلف سیستم رو کمتر کنی، سیستم راحت‌تر و سریع‌تر می‌تونه رشد کنه و به مقیاس‌های بالاتر برسه.

چرا هماهنگی زیاد مشکل‌ساز میشه؟

وقتی یک سیستم توزیع‌شده (Distributed System) داری، معمولاً اجزای مختلف مثل پایگاه داده‌ها، سرویس‌های پردازشی، و سرویس‌های ارتباطی، همگی نیاز به همگام‌سازی (Synchronization) دارن تا نتیجه نهایی به درستی تولید بشه. اما این هماهنگی باعث ایجاد تنگناها و محدودیت‌هایی میشه که در نهایت، به کاهش کارایی و سرعت منجر میشه.

برای مثال، تصور کن دو سرویس مختلف سعی کنن همزمان روی یک دیتابیس مشترک تغییراتی ایجاد کنن. در این حالت، برای حفظ یکپارچگی داده‌ها، سیستم مجبور میشه از قفل‌ها (Locks) و هماهنگی‌های پیچیده‌ای استفاده کنه. این کار باعث میشه که هر سرویس منتظر بمونه تا سرویس دیگه کارش رو تموم کنه و این انتظارها می‌تونه سرعت سیستم رو به شدت پایین بیاره.

هماهنگی به چه شکل‌هایی وجود داره؟

هماهنگی معمولاً در سطوح مختلف مثل داده‌ها (Data) و محاسبات (Compute) اتفاق می‌افته. برای مثال، در سطح داده‌ها، سرویس‌ها ممکنه نیاز داشته باشن که برای حفظ ACID (اتمی بودن، سازگاری، ایزوله بودن و پایداری) از قفل‌های دیتابیس استفاده کنن. در سطح محاسبات هم، هماهنگی ممکنه برای مدیریت توزیع کارها بین سرویس‌ها یا جلوگیری از دوباره‌کاری‌ها (Duplicate Processing) نیاز باشه.

در نتیجه، به حداقل رسوندن این هماهنگی‌ها بهت کمک می‌کنه که سرعت سیستم رو بالا ببری و بدون ایجاد تنگنا، به راحتی مقیاس‌پذیری افقی رو پیاده‌سازی کنی.

چطور میشه هماهنگی‌ها رو به حداقل رسوند؟

  • استفاده از کامپوننت‌های جدا و ارتباطات غیرهمزمان: یکی از راه‌های کاهش هماهنگی، اینه که کامپوننت‌ها به صورت مستقل و بدون نیاز به هماهنگی با هم کار کنن. این کار رو می‌تونی با استفاده از ارتباطات غیرهمزمان (Asynchronous Communication) و پیام‌محور (Event-Driven) پیاده‌سازی کنی. در این مدل، کامپوننت‌ها به جای اینکه مستقیماً با هم درگیر باشن، از طریق پیام‌ها یا رویدادها با هم ارتباط می‌گیرن.
  • پذیرش همگرایی نهایی (Eventual Consistency): به جای تلاش برای حفظ سازگاری قوی (Strong Consistency) که نیاز به هماهنگی‌های زیادی داره، می‌تونی به سمت سازگاری نهایی (Eventual Consistency) حرکت کنی. یعنی داده‌ها ممکنه برای مدتی با هم همگام نباشن، اما در نهایت به یک وضعیت پایدار برسن. اینکار نیاز به هماهنگی رو به شدت کاهش می‌ده.
  • استفاده از پترن‌هایی مثل CQRS و Event Sourcing: با استفاده از پترن CQRS (جداسازی خواندن و نوشتن)، می‌تونی عملیات‌های نوشتن و خواندن رو از هم جدا کنی تا هر کدوم مسیرهای مستقل خودشون رو داشته باشن و روی همدیگه اثر منفی نذارن.
    با پترن Event Sourcing، تغییرات وضعیت سیستم رو به شکل رویدادها (Events) ذخیره می‌کنی. این کار نیاز به قفل‌های دیتابیس و هماهنگی‌های پیچیده رو از بین می‌بره، چون هر تغییری به شکل یک رویداد مجزا ثبت میشه.
  • پارتیشن‌بندی داده‌ها و وضعیت‌ها (Partition Data and State): با تقسیم داده‌ها به پارتیشن‌ها یا Shardهای جداگانه، هر سرویس می‌تونه بدون نیاز به هماهنگی با بقیه، روی بخش خودش کار کنه. مثلاً اگه یه دیتابیس بزرگ داری، اون رو به چند بخش مستقل تقسیم کن تا سرویس‌ها فقط روی بخش خودشون تمرکز کنن. این کار، نیاز به هماهنگی بین سرویس‌ها رو به شدت کاهش میده.
  • طراحی عملیات‌های Idempotent: عملیات‌های Idempotent به این معنی هستن که حتی اگه یک عملیات چندین بار تکرار بشه، نتیجه نهایی همچنان یکسان می‌مونه. با این روش، اگه یکی از سرویس‌ها وسط کار دچار مشکل بشه، سرویس دیگه‌ای می‌تونه ادامه کار رو انجام بده، بدون اینکه نیاز به هماهنگی پیچیده‌ای باشه.
  • استفاده از Optimistic Concurrency: به جای قفل کردن منابع (که باعث کاهش کارایی میشه)، از مدل‌های Optimistic Concurrency استفاده کن. در این مدل، هر تراکنش روی یک نسخه کپی از داده‌ها کار می‌کنه و در زمان ذخیره‌سازی نهایی، سیستم بررسی می‌کنه که آیا داده‌ها تغییر کرده‌اند یا نه. این روش باعث کاهش نیاز به قفل‌ها و هماهنگی‌های سنگین میشه.
  • استفاده از پترن‌های موازی‌سازی مثل Map-reduce: اگه کارهای زیادی داری که میشه به صورت مستقل انجام داد، اون‌ها رو به وظایف کوچیک‌تر تقسیم کن و روی چندین نود مختلف به صورت موازی اجرا کن. این کار نیاز به هماهنگی رو به حداقل می‌رسونه و بهت کمک می‌کنه از مقیاس‌پذیری افقی بهره‌مند بشی.
  • استفاده از پترن انتخاب رهبر (Leader Election): بعضی وقت‌ها هماهنگی اجتناب‌ناپذیره، در این موارد از پترن Leader Election استفاده کن تا همیشه یه هماهنگ‌کننده داشته باشی و اگه از کار افتاد، نود جدیدی به عنوان رهبر انتخاب بشه و هماهنگی رو ادامه بده.(درمورد این موضوع روی الگوریتم raft هم یک مقدار مطالعه داشته باشین)

به حداقل رسوندن هماهنگی بین بخش‌های مختلف سیستم، بهت کمک می‌کنه که تنگناها و مشکلات عملکردی رو کاهش بدی و به راحتی سیستمت رو در مقیاس‌های بالاتر گسترش بدی. با استفاده از روش‌هایی مثل تقسیم‌بندی داده‌ها، استفاده از ارتباطات غیرهمزمان، پذیرش همگرایی نهایی و پیاده‌سازی پترن‌های مناسب، می‌تونی سیستمی بسازی که علاوه بر سرعت و کارایی بالا، در برابر خطاها هم مقاوم باشه و به راحتی مقیاس‌پذیری افقی رو پیاده‌سازی کنه.


طراحی برای مقیاس‌پذیری افقی (Design to Scale Out)

مقیاس‌پذیری افقی یکی از اصول کلیدی در طراحی سیستم‌های مدرن است که بهت اجازه میده با افزایش تعداد منابع (مثل سرورها، سرویس‌ها یا نودها)، ظرفیت و عملکرد سیستم رو متناسب با نیاز بالا ببری. این قابلیت، به ویژه توی محیط‌های ابری (Cloud)، اهمیت بیشتری پیدا می‌کنه چون می‌تونی بر اساس نیاز لحظه‌ای، منابع رو به صورت داینامیک اضافه یا کم کنی.

به عبارت ساده، مقیاس‌پذیری افقی یعنی به جای ارتقا دادن قدرت سخت‌افزاری یک سرور (Scale Up)، تعداد بیشتری سرور رو به مجموعه اضافه کنی تا بار رو بین این سرورها توزیع کنی.

مزیت مقیاس‌پذیری افقی در سیستم‌های ابری

در محیط‌های ابری، یکی از بزرگترین مزیت‌ها اینه که می‌تونی از مقیاس‌پذیری الستیک (Elastic Scaling) استفاده کنی. به این معنی که هر وقت ترافیک بالایی وارد شد، سرورها و سرویس‌های بیشتری اضافه می‌کنی و وقتی بار کاهش پیدا کرد، این منابع اضافی رو حذف می‌کنی تا هزینه‌ها رو بهینه‌سازی کنی. این انعطاف‌پذیری در محیط‌های ابری، بهت اجازه می‌ده بدون نیاز به نگرانی از بابت کمبود منابع، پاسخگوی هر نوع حجم کاری (Workload) باشی.

چالش‌های مقیاس‌پذیری افقی

طراحی برای مقیاس‌پذیری افقی خیلی ساده به نظر می‌رسه: «چندتا سرور اضافه کن و تموم!» اما در واقع، موانعی مثل گلوگاه‌ها (Bottlenecks) و نقاط همگام‌سازی (Synchronization Points) می‌تونن به شدت روی عملکرد و مقیاس‌پذیری تأثیر بذارن، در یک سیستم ایده‌آل، وقتی تعداد منابع دو برابر میشه، باید بتونی عملکرد سیستم رو هم دو برابر کنی. ولی در دنیای واقعی، این نسبت به ندرت به شکل کامل اتفاق می‌افته، چون معمولاً بعضی بخش‌های سیستم دچار تنگناهایی میشن که با افزایش منابع، نتیجه بهینه‌ای به دست نمیاد.

اصول مهم برای پیاده‌سازی مقیاس‌پذیری افقی

  • اجتناب از Instance Stickiness: چسبندگی یا Session Affinity به این معنیه که درخواست‌های یک کاربر همیشه به همون سرور یا سرویس خاص ارسال بشه. این اتفاق معمولاً به خاطر ذخیره‌سازی حالت (State) در حافظه محلی یا استفاده از کلیدهای رمزنگاری منحصر به‌فرد برای هر سرور رخ میده. اما این کار باعث میشه نتونی به راحتی سرورها رو اضافه یا کم کنی، چون بار درخواست‌ها روی چندتا سرور خاص متمرکز میشه. بنابراین، باید کاری کنی که هر Instance بتونه هر درخواست رو پردازش کنه.
  • شناسایی و رفع گلوگاه‌ها (Bottlenecks): اضافه کردن منابع همیشه راه‌حل نیست! اگه مشکل اصلی توی پایگاه داده یا سرویس‌های Back-end باشه، اضافه کردن تعداد بیشتری سرور جلویی (Front-end) کمکی نمی‌کنه. قبل از پیاده‌سازی مقیاس‌پذیری افقی، گلوگاه‌ها و نقاط بحرانی رو شناسایی کن و رفعشون کن.
  • تقسیم کارها بر اساس نیازمندی‌های مقیاس‌پذیری: اپلیکیشن‌ها معمولاً از چندین Workload مختلف تشکیل شدن که هر کدومشون نیازهای متفاوتی برای مقیاس‌پذیری دارن. مثلاً یه بخش ممکنه دائماً درگیر درخواست‌های کاربر باشه و نیاز به مقیاس‌پذیری بالا داشته باشه، در حالی که یه بخش دیگه بار کمتری داره و به مقیاس‌پذیری زیادی نیاز نداره. هر Workload رو جداگانه مدیریت کن تا بتونی فقط بخش‌هایی که نیاز دارن رو مستقل مقیاس‌دهی کنی.
  • استفاده از کامپوننت‌های مستقل و غیرهمگام (Asynchronous Communication): بهتره کامپوننت‌ها از طریق پروتکل‌های غیرهمزمان با هم ارتباط برقرار کنن و به شکل مستقل کار کنن. این کار باعث میشه هر کامپوننت بتونه بدون نیاز به هماهنگی با بقیه، به راحتی مقیاس‌پذیر بشه. از مکانیزم‌هایی مثل Message Queue استفاده کن تا بتونی بار اضافی رو مدیریت و در زمان مناسب پردازش کنی.
  • پرهیز از هماهنگی و انتظارهای بی‌مورد: هماهنگی زیاد و انتظار برای منابع مشترک، قاتل مقیاس‌پذیریه. همیشه به دنبال راه‌حل‌هایی باش که نیاز به این نوع تعاملات رو کم کنی. مثلاً به جای تراکنش‌های سنگین، از همگرایی نهایی (Eventual Consistency) استفاده کن.
  • انتقال وظایف سنگین به پس‌زمینه (Offload Resource-Intensive Tasks): کارهایی که منابع زیادی مصرف می‌کنن، مثل پردازش‌های سنگین یا عملیات I/O، رو به پس‌زمینه (Background Jobs) منتقل کن تا فشار از روی سرویس‌هایی که درخواست‌های کاربران رو مدیریت می‌کنن، برداشته بشه.
  • پیاده‌سازی مکانیزمهای مقیاس‌پذیری خودکار (Auto-scaling): یکی از مهم‌ترین ابزارها برای مقیاس‌پذیری افقی، استفاده از auto-scaling بر اساس معیارهای عملکردی مثل میزان استفاده از CPU یا طول صف درخواست‌هاست. اگه Workloadهایت پیش‌بینی‌پذیر هستن، از زمان‌بندی استفاده کن تا در ساعات اوج کار، سرورها رو اضافه کنی و در ساعات کم‌بار، منابع اضافی رو حذف کنی.(یکی از قابلیت هایی که پلتفرمهایی مثل کوبرنتیز به ما میدن، همین موضوع هستش)
  • طراحی برای مقیاس‌دهی معکوس (Scale In): باید سیستمت رو طوری طراحی کنی که وقتی نیاز به منابع اضافی نداری، بتونی به راحتی اون‌ها رو حذف کنی، بدون اینکه روی عملکرد کلی سیستم تأثیر بذاره. مطمئن شو که عملیات‌ها به شکل Idempotent طراحی شدن و می‌تونن بعد از حذف یا قطع یک Instance، کارشون رو از همون نقطه ادامه بدن.
  • مدل‌سازی و بهینه‌سازی مقیاس‌پذیری (Model and Optimize Scalability): از مدل‌سازی‌هایی مثل Amdahl استفاده کن تا بفهمی کدوم قسمت‌های سیستم قابل موازی‌سازی هستن و کجاها همچنان نیاز به هماهنگی و همگام‌سازی وجود داره. این کار بهت کمک می‌کنه نقاط ضعف سیستمت رو شناسایی کنی و بفهمی کجا باید روی بهبود مقیاس‌پذیری تمرکز کنی.

طراحی برای مقیاس‌پذیری افقی، یک اصل حیاتی در ساخت سیستم‌های بزرگ و پیچیده است که می‌خوای همزمان پایدار و انعطاف‌پذیر باشن. با پیاده‌سازی اصولی مثل اجتناب از Session Affinity، تقسیم‌بندی Workloadها، استفاده از ارتباطات غیرهمزمان و پیاده‌سازی Autoscaling، می‌تونی سیستمی بسازی که همزمان با رشد نیازها، رشد کنه و در زمان کاهش بار، هزینه‌ها رو کاهش بده.

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


پایان

امیدوارم که واستون مفید بوده باشه!

منابع:

https://learn.microsoft.com/en-us/azure/architecture/guide/design-principles/minimize-coordination
https://learn.microsoft.com/en-us/azure/architecture/guide/design-principles/scale-out
طراحی نرم افزارسیستم دیزایننرم افزارمهندسی نرم افزاردواپس
SRE at Asa Co. / Agah Group
شاید از این پست‌ها خوشتان بیاید