کتابهای دستهبندی: معماری نرمافزار · همهٔ کتابها
کتابخانهٔ شنیدنی شبگفتار
کتابهای معماری نرمافزار
خلاصههای صوتی فارسی کتابهای مرتبط با معماری نرمافزار؛ کتاب و فصل دلخواهتان را برای شنیدن انتخاب کنید.
این مجموعه خلاصهٔ کتابهاست؛ نسخهٔ کامل کتاب اصلی نیست.
4 عنوان · 34 قسمت منتشرشده
راه را پیدا کنید
مسیرهای مطالعاتی
قفسهٔ کتابها
از این کتابها شروع کنید
بخش اول: الگوهای معماری در سیستمهای توزیعشده
نمونهٔ رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که وقتی تعداد کاربران یک سرویس آنلاین به طور ناگهانی چند برابر میشود، زیرساخت آن چطور زیر بار دوام میآورد؟ چطور ممکن است درخواستهای میلیونها نفر در سراسر دنیا بدون از کار افتادن سیستم پردازش شوند؟
این موضوع در فصلهای ابتدایی کتاب «الگوهای سیستمهای توزیعشده» نوشتهٔ اونمش جوشی بررسی میشود که به ما نشان میدهد چطور میتوان چالشهای مقیاسپذیری و پایداری را با استفاده از الگوهای معماری مدیریت کرد.
اولین ایده برای مدیریت دادههای حجیم، بخشبندی کردن اطلاعات روی چندین ماشین است. مسئله این است که وقتی دادهها تقسیم میشوند، احتمال خرابی سختافزار افزایش مییابد و گزینههای پیش رو شامل تکثیر دادهها برای پوشش این خرابیهاست؛ تریدآف این روش در پیچیدگی هماهنگی میان گرههاست. برای نمونه، در یک سامانهٔ خردهفروشی بزرگ، اطلاعات سبد خرید مشتریان روی چندین گره مختلف کپی میشود تا با از دست رفتن یکی از آنها، سیستم همچنان پاسخگو بماند.
برای حفظ ثبات و پایداری دادهها روی یک گره، استفاده از Write-Ahead Log (لاگ نوشتن پیشرو) به کار گرفته میشود. مشکل اینجاست که اگر سرور هنگام بهروزرسانی فایلهای متعدد دچار نقص شود، دادهها ناسازگار میشوند. راهحل این است که سیستم پیش از اعمال تغییرات، جزئیات را در یک فایل ترتیبی ثبت کند تا در صورت راهاندازی مجدد، وضعیت قبلی قابل بازیابی باشد. پایگاههای داده از این الگو برای ثبت و مدیریت تراکنشها استفاده میکنند.
مشکل بعدی بهروزرسانیهای رقابتی است؛ وقتی دو کاربر همزمان درخواستهای متفاوتی میفرستند، سیستم به مدیریت هماهنگ نیاز دارد. الگوی Leader and Followers (رهبر و پیروان) این مسئله را حل میکند به این شکل که یک گره به عنوان رهبر تمام تغییرات را میپذیرد و به بقیه منتقل میکند. تریدآف آن تمرکز بار روی یک گره است و برای حل خطاهای ناشی از قطع ارتباط آن، Quorum (حد نصاب) به کار میرود که یعنی یک تصمیم تنها زمانی معتبر است که اکثریت گرهها آن را تأیید کرده باشند.
گاهی رهبر از کار میافتد و گرههای دیگر باید رهبر جدیدی انتخاب کنند که این امر با خطراتی در ناسازگاری لاگها همراه است. علامت سطح بالا یا High-Water Mark روشی است که نشان میدهد آخرین بهروزرسانی تا کدام نقطه توسط اکثریت گرهها تأیید شده است. همچنین برای اینکه ارسال دوبارهٔ درخواست توسط مشتریان باعث اجرای تکراری عملیات نشود، سامانه از Idempotent Receiver (گیرندهٔ خودتوان) استفاده میکند تا با بررسی شناسهٔ یکتای درخواست، پاسخهای قبلی را بدون اجرای مجدد برگرداند.
پیشنهاد من این است که هنگام طراحی سامانههای توزیعشده، از همان ابتدا به چالشهای همگامسازی زمان و خطاهای جزئی شبکه فکر کنید و به جای تلاش برای پیادهسازی دستی الگوریتمهای پیچیده، از الگوهای اثباتشده و ابزارهای استاندارد بهره بگیرید.
امیدوارم این بررسی کوتاه از الگوهای سیستمهای توزیعشده برایتان مفید بوده باشد و در قسمتهای بعدی همراه ما بمانید.
مقدمه: ریشهیابی شکست پروژههای نرمافزاری
نمونهٔ رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که چرا با وجود پیشرفت ابزارها و متدولوژیها، هنوز هم درصد بالایی از پروژههای نرمافزاری به موقع تحویل داده نمیشوند یا شکست میخورند؟ مشکل اصلی این نیست که یاد گرفتن فریمورکهای جاوااسکریپت سخت است، بلکه چالش اصلی درک درست دنیای پیچیده و دامنههای مختلف کسبوکار است.
در کتاب «یادگیری طراحی دامنهمحور» نوشتهٔ «ولاد خونونوف»، در فصل مقدمه بررسی میشود که ریشهٔ بحران نرمافزار در ضعف ارتباطات است و طراحی دامنهمحور تلاش میکند این مشکل را از ریشه حل کند.
اولین مسئله، ناتوانی در درک صحیح دامنه Domain یعنی حوزهٔ کاری و دانشی که نرمافزار برای آن ساخته میشود است که باعث پیادهسازی نامطلوب سیستم میشود. گزینههای موجود این است که یا بدون شناخت کافی کدنویسی را شروع کنیم یا زمان کافی برای فهم قوانین کسبوکار بگذاریم که معاوضهٔ آن، صرف وقت بیشتر در ابتدا در برابر نجات پروژه در بلندمدت است؛ مثلاً در یک سیستم مالی، اگر بخش حسابداری را درست درک نکنیم، کل ساختار نرمافزار با شکست مواجه میشود.
دومین مسئله، ریشهدار بودن بحران نرمافزار در طول پنجاه سال گذشته است که نشان میدهد متدهایی مثل اجایل یا برنامهنویسی تستمحور به تنهایی نتوانستهاند مشکل را حل کنند. گزینههای جایگزین تمرکز بر فرآیندها یا تمرکز بر حل ریشهای مشکلات ارتباطی بین تیمهاست که نویسنده معتقد است ابزارهای استراتژیک و تاکتیکی طراحی دامنهمحور روی همین بهبود ارتباط تمرکز دارند.
سومین مسئله، مبهم بودن نیازمندیها و اهداف پروژه است که به دلیل نبود درک مشترک بین ذینفعان رخ میدهد. گزینهها شامل ادامهٔ روشهای سنتی ارتباطی یا استفاده از ابزارهای استراتژیک این کتاب برای آنالیز کسبوکار و ساختن یک درک مشترک است که هزینهٔ هماهنگی را به شدت کاهش میدهد.
چهارمین مسئله، فاصله گرفتن کدها از زبان واقعی کسبوکار است که ارتباط بین برنامهنویسان و افراد غیرفنی را دشوار میکند. گزینههای پیش رو نوشتن کد با اصطلاحات فنی پیچیده یا استفاده از ابزارهای تاکتیکی است تا کد نرمافزار مستقیماً اهداف کسبوکار و زبان آن را بازتاب دهد.
به عنوان پیشنهاد شخصی، به نظرم بهتر است پیش از درگیر شدن با جزئیات فنی و نوشتن کدهای پیچیده، زمان بیشتری را به گفتگو با افراد متخصص در زمینهٔ کسبوکار اختصاص دهید تا مطمئن شوید ساختار نرمافزار شما دقیقاً روی نیازهای واقعی همان حوزه بنا شده است.
امیدوارم این نگاه تازه به شما کمک کند تا در مسیر توسعه نرمافزار، ارتباط بهتری با اهداف کسبوکار برقرار کنید و پروژههای پایدارتری بسازید.
فصل دوازدهم: آینده سامانههای دادهای
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که چرا وقتی حجم دادهها زیاد میشود، هیچ پایگاه دادهٔ واحدی نمیتواند تمام نیازهای ما را به تنهایی برآورده کند و مجبور میشویم چندین ابزار مختلف را به زور به هم وصل کنیم؟ آیا راه بهتری برای فرار از این کلاف سردرگم ابزارهای ناسازگار وجود دارد؟
این مبحث از کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» به بررسی آیندهٔ سیستمهای دادهگرا و الگوهای نوآورانه در معماری داده میپردازد.
اولین ایدهٔ کلیدی دربارهٔ چالش یکپارچهسازی دادهها است. در برنامههای پیچیده، دادهها به شکلهای گوناگونی استفاده میشوند و تلاش برای انجام دادن همهٔ کارها در یک تکه نرمافزار به شکست میانجامد. راهکار مرسوم، چسباندن چندین ابزار مختلف مثل پایگاه داده و موتور جستجو به همدیگر است، اما اگر مشتریان مستقیماً روی هر دو سیستم بنویسند، بهدلیل تفاوت ترتیب پردازش، ناسازگار میشوند. نویسنده پیشنهاد میکند که تمام ورودیها از یک مسیر واحد عبور کنند تا بقیهٔ نماها بر اساس همان ترتیب ساخته شوند.
دومین ایدهٔ کلیدی مقایسهٔ رویکرد دادهٔ مشتقشده (Derived Data) با تراکنشهای توزیعشده است. دادهٔ مشتقشده یعنی مجموعهای که از دادههای دیگر طی یک فرایند قابل تکرار به دست میآید تا خواندن را سریعتر کند. تراکنشهای توزیعشده با استفاده از قفلها تلاش میکنند سازگاری ایجاد کنند، اما ویژگیهای ضعیفی در تحمل خطا دارند. در مقابل، سیستمهای مبتنی بر لاگ با استفاده از تغییر دادهٔ ضبطشده (Change Data Capture) که تکنیک ثبت تغییرات پایگاه داده برای اعمال به سایر سیستمهاست، دادهها را به شکل غیرهمزمان بهروزرسانی میکنند که این روش جداسازی مؤلفهها را بهبود میبخشد.
سومین ایدهٔ کلیدی به محدودیت ترتیب کلی و ضرورت حفظ علیت مربوط میشود. ساختن یک لاگ کاملاً مرتبشده نیازمند عبور تمام رویدادها از یک گره رهبر است که با مقیاسبندی سیستم در چند پارتیشن (Partition) که به معنی بخشبندی دادهها روی گرههای مختلف است، دچار مشکل میشود. در غیاب ترتیب کلی، وابستگیهای علی در سیستمهای توزیعشده ممکن است گم شوند؛ مثلاً وقتی یک کاربر رابطهٔ دوستی را قطع میکند و بعد پیامی میفرستد، اگر رویدادها به هم ریخته پردازش شوند، پیام ممکن است قبل از لغو دوستی به دست طرف مقابل برسد. برای حل این مشکل، استفاده از شناسههای یکتا راهگشا است.
چهارمین ایدهٔ کلیدی اتحاد پردازش دستهای و جریانی است. پردازش دستهای (Batch Processing) روی دادههای محدود و پایانپذیر کار میکند و توابع خالص و قطعی تولید میکند، در حالی که پردازش جریانی (Stream Processing) روی دادههای بینهایت و پیوسته عمل میکند. در گذشته معماری لامبدا تلاش میکرد هر دو سیستم را موازی نگه دارد، اما رویکردهای مدرن به دنبال یکی کردن آنها در یک موتور واحد هستند که بتواند رویدادهای تاریخی را هم بازپخش کند و اجرا را به صورت دقیقاً یکبار (Exactly-Once) تضمین نماید؛ یعنی طوری که اثر نهایی عملیات حتی با وجود خطا دقیقاً یک بار اعمال شود.
پنجمین ایدهٔ کلیدی مفهوم تکهتکه کردن پایگاههای داده (Unbundled Databases) است. درست همانطور که سیستمعاملهای یونیکس از ابزارهای کوچک و تخصصی تشکیل شدهاند که با لولهها (Pipes) به هم وصل میشوند، معماری دادهها نیز به سمت ترکیب ابزارهای تخصصی حرکت میکند. در این معماری، خواندن از طریق پایگاههای دادهٔ فدرال (Federated Databases) که رابط پرسوجوی واحدی برای موتورهای ذخیرهسازی مختلف فراهم میکنند، یکپارچه میشود و نوشتن با استفاده از لاگ رویدادها همزمان میگردد تا انعطاف سیستم افزایش یابد.
پیشنهاد عملی من به شما این است که وقتی برنامههای مقیاسپذیر میسازید، بلافاصله به سراغ تراکنشهای توزیعشدهٔ سنگین نروید، بلکه طراحی مبتنی بر رویداد و دادههای مشتقشده را با شناسههای یکتا برای پیگیری درخواستها امتحان کنید تا سیستم شما در برابر خطاها انعطافپذیرتر بماند.
این فصل به ما نشان میدهد که آیندهٔ سیستمهای دادهگرا در گرو گذر از انبارههای صلب و حرکت به سوی جریانهای انعطافپذیر داده است.
فصل یازدهم: پردازش جریانها
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردید که چطور اپلیکیشنهای امروزی تراکنشها، پیامها و رفتارهای ما را در همان لحظه و بدون معطلی تحلیل میکنند، در حالی که حجم دادهها هر لحظه بیشتر میشود؟ این مسئله به یکی از بنیادیترین چالشهای مهندسی نرمافزار تبدیل شده است که سیستمهای توزیعشده با آن دستوپنجه نرم میکنند. در کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن»، فصل یازدهم به همین موضوع اختصاص دارد و نشان میدهد چطور میتوانیم دادهها را به شکل جریانهای بینهایت پردازش کنیم.
برای درک بهتر پردازش جریانی (Stream processing)، یعنی پردازش پیوستهٔ رویدادها همزمان با تولید آنها، باید بدانیم برخلاف پردازش دستهای (Batch processing) که با فایلهای محدود و مشخص سر و کار دارد، دنیای واقعی پر از دادههای پایانناپذیر است. وقتی کاربران مدام در حال تولید داده هستند، تقسیم کردن مصنوعی دادهها به بازههای روزانه یا ساعتی باعث تاخیر زیادی میشود. در اینجا سیستمهای پیامرسان با چالش مدیریت مصرفکنندههای کند روبهرو میشوند؛ اگر تولیدکننده سریعتر از مصرفکننده پیام بفرستد، سیستم باید بین حذف پیامها، صفبندی در حافظه، یا اعمال کنترل جریان یکی را انتخاب کند.
یکی از روشهای کلاسیک برای تبادل پیام، استفاده از کارگزار پیام (Message broker) است که مانند یک پایگاه دادهٔ بهینهشده برای جریان پیامها عمل میکند. کارگزارها معمولاً پس از تحویل موفق پیام به مصرفکننده، آن را پاک میکنند که این رویکرد برای ذخیرهسازی بلندمدت مناسب نیست. در عوض، رویکرد مبتنی بر لاگ (Log-based message broker) مانند آپاچی کافکا، رویدادها را به صورت دنبالهای الحاقی روی دیسک ذخیره میکند تا چندین مصرفکننده بتوانند مستقل از هم، دادهها را بخوانند بدون اینکه نگران پاک شدن پیامها باشند.
ارتباط عمیقی بین پایگاههای داده و جریانها وجود دارد که نمونهٔ بارز آن تغییر دادههاست. مکانیزم تغییر دادهها یا سیدیسی (Change Data Capture) راهی است که تمام تغییرات نوشتهشده در یک پایگاه داده را به شکل یک جریان استخراج میکند تا سیستمهای دیگر مانند ایندکسهای جستجو یا کشها همیشه بهروز بمانند. این الگو کمک میکند تا از خطاهای رایج ناشی از نوشتن همزمان در چند سیستم جلوگیری شود و یک پایگاه داده به عنوان منبع حقیقت عمل کند.
در بحث پردازش جریانها، زمان یکی از پیچیدهترین ابعاد است که باید بین زمان رویداد و زمان پردازش تفاوت قائل شویم. وقتی رویدادها به دلیل تاخیر شبکه یا قطعی اینترنت دیرتر میرسند، سیستم باید با این رویدادهای جا مانده کنار بیاید و پنجرههای زمانی را مدیریت کند. انواع پنجرهها مانند پنجرهٔ غلتان برای تقسیم زمان به بازههای ثابت بدون همپوشانی، به ما کمک میکنند تا تحلیلهای آماری دقیقی روی دادههای در حال حرکت انجام دهیم.
پیشنهاد عملی من این است که وقتی معماری سیستمهای خود را طراحی میکنید، به جای تکیه صرف بر وضعیتهای تغییرپذیر پایگاه داده، به رویدادها به چشم حقایق تغییرناپذیر نگاه کنید. ثبت تغییرات به صورت لاگهای الحاقی به شما اجازه میدهد در صورت بروز خطا، دادهها را دوباره پخش کنید و نماهای خواندنی جدید بسازید بدون اینکه نگران آسیب دیدن دادههای اصلی باشید.
امیدوارم این روایت از کتاب طراحی برنامههای دادهگرا برای شما مفید بوده باشد و دید بهتری نسبت به مدیریت جریان دادهها در سیستمهای مقیاسپذیر پیدا کرده باشید.
فصل دهم: پردازش دستهای
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که شرکتهای بزرگ چطور حجم عظیمی از دادههای انباشتهشده را پردازش میکنند، وقتی هیچ کاربری پشت خط ننشسته و منتظر پاسخ آنی نیست؟ چطور میتوان میلیاردها رکورد گزارش سرور را بدون اخلال در سرویسهای آنلاین تحلیل کرد و از دل آنها گزارشهای دقیق بیرون کشید؟
این مبحث کلیدی از کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» است که به ما نشان میدهد چگونه پردازش دادهها فراتر از درخواست و پاسخهای آنلاین معنا پیدا میکند.
اولین ایدهٔ بنیادین، استفاده از رویکرد پردازش دستهای (batch processing) پردازش آفلاین است. در این سیستمها، ورودی حجم ثابتی از داده است که پردازش آن ممکن است دقایق یا روزها طول بکشد و هیچ کاربری منتظر نتیجهٔ فوری نیست. گزینهٔ جایگزین، سیستمهای آنلاین هستند که برای پاسخ سریع به کاربر طراحی شدهاند؛ اما تفاوت بزرگ پردازش دستهای در این است که به جای بهینهسازی زمان پاسخ، روی توان عملیاتی متمرکز میشود تا بیشترین حجم داده را در کمترین زمان ممکن پردازش کند. برای نمونه، تحلیل لاگهای دسترسی وبسایت برای پیدا کردن پربازدیدترین صفحات در پایان روز، نمونهای کلاسیک از این کار است.
ایدهٔ دوم، الهام گرفتن از فلسفهٔ ابزارهای یونیکس در مقیاس توزیعشده است. در سیستمهای سنتی، برنامهها ممکن است در حافظه کار کنند، اما ابزارهایی مانند دستورات مرتبسازی و خطوط لوله به کمک ما میآیند تا دادههای بزرگتر از حافظه اصلی را با کمک دیسک و الگوهای مرتبسازی و ادغام مدیریت کنند. مسئله این است که وقتی حجم دادهها از رم سیستم بیشتر میشود، روشهای متکی بر جدول هش داخلی شکست میخورند. راهحل این است که دادهها را قطعهقطعه مرتب کرده و روی دیسک بنویسیم و سپس با ادغامسازی مرتبسازیشده آنها را ترکیب کنیم، دقیقا مشابه همان کاری که ابزار مرتبسازی لینوکس برای مدیریت دادههای عظیم انجام میدهد.
ایدهٔ سوم، معماری نگهداری دادهها روی یک سامانهٔ فایل توزیعشده است. برای پردازش در مقیاس هزاران ماشین، الگوریتمهایی مانند مپردیوز (MapReduce) معرفی شدند که دادهها را روی فایلسیستم توزیعشدهٔ هدوپ HDFS ذخیره میکنند. مسئله اصلی این است که چطور محاسبات را به جایی که داده قرار دارد ببریم تا از بار شبکه کاسته شود. گزینهها شامل استفاده از ذخیرهسازی متمرکز یا معماری بدون اشتراک است. معامله یا تریدآف در اینجا این است که ساختار بدون اشتراک نیازی به سختسافزارهای گرانقیمت شبکه ندارد و محاسبات مستقیماً روی گرههای حاوی داده اجرا میشود. نمونهٔ عینی آن اجرای وظایف مپ و ردیوز روی همان ماشینهایی است که بلوکهای فایل ورودی را ذخیره کردهاند.
ایدهٔ چهارم به چگونگی اجرای پیوستگیها عمل جوین در پردازشهای دستهای مربوط میشود. وقتی بخواهیم لاگ فعالیت کاربران را با پایگاه دادهٔ پروفایل آنها ترکیب کنیم، پرسوجوی تکتک رکوردها از پایگاه دادهٔ از راه دور عملکرد را به شدت پایین میآورد. گزینهٔ بهتر، کپی کردن پایگاه داده در همان فایلسیستم توزیعشده و استفاده از روش جوین کاهشیافته و مرتبسازی sort-merge join است. در این روش، کلیدها توسط مپردیوز مرتب و کنار هم قرار میگیرند تا ردیوز بتواند دادههای مرتبط را بدون درخواستهای شبکه و با کارایی بالا ترکیب کند.
پیشنهاد من این است که وقتی با حجم دادههای تحلیلی کلان سر و کار دارید، به جای وابستگی به تراکنشهای آنلاین و پرسوجوهای منفرد، از الگوهای پردازش دستهای با دادههای تغییرناپذیر استفاده کنید تا قابلیت اطمینان و خطایابی سیستم شما بهشدت افزایش یابد.
در نهایت، این معماریهای توزیعشده به ما یاد میدهند که جداسازی منطق برنامهنویسی از زیرساخت سیمکشی دادهها، کلید ساخت سیستمهای دادهگرا و مقیاسپذیر در دنیاست.
فصل نهم: سازگاری و اجماع
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که اگر در یک پایگاه داده توزیعشده، دو نفر همزمان به اطلاعات متفاوتی نگاه کنند، سیستم چطور باید تصمیم بگیرد کدام نسخه درست است؟ وقتی شبکه با تاخیر مواجه میشود یا گرهها از کار میافتند، هماهنگ نگه داشتن تمام بخشهای یک سیستم بزرگ به یکی از پیچیدهترین چالشهای مهندسی نرمافزار تبدیل میشود.
در کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن»، فصل نهم به بررسی دقیق مفهوم سازگاری (consistency) و اجماع (consensus) میپردازد تا نشان دهد چطور میتوانیم سیستمهای توزیعشدهای بسازیم که در برابر خطاها مقاومت کنند. درک این مفاهیم به ما کمک میکند تا حد و مرز تضمینهایی را که پایگاههای داده میتوانند به ما بدهند به خوبی بشناسیم.
یکی از قویترین مدلهای سازگاری، خطیسازیپذیری (linearizability) است؛ ویژگیای که باعث میشود سیستم طوری رفتار کند که انگار فقط یک نسخه از داده وجود دارد و تمام عملیات بهصورت اتمی روی آن انجام میشوند. مشکل اینجاست که تضمین تازگی دادهها در خطیسازیپذیری هزینه دارد و سیستم را در برابر مشکلات شبکه آسیبپذیر میکند. مثلاً اگر وبسایتی نتواند این تضمین را بدهد، ممکن است کاربری امتیاز مسابقه فوتبال را زودتر از کاربر دیگر روی صفحه ببیند که این موضوع تجربه گیجکنندهای ایجاد میکند.
موضوع مهم دیگر، ترتیب رویدادها و علیت (causality) در سیستمهای توزیعشده است. علت همیشه قبل از معلول اتفاق میافتد و حفظ این رابطه برای جلوگیری از تناقضهایی مثل دیدن پاسخ یک سوال قبل از خود سوال حیاتی است. ترتیبسنجی کامل با استفاده از روشهایی مانند تمبرهای لامپورت (Lamport timestamps) که به هر رویداد یک جفت شمارنده و شناسهٔ گره اختصاص میدهند، کمک میکند تا رویدادها بهگونهای مرتب شوند که با روابط علّی سازگار بمانند.
وقتی صحبت از نوشتن دادهها در میان چند گره میشود، سیستمهای پایگاه داده گاهی به توافق جمعی نیاز دارند که به آن مسئلهٔ تعهد اتمی (atomic commit) میگویند. پروتکل تعهد دو مرحلهای (two-phase commit) تلاشی برای حل این مشکل است که در آن هماهنگکننده ابتدا از همه میپرسد که آیا آمادهٔ ذخیره هستند یا نه، و بعد از گرفتن پاسخ مثبت، فرمان نهایی را صادر میکند. با این حال، اگر هماهنگکننده در میان راه سقوط کند، گرهها در حالت نامطمئن معلق میمانند و قفلها آزاد نمیشوند.
برای حل اساسی این چالشها، سیستمهای اجماع (consensus) وارد میدان میشوند تا به گرهها کمک کنند روی یک تصمیم واحد به توافق برسند. ابزارهایی مثل زوکیپر (Apache ZooKeeper) از الگوریتمهای اجماع برای پیادهسازی پخش ترتیب کامل (total order broadcast) استفاده میکنند؛ به این معنا که همهٔ پیامها دقیقاً با یک ترتیب مشخص به تمام گرهها میرسند تا تکرار دادهها بدون تناقض انجام شود.
پیشنهاد من این است که پیش از پیادهسازی معماریهای پیچیدهٔ توزیعشده، بهدقت بررسی کنید که آیا برنامهتان واقعاً به خطیسازیپذیری سختگیرانه نیاز دارد یا سازگاریهای ضعیفتر و سریعتر برای نیازهای محصول شما کفایت میکنند. انتخاب درست مدل سازگاری میتواند معماری نرمافزار شما را از پیچیدگیهای غیرضروری نجات دهد.
امیدوارم این مرور کوتاه به روشنتر شدن ابعاد سازگاری و اجماع در سیستمهای توزیعشده کمک کرده باشد؛ منتظر نظرات شما در بخش گفتگو هستیم.
فصل هشتم: مشکلات سیستمهای توزیعشده
رایگانتوضیح و منابع این قسمت
وقتی کامپیوتر شخصی شما کاملاً سالم کار میکند یا ناگهان صفحهٔ آبی مرگ را نشان میدهد، تکلیف روشن است؛ اما آیا میدانید چرا ساختن نرمافزار روی چند کامپیوتر متصل به شبکه دنیایی کاملاً متفاوت و پر از ابهام است؟
این موضوع در فصل هشتم کتاب «طراحی برنامههای دادهگرا» اثر «مارتین کلپمن» بررسی شده و نشان میدهد چرا فرض اینکه همهچیز ممکن است خراب شود در سیستمهای توزیعشده یک ضرورت است.
اولین چالش جدی، غیرقابلاعتماد بودن شبکههاست. در سیستمهای توزیعشده، ماشینها از طریق شبکه با هم ارتباط دارند و شبکه تضمینی نمیدهد که پیامی برسد یا چه زمانی برسد. وقتی پیامی میفرستید و پاسخی نمیگیرید، نمیتوانید تشخیص دهید که آیا درخواست شما گم شده، گره مقابل از کار افتاده یا پاسخ در راه برگشت تأخیر داشته است. راه حل معمول استفاده از زمانسنج timeout است؛ اما تعیین یک مقدار ثابت برای آن دشوار است زیرا تأخیرهای شبکه به دلیل ازدحام صفها متغیر هستند.
دومین مشکل به ساعتهای سیستم مربوط میشود. ماشینهای مختلف ساعتهای سختافزاری مستقل دارند که سرعتشان با هم فرق میکند و به مرور زمان دچار انحراف میشود. حتی با پروتکل همگامسازی زمان Network Time Protocol (NTP)، ساعتها ممکن است ناگهان بپرند یا عقب بمانند. استفاده از ساعتهای دیوارنگاره time-of-day clock برای مرتبسازی تراکنشها خطرناک است، زیرا اگر ساعت یک گره جلوتر باشد، ممکن است نوشتههای جدیدتر را به اشتباه حذف کند.
سومین مسئله، وقفههای پردازش process pauses است. یک گره ممکن است وسط اجرای کار به خاطر مکث زبالهروب حافظه garbage collection متوقف شود، بدون اینکه خودش بفهمد. در این فاصله، سایر گرهها فکر میکنند او مرده و گره دیگری را به جایش انتخاب میکنند؛ اما وقتی گره متوقفشده بیدار میشود، همچنان فکر میکند رهبر است و دست به عملیات خطرناک میزند.
چهارمین ایده به حقیقت و اجماع مربوط میشود. در یک سیستم توزیعشده، هیچ گرهی نمیتواند به تنهایی همهچیز را درست تشخیص دهد، چون شبکه و زمان قابلاعتماد نیستند. به همین دلیل تصمیمگیریها به رأیگیری اکثریت quorum نیاز دارد تا مشخص شود کدام گره واقعاً زنده یا رهبر است. برای جلوگیری از خرابکاری گرههایی که به اشتباه فکر میکنند هنوز رهبرند، میتوان از توکنهای حصارکشی fencing tokens استفاده کرد تا سیستم ذخیرهسازی درخواستهای قدیمی با توکن کهنه را رد کند.
پیشنهاد من به عنوان طراح سیستم این است که هیچگاه به شبکه پایدار، ساعتهای دقیق و اجرای بدون وقفه پردازندهها اعتماد نکنید و همیشه فرض کنید بدترین اتفاقات ممکن است رخ دهند.
امیدواریم این بررسی برای شما مفید بوده باشد. تا اپیزود بعدی، پیروز و پایدار باشید.
فصل هفتم: تراکنشها
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که وقتی چند نفر همزمان روی یک حساب بانکی یا یک موجودی کار میکنند، چطور پایگاه داده جلوی قاطی شدن دادهها را میگیرد؟ واقعیت این است که در دنیای سیستمهای داده، شبکهها قطع میشوند، سختافزارها از کار میافتند و درخواستهای همزمان میتوانند کل سیستم را به هم بریزند.
در کتاب «طراحی برنامههای دادهگرا» اثر «مارتین کلپمن»، فصل هفتم به بحث مهم تراکنشها (Transactions) اختصاص دارد و بررسی میکند که چطور این مکانیسم، برنامهنویسی را با پنهان کردن خطاهای جزئی و مسائل همزمانی سادهتر میکند.
برای درک بهتر رفتار تراکنشها، مفهوم ACID مخفف اتمی بودن، سازگاری، ایزولاسیون و دوام مطرح میشود که البته تعاریف تجاری آن گاهی مبهم است. ویژگی Atomicity (اتمی بودن) تضمین میکند که اگر خطایی وسط چند عملیات رخ دهد، کل کار لغو میشود و تغییرات ثبتشده برمیگردند تا برنامهنویس نگران خطاهای جزئی نباشد. مثال بارز آن انتقال وجه است که اگر مرحلهای شکست بخورد، کل تراکنش متوقف میشود.
مشکل بعدی، تداخل تراکنشهای همزمان است که برای حل آن از Isolation (ایزولاسیون) استفاده میشود. سطوح مختلف ایزولاسیون از خواندن متناوب Read Committed شروع میشوند که از کثیفخوانی جلوگیری میکند، اما گاهی به پدیدههایی مثل Lost Update (بهروزرسانی ازدسترفته) منجر میشود. وقتی دو کاربر همزمان روی یک مقدار کار میکنند و یکی تغییر دیگری را بازنویسی میکند، بهروزرسانی ازدسترفته رخ میدهد که پایگاههای داده با قفلگذاری یا عملگرهای اتمی با آن مقابله میکنند.
حتی در سطوح پیشرفتهتر مثل Snapshot Isolation (ایزولاسیون اسنپشات)، که با مکانیزم Multi-Version Concurrency Control (کنترل همزمانی چندنسخهای) به هر تراکنش یک نمای ثابت از دادهها را میدهد، مسائل پیچیدهتری مثل Write Skew (انحراف نوشتن) ممکن است اتفاق بیفتد. انحراف نوشتن زمانی رخ میدهد که دو تراکنش بر اساس یک پیشفرض درست تصمیم میگیرند دادهای را تغییر دهند، اما تداخل همزمان باعث نقض قوانین کسبوکار میشود؛ مثل نمونهٔ پزشکان کشیک که همزمان درخواست مرخصی میدهند.
برای رسیدن به بالاترین سطح اطمینان و جلوگیری از تمام این خطاهای همزمانی، Serializability (سریالپذیری) به کار میرود تا نتیجهٔ اجرای همزمان تراکنشها دقیقاً شبیه اجرای پشتسرهم آنها باشد. سیستمها برای پیادهسازی این سطح از الگوریتمهایی مثل Two-Phase Locking (قفلگذاری دو مرحلهای) یا روشهای خوشبینانه مثل Serializable Snapshot Isolation (ایزولاسیون اسنپشات سریالپذیر) استفاده میکنند که هر کدام هزینهها و مزایای خاص خود را دارند.
پیشنهاد من به عنوان میزبان این است که در طراحی سیستمهای خود، تا جایی که امکان دارد از تراکنشهای کوتاه و مشخص استفاده کنید و پیش از انتخاب سطح ایزولاسیون، دقیقاً بررسی کنید که برنامهٔ شما به چه سطح از سازگاری نیاز دارد.
امیدوارم این بخش از نگاهی به کتاب طراحی برنامههای دادهگرا برایتان مفید بوده باشد و در برنامهسیستمهای بعدیتان به کار بیاید.