هر تیم مهندسی خواهان سیستمی است که بدون دردسر رشد کند. ما ترافیک در حال افزایشِ نرم، سرورهای روان و درآمدی که به تدریج بالا میرود را تصور میکنیم. اما واقعیت زمانی رخ میدهد که یک کمپین بازاریابی ویروسی، موجی از کاربران را روانه میکند، دیتابیس قفل میشود و کسی در ساعت سه صبح با سراسیمگی در حال راهاندازی مجدد سرویسهاست. واکنش غریزی، مقصر دانستن ابزارهاست. ما به خودمان میگوییم که به هستههای بیشتر، دیسکهای سریعتر یا یک لایه کشینگ دیگر نیاز داشتیم. اما رشد از سختافزار حاصل نمیشود، بلکه از ساختار نشأت میگیرد. اگر زیربنای شما نتواند بار را توزیع کند، هر کاربر جدید به جای یک پیروزی، به یک بار اضافی تبدیل میشود.
چرا ابزارها نمیتوانند یک زیربنای معیوب را نجات دهند
شما میتوانید صدها نمونه ابری (cloud instance) راه اندازی کنید، توازنکنندههای بار (load balancers) بین مناطق جغرافیایی اضافه کنید و تمام داراییهای استاتیک را در یک شبکه توزیع محتوای جهانی (CDN) کش کنید. اینها عوامل تقویتکننده هستند؛ اما ضرب کردن در صفر، باز هم نتیجه صفر خواهد بود. یک اپلیکیشن یکپارچه (monolithic) با وابستگیهای درهمتنیده، هر چقدر هم که سختافزار قدرتمندی زیر آن باشد، در نهایت زیر وزن خودش خفه خواهد شد.
یک فروشگاه آنلاین را تصور کنید که کاتالوگ محصولات، پردازش پرداخت و احراز هویت کاربر، همگی در یک کدبیس واحد قرار دارند. وقتی جریان پرداخت کند میشود، کل سایت با کندی شدید مواجه میشود. صفحه ورود دچار لگ میشود و تجربه مرور سایت آسیب میبیند. شما نمیتوانید گلوگاه را بدون مقیاسپذیر کردن بقیه اجزا، گسترش دهید. این کار گران، ناکارآمد و شکننده است. در نهایت، شما برای توان محاسباتی هزینه پرداخت میکنید که به نفع هیچکس نیست، در حالی که کاربران منتظر صفحاتی هستند که باید فوراً بارگذاری میشدند.
معماری، پاسخ به این تله است. معماری همان اسکلت نامرئی است که تعیین میکند آیا ابزارهای شما به شما کمک میکنند یا آسیب میرسانند.
معنای واقعی یک معماری مستحکم چیست
یک معماری مستحکم، صرفاً برنامهای برای تعیین محل قرارگیری مسئولیتهاست. معماری با پرسیدن سوالات دشوار در مراحل اولیه، از بروز مشکلات جلوگیری میکند. اگر یک بخش از سیستم خراب شود چه اتفاقی میافتد؟ آیا میتوانید منطق صورتحساب را بدون دستکاری در موتور پیشنهاددهنده تغییر دهید؟ آیا هجوم ترافیک در یک گوشه از اپلیکیشن میتواند باعث شود بقیه سیستم به حالت عادی خود ادامه دهند؟ این سوالات بسیار مهمتر از انتخاب زبان برنامهنویسی، فریمورک یا ارائهدهنده خدمات ابری هستند.
یک معماری خوب به شما فضا برای تغییر تصمیمات میدهد. معماری مرزهای مشخصی تعریف میکند تا آزمایشهای یک تیم، بار کاری عملیاتی تیم دیگر را بیثبات نکند. معماری با شکست به عنوان یک شرایط عملیاتی عادی برخورد میکند، نه یک اتفاق غافلگیرکننده. وقتی با در نظر گرفتن احتمال شکست طراحی میکنید، دیگر خانههای شیشهای نمیسازید، بلکه ساختارهایی میسازید که انعطافپذیر هستند.
میکروسرویسها به عنوان یک الگوی کاربردی
یک راه عملی برای دستیابی به این نوع ساختار، تقسیم اپلیکیشن به میکروسرویسها است. به جای یک کدبیس غولآسا، اپلیکیشن را به بخشهای کوچک تقسیم میکنید. هر بخش یک وظیفه مشخص را انجام میدهد. سرویس پرداخت، تراکنشها را پردازش میکند؛ سرویس موجودی، موجودی کالا را پیگیری میکند؛ و سرویس اعلان، ایمیلها و پیامکها را ارسال میکند. آنها به جای دسترسی مستقیم به حافظه یا جداول دیتابیس مشترک، از طریق رابطهای (interfaces) تعریفشده با هم ارتباط برقرار میکنند.
این جداسازی، فضای مانور واقعی را هم از نظر فنی و هم از نظر سازمانی ایجاد میکند.
بهروزرسانی قطعات کوچک بدون از کار انداختن کل سیستم
وقتی سرویسها کوچک و متمرکز باشند، میتوانید یک قطعه را بدون ریسکِ شکست زنجیرهای، اصلاح کنید. اگر تیم شما با باگی در الگوریتم محاسبه هزینه ارسال مواجه شد، فقط همان سرویس را اصلاح و به تنهایی مستقر میکنید. بقیه اپلیکیشن به کار خود ادامه میدهد. کاربران همچنان محصولات را مرور میکنند، وارد حساب خود میشوند و کالاها را به سبد خرید اضافه میکنند. محدوده آسیب (blast radius) هر تغییر، بسیار کوچک باقی میماند. این را با یک سیستم یکپارچه (monolith) مقایسه کنید که در آن یک غلط تایپی در یک تابع کمکی میتواند فرآیند پرداخت، ثبتنام و گزارشگیری را همزمان از کار بیندازد.
مقیاسپذیری توابع خاص هنگام افزایش ترافیک
ترافیک در یک اپلیکیشن هرگز یکنواخت نیست. در طول یک فروش ویژه (flash sale)، ممکن است خط لوله سفارشات شما تحت فشار باشد، در حالی که سیستم مدیریت محتوا تقریباً بیکار است. در یک سیستم با وابستگیهای شدید (tightly coupled)، یا همه چیز را مقیاسپذیر میکنید یا هیچکدام را. اما با میکروسرویسها، منابع خود را دقیقاً هدف قرار میدهید. نمونههای بیشتری از سرویس پرداخت راه میاندازید و اجازه میدهید کاتالوگ محصولات با همان ظرفیت همیشگی خود کار کند. در زمان عرضه یک محصول جدید، ممکن است پردازشگرهای تصاویر شما هزاران تصویر بندانگشتی را در صف قرار دهند، در حالی که ایندکس جستجو آرام است. دلیلی ندارد که کلاستر جستجو را فقط برای پاسخگویی به پردازشگرهای تصویر گسترش دهید. شما پول خود را دقیقاً جایی خرج میکنید که کاربران آن را حس میکنند و سیستم شما تحت فشار، پاسخگو باقی میماند.
استقرار کد جدید بدون زمانهای توقف طولانی
سرویسهای کوچک الگوهای استقراری را ممکن میسازند که پنجرههای تعمیر و نگهداری را از رده خارج میکنند. شما میتوانید از استقرارهای غلتان (rolling deployments) استفاده کنید؛ یعنی کد جدید را به زیرمجموعهای از نمونهها (instances) ارسال کنید در حالی که بقیه همچنان به سرویسدهی ترافیک ادامه میدهند. نرخ خطاهای خود را زیر نظر داشته باشید و اگر چیزی مشکوک به نظر رسید، درخواستها را در عرض چند ثانیه به نسخه قبلی برگردانید. استقرارهای آبی-سبز (blue-green deployments) به شما اجازه میدهند یک محیط کاملاً جدید را راهاندازی، تأیید و با حداقل ریسک، ترافیک را به آن منتقل کنید. سیستم نیازی ندارد که ساعتها از دسترس خارج شود تا کسی بتواند مهاجرتهای پایگاه داده (database migrations) را به صورت دستی انجام دهد.
ساخت ویژگیهای جدید با سرعت بیشتر
پایگاههای کد بزرگ باعث ایجاد احتیاط بیش از حد میشوند. یک تغییر کوچک مستلزم درک هزاران خط از منطقهای بیارتباط، تستهای رگرسیون (regression tests) که ساعتها طول میکشند و برنامههای استقراری است که شبیه به پرتاب موشک هستند. سرویسهای کوچک این ترس را از بین میبرند. یک تیم میتواند با تغییر دادن چند صد خط در سرویسی که به خوبی آن را میشناسد، یک ویژگی جدید بسازد. آنها در همان روز کد را کامیت، تست و عرضه میکنند. این سرعت به صورت مرکب افزایش مییابد. وقتی سرویسها با مسئولیتهای مشخص محدود شده باشند، تیمها دیگر در کار یکدیگر دخالت نمیکنند. آنها مالکیت دامنه خود را از ابتدا تا انتها بر عهده دارند.
استقلال از اختلالات بزرگ جلوگیری میکند
هر سرویس به تنهایی کار میکند. این استقلال صرفاً یک راحتی سازمانی نیست، بلکه یک بیمه ساختاری است. اگر موتور پیشنهاددهنده از کار افتاد، فروشگاه همچنان باید محصولات را بفروشد. اگر خط لوله تحلیل (analytics pipeline) در برابر یک رویداد بدشکل دچار مشکل شد، سرویس ورود (login service) همچنان باید کاربران را احراز هویت کند. شما بین سرویسها قطعکنندهها (circuit breakers) و مسیرهای جایگزین (fallback paths) طراحی میکنید تا یک شکست به یک قطعی کامل تبدیل نشود. سیستم در کنار کاربران شما رشد میکند، زیرا میتواند فشار را بدون از هم پاشیدن ساختار خود، جذب کند.
یک نکته احتیاطی: کورکورانه تقسیم نکنید
هیچکدام از اینها به این معنا نیست که باید از روز اول پایگاه کد خود را تکهتکه کنید. میکروسرویسها نیازمند مرزهای مشخص هستند. اگر تیمهای شما هنوز نمیدانند یک دامنه کجا تمام میشود و دامنه دیگر از کجا شروع میشود، به جای یک سیستم توزیعشده، یک آشفتگی توزیعشده ایجاد خواهند کرد. شما پیچیدگی کد را با پیچیدگی عملیاتی معامله خواهید کرد و ناگهان درگیر مدیریت تأخیر شبکه (network latency)، تراکنشهای توزیعشده (distributed transactions)، طوفانهای تلاش مجدد (retry storms) و مشاهدهپذیری (observability) در دهها جریان لاگ خواهید بود. عیبیابی یک فرآیند پرداخت کند میتواند به معنای ردیابی یک درخواست واحد در میان چهار پرش شبکه و سه ذخیرهساز داده مختلف باشد.
اگر تیم شما برای پرداخت این هزینه آماده نیست، درمان بدتر از بیماری است. گاهی اوقات حرکت هوشمندانهتر این است که با یک مونولیت ماژولار (modular monolith) شروع کنید. منطق پرداخت را از منطق موجودی در داخل پایگاه کد جدا نگه دارید، حتی اگر با هم مستقر شوند. مرزها را با APIهای داخلی و طرحوارههای پایگاه داده (database schemas) مجزا در همان موتور اعمال کنید. وقتی آن مرزها پایدار شدند و الگوهای ترافیک هزینهی اضافی را توجیه کردند، یک سرویس را استخراج کنید. معماری باید مجموعهای از درهای تعمدی باشد، نه دیوارهایی که یکشبه به دلیل خواندن یک پست وبلاگ بنا شدهاند.
با هدفمندی شروع کنید
معماری مستحکم به معنای پیشبینی ترافیک برای پنج سال آینده نیست، بلکه به معنای ایجاد گزینههای مختلف برای خودتان است. شما نمیتوانید برای رشد اپلیکیشن وب خود تنها به ابزارها تکیه کنید، اما میتوانید قبل از بالا گرفتن فشار، با تفکر راه حل پیدا کنید. به مرزهای بین مسئولیتها احترام بگذارید. بخشهای کوچک و متمرکزی بسازید که سرنوشت خود را مدیریت کنند. به تیمها خودمختاری بدهید تا بدون از کار انداختن کل سیستم، سریع حرکت کنند. وقتی با یک معماری مستحکم شروع میکنید، بعداً در زمان و تلاش صرفهجویی میکنید، زیرا در حالی که سایت در حال سوختن است، مجبور به بازنویسی منطق اصلی نخواهید بود.
نکته اصلی
مقیاسپذیری ویژگیای نیست که وقتی رشد فرا رسید، آن را به سیستم اضافه کنید. بلکه نتیجه طبیعی انتخابهایی است که در ابتدا درباره نحوه جریان یافتن مسئولیتها در سیستم خود انجام دادهاید. مرزهای درست را انتخاب کنید. خطا را ایزوله کنید. آنچه مشکلساز است را مقیاسپذیر کنید و آنچه به درستی کار میکند را رها کنید. با انجام این کار، ابزارهایی که بعداً اضافه میکنید، واقعاً چیزی مستحکم برای تکیه کردن خواهند داشت.
