ویرگول
ورودثبت نام
امیر معصوم بیگی
امیر معصوم بیگی
امیر معصوم بیگی
امیر معصوم بیگی
خواندن ۲۰ دقیقه·۲ ماه پیش

انتزاع (Abstraction) در طراحی سیستم‌های مدرن (2)

فهرست

بخش ۱ — مقدمه و مفهوم انتزاع (Abstraction)

  1. چرا طراحی سیستم پیچیده است

  2. نقش انتزاع در مهار پیچیدگی

  3. تعریف ساده و دقیق انتزاع

  4. انتزاع در زندگی روزمره

  5. انتزاع در مهندسی نرم‌افزار

  6. چرا بدون انتزاع طراحی سیستم ممکن نیست


بخش ۲ — انتزاع در سه حوزهٔ اصلی سیستم‌های بزرگ

۲.۱ انتزاع در پایگاه داده (Database Abstraction)

  1. نقش دیتابیس در سیستم‌های بزرگ

  2. مشکل هم‌زمانی و ناسازگاری داده

  3. تراکنش (Transaction) به‌عنوان ابزار انتزاع

  4. رفتار «همه یا هیچ»

  5. مثال کامل: انتقال پول

  6. چگونه دیتابیس پیچیدگی داخلی را پنهان می‌کند

۲.۲ انتزاع در ارتباطات شبکه: Remote Procedure Call (RPC)

  1. چالش‌های ارتباط سرویس‌ها بر روی شبکه

  2. RPC چیست

  3. RPC چگونه فراخوانی شبکه‌ای را شبیه تابع محلی می‌کند

  4. ساختار کلی RPC

  5. مثال کاربردی در یک فروشگاه آنلاین

۲.۳ انتزاع در رفتار داده — Consistency Models

  • چرا در سیستم‌های توزیع‌شده نسخه‌های داده ممکن است متفاوت باشد

  • تعریف مدل سازگاری (Consistency Model)

  • سازگاری نهایی — Eventual Consistency

  • سازگاری بر پایه «وابستگی منطقی بین رویدادها» — Causal Consistency

  • سازگاری ترتیبی — Sequential Consistency

  • سازگاری سخت / خطی — Strict / Linearizable Consistency

  • مثال‌هایی برای هر مدل


بخش ۳ — انتزاع در مدیریت خرابی‌ها — Failure Models

  • اهمیت شناخت انواع خرابی در سیستم توزیع‌شده

  • تعریف مدل خرابی (Failure Model)

  • خرابی آشکار — Fail-stop Failure

  • خرابی خاموش — Crash Failure

  • خرابی در ارسال/دریافت پیام — Omission Failure

    • حالت «ارسال نشدن پیام» — Send Omission

    • حالت «دریافت نشدن پیام» — Receive Omission

  • خرابی زمانی — Temporal Failure

  • خرابی غیرقابل‌پیش‌بینی / نامطمئن — Byzantine Failure

  • مثال عملی برای هر نوع خرابی


بخش ۴ — جمع‌بندی نهایی

  1. نقش انتزاع در طراحی سیستم‌های واقعی

  2. مزایای طراحی با انتزاع

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

  4. این مفاهیم چگونه در طراحی Instagram، Uber، YouTube و سیستم‌های مشابه به‌کار می‌روند

  5. چرا فهم انتزاع مقدمۀ تمام مباحث مهم‌تر مثل دیتابیس، کش، لا‌بالنسر، صف پیام و… است


بخش ۱ — مقدمه و مفهوم انتزاع (Abstraction)

چرا طراحی سیستم پیچیده است

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

نقش انتزاع در مهار پیچیدگی

انتزاع روشی است که به ما اجازه می‌دهد دربارهٔ یک موضوع پیچیده با یک تصویر ساده‌شده فکر کنیم. این روش بخش‌هایی از سیستم را به‌صورت «جعبه»‌هایی تعریف می‌کند که فقط رفتار بیرونی‌شان برای ما مهم است، نه نحوهٔ پیاده‌سازی درونی آن‌ها.
انتزاع تمرکز ما را از جزئیات غیرضروری جدا می‌کند و روی مسئلهٔ اصلی قرار می‌دهد. بدون انتزاع مجبور می‌شدیم هم‌زمان به ریزترین مواردی مثل بسته‌های شبکه، قفل‌گذاری دیتابیس یا جزئیات سیستم‌عامل فکر کنیم، و هیچ‌چیز قابل طراحی نمی‌شد.

تعریف ساده و دقیق انتزاع

انتزاع یعنی ایجاد یک مدل ساده از یک بخش پیچیدهٔ سیستم، به‌گونه‌ای که بتوان با آن مدل کار کرد بدون این‌که نیاز باشد همهٔ جزئیات داخلی را بدانیم.
در عمل یعنی: «چیزی را آن‌طور ببینیم و استفاده کنیم که لازم داریم، نه آن‌طور که در واقعیت پیاده‌سازی شده است».

انتزاع در زندگی روزمره

نمونه‌های انتزاع را هر روز می‌بینیم.
وقتی از خودرو استفاده می‌کنیم، لازم نیست بدانیم موتور چگونه احتراق می‌کند، سوخت چطور تزریق می‌شود یا ECU چطور فرمان می‌دهد. برای ما خودرو یک «رابط ساده» دارد: فرمان، پدال گاز، پدال ترمز.
یا وقتی از برق شهری استفاده می‌کنیم، لازم نیست بدانیم برق چگونه تولید و منتقل می‌شود؛ فقط کلید را می‌زنیم و لامپ روشن می‌شود.
این‌ها نمونه‌های روشن انتزاع هستند: لایهٔ پیچیده پنهان شده و ما با یک مدل ساده کار می‌کنیم.

انتزاع در مهندسی نرم‌افزار

در نرم‌افزار نیز بسیاری از ابزارها و مفاهیم بر اساس انتزاع ساخته شده‌اند.
کتابخانه‌ها (Libraries)، چارچوب‌ها (Frameworks)، APIها، حتی زبان‌های برنامه‌نویسی—all یک لایهٔ ساده‌تر روی واقعیت پیچیده ایجاد می‌کنند.
وقتی از یک کتابخانهٔ آماده استفاده می‌کنیم، لازم نیست بدانیم الگوریتم داخلی آن چگونه نوشته شده؛ فقط رفتار و ورودی و خروجی آن را می‌شناسیم.
همین باعث می‌شود توسعه سریع‌تر، قابل‌پیش‌بینی‌تر و گفت‌وگوی بین برنامه‌نویسان ساده‌تر شود.

چرا بدون انتزاع طراحی سیستم ممکن نیست

طراحی سیستم نیازمند نگاه کلان است: اینکه داده از کجا می‌آید، چگونه پردازش می‌شود، چگونه ذخیره می‌شود، چگونه بین سرویس‌ها جابه‌جا می‌شود و در صورت افزایش حجم چه تغییری لازم است.
اگر هنگام طراحی مجبور باشیم به موارد ریزتری مانند جزئیات پروتکل‌های شبکه، نحوهٔ مدیریت حافظه، اجرای تراکنش‌ها یا پیاده‌سازی ذخیره‌سازی فکر کنیم، ذهن ما از مسئلهٔ اصلی منحرف می‌شود و طراحی قابل انجام نیست.
انتزاع این اجازه را می‌دهد که سیستم را در لایه‌های جداگانه ببینیم—هر لایه با سطح مشخصی از جزئیات—و دقیقاً در همان سطحی فکر کنیم که برای تصمیم‌گیری لازم است.


بخش ۲ — انتزاع در سه حوزهٔ اصلی سیستم‌های بزرگ

سیستم‌های توزیع‌شده سه نقطهٔ حساس دارند که بیشترین پیچیدگی در آن‌ها دیده می‌شود:

  1. کار با داده و مدیریت هم‌زمانی → پایگاه داده

  2. ارتباط سرویس‌ها از طریق شبکه → RPC

  3. رفتار داده در شرایط چند نسخه‌ای → مدل‌های سازگاری داده

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


۲.۱ انتزاع در پایگاه داده (Database Abstraction)

۱. نقش دیتابیس در سیستم‌های بزرگ

در سیستم‌های مدرن، پایگاه داده مرکز نگهداری اطلاعات است:
کاربر، سفارش‌ها، پیام‌ها، فایل‌ها، پرداخت‌ها، لاگ‌ها و …
این بخش حساس‌ترین نقطهٔ سیستم است، زیرا داده باید همیشه صحیح، منظم و قابل اعتماد باقی بماند.

اما دیتابیس در باطن با مشکلات جدی روبه‌روست:

  • چند کاربر ممکن است هم‌زمان یک داده را تغییر دهند

  • ممکن است سیستم هنگام نوشتن داده خاموش شود

  • ممکن است بخشی از داده بازنویسی شود و بخشی نه

  • ممکن است دو عملیات مرتبط با هم نیمه‌کاره بمانند

اگر برنامه‌نویس مجبور بود کل این جزئیات را مدیریت کند، کار غیرممکن می‌شد.

اینجاست که انتزاع دیتابیس وارد می‌شود.


۲. مشکل هم‌زمانی و ناسازگاری داده

فرض کنید دو کاربر هم‌زمان موجودی حساب خود را تغییر می‌دهند.
اگر عملیات‌ها به ترتیب درست اجرا نشوند، ممکن است موجودی اشتباه شود.

یا تصور کنید در یک فروشگاه آنلاین:

  • کاربر A در حال پرداخت است

  • کاربر B هم‌زمان همان کالا را اضافه می‌کند

  • موجودی کالا محدود است

اگر دیتابیس خودش نتواند ترتیب درست عملیات را فراهم کند، دادهٔ اشتباه وارد سیستم می‌شود.

این پیچیدگی‌ها برای برنامه‌نویس قابل مدیریت نیست، بنابراین دیتابیس باید یک تصویر ساده‌شده بدهد.


۳. تراکنش (Transaction) به‌عنوان ابزار انتزاع

تراکنش ابزاری است که دیتابیس برای ساده کردن کار ارائه می‌دهد.

تراکنش می‌گوید:

یک مجموعهٔ چندعملیاتی یا کامل انجام می‌شود، یا اصلاً انجام نمی‌شود.

این رفتار «همه یا هیچ» یعنی:

  • عملیات ناقص هیچ‌وقت پذیرفته نمی‌شود

  • داده در حالت نیمه‌کاره نمی‌ماند

  • اگر سیستم وسط کار خراب شود، عملیات به حالت قبل برمی‌گردد

برنامه‌نویس فقط دستورهایی مثل این می‌بیند:

BEGIN
COMMIT
ROLLBACK

درحالی‌که دیتابیس پشت صحنه:

  • قفل‌گذاری

  • نوشتن لاگ

  • هماهنگی نسخهٔ داده

  • جلوگیری از تداخل عملیات‌ها

  • بازیابی پس از خرابی

را انجام می‌دهد.

تمام این کارها «پنهان» می‌شوند.
این یعنی انتزاع واقعی.


۴. مثال کامل: انتقال پول

فرض کنید می‌خواهید ۵۰٬۰۰۰ تومان از حساب A به حساب B منتقل کنید.

این عملیات شامل دو کار است:

  • کم کردن موجودی A

  • اضافه کردن موجودی B

این دو باید دقیقاً با هم انجام شوند.
اگر یکی انجام شود و دیگری نه، سیستم دچار خطای خطرناک می‌شود.

تراکنش تضمین می‌کند:

یا هر دو انجام می‌شود، یا هیچ‌کدام.

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


۲.۲ انتزاع در ارتباطات شبکه: Remote Procedure Call (RPC)

۱. چالش‌های ارتباط سرویس‌ها

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

اما «ارتباط از طریق شبکه» مشکلات زیادی دارد:

  • بسته‌بندی پیام

  • رمزگذاری و رمزگشایی

  • ارسال از طریق شبکه

  • قطعی شبکه

  • تأخیر

  • تکرار مجدد درخواست (Retry)

  • Timeout

  • دریافت و بازکردن پیام

  • خطاهای احتمالی در هر مرحله

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


۲. RPC چیست؟

RPC یک لایهٔ انتزاعی است که به برنامه‌نویس اجازه می‌دهد یک تابع را طوری صدا بزند که انگار آن تابع در همان برنامه اجرا می‌شود.

در ظاهر، یک تابع معمولی:

user = UserService.Get(10)

در باطن، یک عملیات شبکه‌ای کامل:

  • بسته‌بندی درخواست

  • ارسال روی شبکه

  • اجرا روی سرور دیگر

  • دریافت نتیجه

  • باز کردن پاسخ

  • مدیریت خطاها

این انتزاع باعث می‌شود برنامه‌نویس «شبکه» را مثل «تابع» ببیند.


۳. RPC چگونه فراخوانی شبکه‌ای را شبیه تابع محلی می‌کند

کاربرد RPC دقیقاً این است:

شبکه را از چشم برنامه‌نویس پنهان کند.

در عمل RPC:

  • ساختار پیام ایجاد می‌کند

  • ارتباط برقرار می‌کند

  • خطاها را مدیریت می‌کند

  • نتیجه را برمی‌گرداند

اما در ظاهر:

تو همچنان داری یک تابع ساده صدا می‌زنی.


۴. مثال کاربردی در فروشگاه آنلاین

فرض کنید «سرویس سبد خرید» باید قیمت یک محصول را بگیرد.

ظاهر کد:

price = ProductService.GetPrice(productId)

اما در واقع:

  • این درخواست روی شبکه ارسال شده

  • به سرویس محصولات رسیده

  • پردازش شده

  • نتیجه بازگشته

RPC تمام این جزئیات را مخفی کرده و فقط یک «رابط ساده» ارائه داده.

این یعنی انتزاع در شبکه.


۲.۳ انتزاع در رفتار داده — مدل‌های سازگاری (Consistency Models)

برای اینکه مدل‌های سازگاری را درست بفهمیم، باید ابتدا ببینیم چرا اصلاً مسئله‌ای به نام «ناسازگاری داده» در سیستم‌های توزیع‌شده وجود دارد.

اگر این قدم را خوب بفهمی، تمام مدل‌های بعدی کاملاً شفاف و قابل‌درک می‌شوند.


۱. چرا نسخه‌های داده متفاوت می‌شوند؟

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

سیستم‌ها برای سرعت، پایداری و تحمل خطا، داده را روی چندین سرور ذخیره می‌کنند.
همین موضوع به‌ظاهر ساده، مهم‌ترین علت تفاوت نسخه‌های داده است.

حال چند دلیل اصلی را به‌صورت کامل و با مثال‌های واضح توضیح می‌دهیم:


الف) تأخیر در هماهنگی بین سرورها

وقتی داده روی چند سرور پخش شده باشد، هر تغییری باید در شبکه پخش شود تا همهٔ سرورها آن تغییر را ببینند.
اما شبکه همیشه سریع و تضمینی نیست.

مثال راحت:

  • سرور شماره ۱ اطلاعات کاربر را به‌روز کرد.

  • سرور شماره ۲ هنوز اطلاع ندارد.

  • سرور شماره ۳ به دلیل شلوغی شبکه چند ثانیه دیرتر متوجه می‌شود.

در همین فاصلهٔ زمانی کوتاه،
سه نسخهٔ متفاوت از یک داده وجود دارد.

در یک سیستم واقعی با میلیون‌ها کاربر، این اتفاق دائمی است.


ب) افزایش سرعت مهم‌تر از هماهنگ‌بودن لحظه‌ای است

فرض کن اینستاگرام برای لایک کردن یک پست، به‌جای اینکه فوراً عدد لایک را نمایش دهد،
اول باید مطمئن شود:

  • همهٔ سرورها نسخهٔ جدید را دارند

  • همهٔ نسخه‌ها بررسی و هماهنگ شده‌اند

  • هیچ سروری عقب نیست

این کار چندین میلی‌ثانیه یا حتی ثانیه طول می‌کشد.

در مقیاس اینستاگرام، همین تأخیر کوچک باعث فروپاشی تجربهٔ کاربر می‌شود.

پس سیستم تصمیم می‌گیرد:

«ابتدا سرعت را تضمین کنیم، هماهنگی بعداً انجام شود.»

اینجا است که نسخه‌های مختلف داده ایجاد می‌شود.


پ) خرابی‌ها و اختلالات طبیعی

در یک سیستم توزیع‌شده،
خرابی یک «استثنا» نیست؛
خرابی یک «حالت طبیعی» است.

مثلاً:

  • یک سرور خاموش می‌شود

  • یک لینک شبکه برای چند ثانیه قطع می‌شود

  • پردازش یک سرور کند می‌شود

  • پیام بین دو سرور دیر می‌رسد

در چنین شرایطی، یک بخش از سیستم نسخهٔ جدید را دریافت می‌کند،
اما بخش دیگر تا مدتی نسخهٔ قدیمی را نگه می‌دارد.


نتیجه

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

برای همین سیستم‌های توزیع‌شده مجبورند یک قانون‌نامه داشته باشند که بگوید:

«اگر کاربر داده را خواند، اجازه داریم چه نسخه‌ای را به او نشان دهیم؟»

این قانون‌نامه همان مدل سازگاری (Consistency Model) است.

حالا که علت اختلاف نسخه‌ها را کاملاً فهمیدیم،
می‌توانیم سراغ انواع مدل‌های سازگاری برویم.


۲. سازگاری نهایی (Eventual Consistency)

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

تعریف ساده

در این مدل، سیستم می‌گوید:

«ممکن است الآن نسخه‌ها یکی نباشند،
اما در نهایت همهٔ سرورها به نسخهٔ درست و مشترک خواهند رسید.»

یعنی:

  • در لحظه = احتمال تفاوت

  • بعد از کمی زمان = هماهنگی کامل

این مدل چه دردی را دوا می‌کند؟

اگر بخواهیم همهٔ سرورها همیشه هماهنگ باشند،
سرعت سیستم به‌شدت پایین می‌آید.

Eventual Consistency با خلاص شدن از این محدودیت سرعت،
به ما اجازه می‌دهد سیستم‌های بسیار سریع بسازیم.

مثال عملی — DNS

وقتی رکورد IP یک دامنه تغییر می‌کند:

  • بعضی سرورها فوراً نسخهٔ جدید را می‌بینند

  • بعضی‌ها چند دقیقه دیرتر

  • اما نهایتاً همه یک نسخه خواهند داشت

هیچ‌کس از DNS انتظار ندارد «در لحظهٔ تغییر»، همهٔ سرورها هماهنگ شوند.

مثالی کاملاً روزمره

در واتساپ، وقتی پیام ارسال می‌کنی:

  • تیک یک خاکستری یعنی «سرور دریافت کرده»

  • تیک دو خاکستری یعنی «به دستگاه طرف رسیده»

  • تیک دو آبی یعنی «خوانده شده»

این مدل رفتار یعنی «هماهنگی مرحله‌به‌مرحله»
که کاملاً با Eventual Consistency هماهنگ است.


۳. سازگاری مبتنی بر وابستگی رویدادها (Causal Consistency)

اگر رویدادها به هم مربوط باشند،
سیستم باید ترتیب آن‌ها را برای همه حفظ کند.

اما اگر هیچ رابطه‌ای نداشته باشند،
نیازی به حفظ ترتیب نیست.

به زبان ساده‌تر:

«اگر یک اتفاق نتیجهٔ اتفاق دیگری باشد،
باید همیشه اولی دیده شود و بعد دومی.»

مثال بسیار ساده و واضح

در شبکهٔ اجتماعی:

کاربر A می‌نویسد:
«کسی فیلم X را دیده؟»

کاربر B پاسخ می‌دهد:
«بله، خیلی خوبه.»

این دو رویداد به هم «وابسته‌اند».
پس سیستم باید همیشه این ترتیب را رعایت کند:

  1. سؤال

  2. پاسخ

اما اگر کاربر C پست دیگری بنویسد،
هیچ ارتباطی بین آن پست و سؤال A وجود ندارد،
پس ترتیب نمایش اهمیتی ندارد.

چرا این مدل مهم است؟

اگر سیستم ترتیب رویدادهای مرتبط را رعایت نکند،
رفتار سیستم نامفهوم و آزاردهنده می‌شود.


۴. سازگاری ترتیبی (Sequential Consistency)

در این مدل،
سیستم تضمین می‌کند عملیات هر کاربر دقیقاً به همان ترتیبی دیده شود که خودش انجام داده است.

اما:
سیستم تضمین نمی‌کند ترتیب واقعی «زمان» برای همه یکی باشد.

مثال روشن

کاربر A سه پست می‌گذارد:

  1. سلام

  2. امروز هوا خوبه

  3. دارم می‌رم بیرون

حتی اگر این پست‌ها در شبکه با تأخیر پراکنده شوند،
سیستم باید همیشه آن‌ها را به همان ترتیب اصلی نمایش دهد.

اما اگر کاربر B هم‌زمان پست بگذارد،
رعایت ترتیب بین پست‌های A و B مهم نیست.

این مدل کجا مفید است؟

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


۵. سازگاری سخت / خطی (Strict / Linearizable Consistency)

این قوی‌ترین، دقیق‌ترین،
اما سنگین‌ترین مدل سازگاری است.

تعریف کامل

در این مدل:

  • هر خواندن باید جدیدترین مقدار واقعی را برگرداند

  • حتی اگر تغییر، کمتر از یک میلی‌ثانیه قبل انجام شده باشد

  • هیچ سروری نباید نسخهٔ قدیمی را برگرداند

به این ترتیب، سیستم مانند یک «دادهٔ یکتا و مرکزی» رفتار می‌کند،
حتی اگر داده روی ده‌ها سرور ذخیره شده باشد.

مثال مهم — تغییر رمز عبور

اگر کاربر رمز عبور خود را تغییر دهد:

  • هیچ بخش سیستم نباید حتی یک لحظه رمز قبلی را قبول کند

  • هیچ نسخهٔ قدیمی نباید وجود داشته باشد

  • امنیت کامل باید تضمین شود

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

هزینهٔ این مدل

برای حصول این سطح از دقت:

  • ارتباط بین سرورها کندتر می‌شود

  • نیاز به هماهنگی لحظه‌ای وجود دارد

  • برخی عملیات قابل پردازش موازی نیستند

پس این مدل بسیار «گران» است و فقط در بخش‌هایی استفاده می‌شود که چاره‌ای جز آن وجود ندارد.



بخش ۳ — انتزاع در مدیریت خرابی‌ها: Failure Models

مقدمه — چرا شناخت خرابی مهم است؟

در دنیای سیستم‌های توزیع‌شده، خرابی یک استثنا نیست؛ بلکه یک وضعیت عادی است.
در سیستمی که صدها یا هزاران سرور دارد:

  • همیشه یک سرور در حال خاموش شدن است

  • همیشه یک بخش از شبکه کمی کندتر از بقیه است

  • همیشه تعدادی از پیام‌ها دیر می‌رسند

  • همیشه احتمال خرابی نرم‌افزار، اشکالات کدنویسی یا حملهٔ بیرونی وجود دارد

اگر سیستم فقط زمانی درست کار کند که «هیچ‌چیز خراب نشود»،
آن سیستم عملاً قابل استفاده نیست.

طراح سیستم باید بتواند بگوید:

  • چه نوع خرابی‌ای رخ داده؟

  • چه تاثیری روی سیستم می‌گذارد؟

  • چگونه باید با آن برخورد کرد؟

هروقت بتوانیم خرابی‌ها را روشن و دقیق دسته‌بندی کنیم،
می‌توانیم برای هر دسته یک استراتژی دفاعی طراحی کنیم.
این دسته‌بندی همان مدل‌های خرابی (Failure Models) است.


۳.۱ نقش مدل خرابی در طراحی سیستم

مدل خرابی کمک می‌کند رفتار درست یک سیستم را «قبل از خرابی، حین خرابی، و بعد از خرابی» تعریف کنیم.

برای مثال:

  • اگر یک سرور خاموش شود، آیا سیستم باید منتظر بماند؟

  • اگر پیامی نرسید، باید دوباره ارسال شود؟

  • اگر یک بخش خراب شد، آیا بقیهٔ سیستم باید ادامه دهند؟

  • اگر دادهٔ ناهماهنگ تولید شد، سیستم باید چه کند؟

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

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


۳.۲ مدل‌های خرابی (Failure Models) — توضیح کامل و مثال‌محور

  1. خرابی قابل‌تشخیص (Fail-stop Failure)

  2. خرابی خاموش (Crash Failure)

  3. خرابی در ارسال/دریافت پیام (Omission Failure)

    • حالت «ارسال نشدن پیام» (Send Omission)

    • حالت «دریافت نشدن پیام» (Receive Omission)

  4. خرابی زمانی (Temporal Failure)

  5. خرابی نامطمئن و غیرقابل‌پیش‌بینی (Byzantine Failure) – پیچیده‌ترین حالت

در ادامه، هر کدام از این مدل‌ها را جداگانه و با جزئیات بررسی می‌کنیم.


۱. Fail-stop Failure — (خرابی قابل تشخیص)

تعریف ساده

در این نوع خرابی:

  • سرور از کار می‌افتد

  • اما بقیهٔ سرورها متوجه این خرابی می‌شوند

  • سیستم می‌داند آن سرور «خاموش» است

این ساده‌ترین نوع خرابی است.

چرا ساده است؟

چون سیستم می‌تواند بلافاصله تصمیم بگیرد:

«این سرور دیگر ماشین حساب نیست. پس درخواست‌ها را نفرستید.»

مثال واقعی

تصور کن یک سرور دیتابیس سخت‌افزارش می‌سوزد.
سیستم مانیتورینگ فوراً گزارش می‌دهد: «سرور پاسخ نمی‌دهد».

در این حالت:

  • درخواست‌ها دیگر به آن سرور ارسال نمی‌شوند

  • سیستم می‌تواند از replica دیگری استفاده کند

Fail-stop ساده‌ترین خرابی است چون تشخیص آن قطعی است.


۲. Crash Failure — خرابی خاموش

تعریف

در این نوع خرابی:

  • سرور ناگهان متوقف می‌شود

  • اما سیستم نمی‌تواند با قطعیت تشخیص دهد که مشکل از خرابی سرور است یا فقط یک تأخیر موقت

چرا سخت‌تر از Fail-stop است؟

چون سیستم باید بین دو احتمال تصمیم بگیرد:

  • آیا سرور مرده است؟

  • یا زنده است، اما فقط دیر جواب می‌دهد؟

و این تشخیص همیشه سخت است.

مثال واقعی

فرض کن یک میکروسرویس به‌طور ناگهانی برای چند ثانیه پاسخ نمی‌دهد:

  • شاید واقعا crash شده

  • شاید CPU پر شده

  • شاید شبکه برای چند ثانیه کند شده

سیستم از بیرون فقط «بی‌پاسخی» می‌بیند
و باید تصمیم بگیرد:

صبر کند؟
یا آن را مرده فرض کند؟

این تصمیم اگر اشتباه گرفته شود، مشکلات جدی ایجاد می‌کند.


۳. Omission Failure — ارسال یا دریافت انجام نمی‌شود

این مدل از خرابی زمانی رخ می‌دهد که «پیام رد و بدل نمی‌شود»
اما سرور لزوماً کاملاً خراب نشده.

دو نوع دارد:


۳.۱ ارسال انجام نمی‌شود (Send Omission)

سرور می‌خواهد پیامی بفرستد،
اما پیام در شبکه گم می‌شود
یا اصلاً خارج نمی‌شود.

مثال واضح

در یک سیستم پرداخت:

  • سرویس فروشگاه به سرویس پرداخت درخواست می‌فرستد

  • پیام از شبکه عبور نمی‌کند

  • پرداخت انجام نشده

  • اما فروشگاه خبر ندارد

نتیجه؟
ممکن است کاربر پول پرداخت کرده باشد اما سیستم ثبت نکرده باشد.


۳.۲ دریافت انجام نمی‌شود (Receive Omission)

پیام به مقصد می‌رسد،
اما سرور دریافت‌کننده:

  • به دلیل شلوغی

  • اشکال نرم‌افزار

  • یا تاخیر زیاد

پیام را پردازش نمی‌کند.

مثال واقعی

در یک صف پیام (Message Queue):

  • سرویس A پیام را ارسال می‌کند

  • سرویس B پیام را دریافت نمی‌کند

  • اما پیام واقعاً رسیده بوده

در چنین شرایطی، سیستم ممکن است پیام را «پردازش نشده» تشخیص دهد و دوباره ارسال کند
و این چند بار پردازش شدن یک پیام می‌تواند مشکل‌ساز شود.


۴. Temporal Failure — خروجی درست، اما «دیر»

تعریف

در این نوع خرابی:

  • سرور کار درست انجام می‌دهد

  • اما آن‌قدر دیر که دیگر پاسخ به درد نمی‌خورد

این یکی از دردناک‌ترین خرابی‌هاست چون سیستم در ظاهر کار می‌کند
اما از نظر کاربر، «خراب» دیده می‌شود.

مثال ساده

در اپلیکیشن اسنپ یا اوبر:

  • راننده موقعیت خود را ۱۰ ثانیه دیرتر ارسال می‌کند

  • مسافر فکر می‌کند راننده «ایستاده»

  • درحالی‌که راننده در حال حرکت است

اطلاعات درست است
اما “به‌موقع” نیست
پس سیستم عملاً مفید نیست.

مثال مهم‌تر

در سامانهٔ بورس،
ارائهٔ قیمت سهام با تأخیر باعث می‌شود معامله‌گر تصمیم اشتباه بگیرد.
در چنین سیستم‌هایی حتی تأخیر ۵۰۰ میلی‌ثانیه فاجعه است.


۵. Byzantine Failure — پیچیده‌ترین و خطرناک‌ترین خرابی

این نوع خرابی کابوس تمام طراحان سیستم‌های بزرگ است.

تعریف کامل

در این خرابی:

  • سرور ممکن است رفتار تصادفی نشان دهد

  • ممکن است اطلاعات اشتباه، قدیمی یا متناقض ارسال کند

  • ممکن است گاهی درست و گاهی غلط کار کند

  • ممکن است پیام‌های ساختگی بفرستد

  • ممکن است نتایج اشتباه تولید کند

و بدتر از همه:

هیچ‌کس نمی‌فهمد که کدام بخش راست می‌گوید.

مثال ملموس

فرض کن سه سرور باید بگویند:

«موجودی حساب چقدر است؟»

سرور ۱: موجودی = ۵۰۰
سرور ۲: موجودی = ۵۰۰
سرور ۳: موجودی = ۱۳ میلیون

کدام درست می‌گوید؟
آیا سرور ۳ خراب است؟
آیا ۱ و ۲ هماهنگ نشده‌اند؟
آیا حملهٔ هکری شده؟

هیچ‌چیز مشخص نیست.

چرا این خرابی وحشتناک است؟

چون سرور خراب ساکت نمی‌ماند
بلکه «مشغول خرابکاری» است.

برخلاف Fail-stop که سرور کامل خاموش می‌شود
و مشکل ساده و قابل تشخیص است،
در Byzantine Failure
سرور زنده است
اما قابل اعتماد نیست.

مثال واقعی: بلاک‌چین

در بلاک‌چین،
باید همیشه فرض کرد که برخی نودها:

  • خراب شده‌اند

  • یا دستکاری شده‌اند

  • یا تحت کنترل مهاجم‌اند

برای همین بلاک‌چین مجبور است از الگوریتم‌های بسیار سنگین مثل:

  • PoW

  • PoS

استفاده کند تا جلوی رفتارهای Byzantine گرفته شود.


۳.۳ چرا برخی خرابی‌ها ساده و برخی بسیار خطرناک‌اند؟

  • Fail-stop ساده است
    چون «خرابی قابل تشخیص» است

  • Crash سخت‌تر است
    چون «بی‌پاسخی» همیشه نشانهٔ خرابی نیست

  • Omission دردسرساز است
    چون پیام‌ها گم می‌شوند و سیستم باید تصمیم بگیرد چه کند

  • Temporal به‌ظاهر کار می‌کند
    اما «دیر بودن» سیستم را بی‌استفاده می‌کند

  • Byzantine خطرناک‌ترین است
    چون «سرور خراب می‌تواند دروغ بگوید»
    و تشخیص حقیقت از دروغ تقریباً غیرممکن است

طراح سیستم باید برای هر کدام استراتژی مناسب بسازد
وگرنه سیستم در دنیای واقعی پایدار نخواهد بود.



بخش ۴ — جمع‌بندی نهایی

انتزاع (Abstraction) شاید در ظاهر یک مفهوم ساده باشد، اما در عمل ستونِ اصلی طراحی سیستم‌های بزرگ است. تقریباً تمام شرکت‌های بزرگ دنیا—از اینستاگرام و اوبر گرفته تا یوتیوب و گوگل—تمام معماری سیستم خود را روی شانهٔ همین مفهوم ساخته‌اند. حالا باید ببینیم دقیقاً چرا انتزاع چنین جایگاهی دارد و نبود آن چه نتایجی دارد.

در ادامه این بخش را در پنج محور اصلی جمع‌بندی می‌کنیم.


۱. نقش انتزاع در طراحی سیستم‌های واقعی

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

  • صدها سرویس دارید

  • صدها پایگاه داده در مناطق مختلف

  • میلیون‌ها کاربر فعال

  • ده‌ها سیستم ذخیره‌سازی

  • کش، صف پیام، CDN، کنترل ترافیک، load balancer

آن وقت حتی «فهمیدن اینکه سیستم دقیقاً چه‌کار می‌کند» مشکل می‌شود.

در چنین شرایطی انتزاع کاری می‌کند که بتوانید:

  • به جای نگاه کردن به هزار جزئیات، یک تصویر قابل فهم داشته باشید

  • رفتار هر بخش را فقط از طریق یک رابط ساده تحلیل کنید

  • سیستم را به بخش‌های مستقل‌تر تقسیم کنید

در حقیقت انتزاع برای طراح سیستمِ بزرگ مثل «نقشهٔ راه» است.
بدون آن، معمار سیستم نمی‌تواند دید کلی داشته باشد و نمی‌تواند دلیل درستی برای انتخاب‌های طراحی ارائه دهد.


۲. مزایای طراحی با انتزاع

وقتی سیستم را در لایه‌های انتزاعی می‌بینیم، چند مزیت مهم ایجاد می‌شود:

الف) امکان تغییر بخش‌ها بدون خراب کردن کل سیستم

وقتی هر لایه فقط یک وظیفهٔ مشخص دارد، می‌توان یک بخش را عوض کرد بدون اینکه بقیه آسیب ببینند.

مثال:

  • دیتابیس را تغییر می‌دهیم

  • کش را عوض می‌کنیم

  • load balancer را ارتقا می‌دهیم

و سیستم همچنان کار می‌کند چون قراردادهای بین این اجزا ساده و ثابت مانده‌اند.

ب) امکان رشد سیستم بدون افزایش پیچیدگی مغز انسان

مغز انسان قادر نیست هزاران ارتباط پیچیده را مدیریت کند.
انتزاع این پیچیدگی را «سطح‌بندی» می‌کند.

مثلاً:

  • از نگاه کلان: فقط چند سرویس اصلی می‌بینیم

  • از نگاه میانی: جزئیات ارتباطات سرویس‌ها

  • از نگاه دقیق: نحوهٔ ذخیره‌سازی، صف، کش، پیام و…

این تفکیک باعث می‌شود بتوانید سیستم بسیار بزرگ را طراحی، عیب‌یابی و توسعه دهید.

پ) افزایش سرعت توسعه

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

انتزاع یعنی:

  • RPC مثل فراخوانی تابع محلی است

  • transaction مثل یک بستهٔ «همه یا هیچ» دیده می‌شود

  • consistency model مثل قانون قابل‌پیش‌بینی رفتار می‌کند

  • failure model مثل یک دسته‌بندی شفاف است

این یعنی سرعت واقعی توسعه.

ت) قابلیت همکاری تیم‌ها

در شرکت‌های بزرگ ۲۰۰ تیم هم‌زمان روی یک محصول کار می‌کنند.
انتزاع باعث می‌شود هر تیم فقط «جهان خودش» را ببیند.

مثلاً:

  • تیم A مسئول دیتابیس است

  • تیم B مسئول لاگ‌گیری است

  • تیم C مسئول پیام‌رسانی است

هر تیم تنها API و رفتار لایهٔ خودش را می‌بیند، و نیازی نیست از جزئیات داخلی دیگر تیم‌ها خبر داشته باشد.


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

انتزاع فقط فهم مفهومی نیست، بلکه مستقیماً روی کیفیت سیستم تأثیر دارد.

الف) ارتباط با مقیاس‌پذیری (Scalability)

برای بزرگ شدن یک سیستم، باید بتوانید:

  • بخش‌ها را مستقل مقیاس دهید

  • ترافیک را بین سرویس‌ها تقسیم کنید

  • منابع را جداگانه افزایش دهید

این کار فقط زمانی ممکن است که هر بخش «یک انتزاع واضح» داشته باشد.

مثال:

  • یک سرویس stateless را می‌توان با اضافه کردن چند replica مقیاس داد

  • یک صف پیام را می‌توان shard کرد

  • یک کش را می‌توان توزیع‌شده کرد

این‌ها بدون انتزاع امکان‌پذیر نیست.

ب) ارتباط با قابلیت اطمینان (Reliability)

خرابی همیشه اتفاق می‌افتد.
اما اگر سیستم بر اساس مدل‌های انتزاعی خرابی طراحی شده باشد:

  • خرابی قابل پیش‌بینی می‌شود

  • رفتار قابل کنترل می‌شود

  • کنارآمدن با خرابی امکان‌پذیر می‌شود

مثلاً اگر بدانید:

  • Omission Failure یعنی پیام ممکن است گم شود

  • Crash Failure یعنی سیستم نمی‌داند مشکل از کجاست

  • Byzantine Failure یعنی لازم است سرورها همدیگر را تایید کنند

می‌توانید معماری مقاوم بسازید.

پ) ارتباط با سادگی تفکر

وقتی هزاران جزئیات نامرتبط را حذف می‌کنید،
تفکر شما واضح‌تر می‌شود.

در طراحی سیستم‌های بزرگ، شفاف فکر کردن مهم‌تر از داشتن جزئیات زیاد است.


۴. کاربرد انتزاع در طراحی Instagram، Uber، YouTube و سیستم‌های مشابه

همهٔ سیستم‌های بزرگ دنیا از انتزاع استفاده می‌کنند.
بیایید چند مثال واقعی ببینیم:

Instagram

  • لایک و کامنت با Eventual Consistency انجام می‌شود چون سرعت مهم‌تر است.

  • انتقال تصویرها توسط CDN انجام می‌شود؛ یک لایهٔ انتزاعی از شبکهٔ جهانی.

  • هر درخواست HTTP توسط Load Balancer به نزدیک‌ترین سرور هدایت می‌شود.

  • دیتابیس‌ها شارد شده‌اند و هر shard یک انتزاع از «بخشی از کاربرها» است.

Uber

  • موقعیت راننده با مدل‌های مخصوص خرابی زمانی (Temporal Failure) مدیریت می‌شود.

  • سرویس‌های راننده و مسافر با RPC با هم صحبت می‌کنند و این پیچیدگی شبکه پنهان شده.

  • سیستم Matching راننده و مسافر به چند لایه تقسیم شده است:
    الگوریتم → موتور جستجو → سرویس‌ها → cache → پایگاه داده.

YouTube

  • سیستم ویدیوها روی چند CDN جهانی با مدل Eventual Consistency منتشر می‌شود.

  • تعداد بازدید ابتدا در cache ذخیره می‌شود، سپس به‌صورت batch در دیتابیس ثبت می‌گردد (به‌خاطر مدل consistency).

  • صف پیام Kafka برای پردازش ویدیو استفاده می‌شود؛ یعنی یک انتزاع کامل از جریان داده.

در تمام این‌ها یک الگوی واضح دیده می‌شود:

هیچ‌کدام کل سیستم را یکجا نمی‌بینند؛ هر بخش فقط انتزاع لایهٔ خودش را می‌بیند.


۵. چرا فهم انتزاع مقدمۀ تمام مباحث مهم‌تر است؟

تمام مفاهیم اصلی طراحی سیستم—

  • پایگاه داده

  • کش

  • load balancer

  • صف پیام

  • CDN

  • replicaton

  • sharding

  • consistency

  • failure models

—همگی بر پایهٔ انتزاع ساخته شده‌اند.

اگر انتزاع را نفهمیم:

  • نمی‌دانیم چرا باید Shard کنیم

  • متوجه نمی‌شویم چرا consistency مدل دارد

  • نمی‌دانیم cache باید چه رفتاری داشته باشد

  • نمی‌فهمیم چرا load balancer فقط یک «جعبه» در دیاگرام است

  • نمی‌دانیم failure model چه تاثیری دارد

  • نمی‌توانیم معماری را به‌درستی تحلیل کنیم

انتزاع اولین قدم است، چون:

بدون انتزاع هیچ بخش دیگری قابل فهم نیست

این موضوع شبیه معماری ساختمان است:

  • قبل از طراحی برق، لوله‌کشی، تهویه، سازه، آسانسور

  • باید «نقشهٔ کلی» وجود داشته باشد

انتزاع همان نقشهٔ کلی است.


جمع‌بندی نهایی

انتزاع ابزار اصلی مهندسان نرم‌افزار برای فکر کردن به سیستم‌های بزرگ است.
بدون آن سیستم‌های امروزی—از فروشگاه آنلاین گرفته تا یوتیوب و اینستاگرام—اصلاً قابل طراحی نیستند.

انتزاع:

  • پیچیدگی را حذف نمی‌کند، مدیریت‌پذیر می‌کند

  • بخش‌ها را جدا می‌کند تا قابل توسعه شوند

  • مسیر طراحی صحیح را مشخص می‌کند

  • پایهٔ مباحث مهمی مثل consistency، failure، replication و scalability است

به همین دلیل است که در آموزش طراحی سیستم،
حتماً باید انتزاع اولین درس باشد.


نویسنده : امیر معصوم بیگی


طراحیانتزاع
۰
۰
امیر معصوم بیگی
امیر معصوم بیگی
شاید از این پست‌ها خوشتان بیاید