چگونه مایکروسرویس‌هارا شناسایی کنیم

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

شما شرکت X نیستید.

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

سری که درد نمی‌کند دستمال نمی‌بندند.

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

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

اول راه

این سوال که «آیا درسته از اول پروژه را به صورت مایکروسرویس شروع کرد؟» را از خیلی‌ها شنیده‌ام. جواب من این است که تنها به یک شرط:‌ اگر شما یک گوی‌جادو یا آینه سحرآمیز در اختیار دارید که آینده را به شما می‌گوید پس مشکلاتی که در آینده به آن برخواهید خورد را پرسید و اگر با مایکروسرویس حل میشد پروژه را از همان ابتدا با مایکروسرویس شروع کنید ولی اگر مثل من یک آدم عادی بدون superpower هستید خیر.

و در نظر داشته باشید از مونولیتیک به مایکرسرویس رفتن قرار نیست یکباره اتفاق بیفتد. big bang rewrite یعنی از اول نوشتن کل محصول برای من کار بسیار ترسناکی است که هیچ وقت سمتش نخواهم رفت. (اما مثال هایی مانند دیجیکالا هستند که به خوبی این کار را انجام داده اند)

پس فرض ما در این وبلاگ این است که پروژه‌ای بزرگ داریم که چند وقتی است از انتشار آن گذشته است. در ادامه به مشکلاتی که مایکروسرویس می‌تواند آنها را حل کند می‌پردازیم.


۱- تحمل بار

وقتی مشتری‌های یک محصول آنلاین زیاد می‌شود، باید در لحظه به کاربرهای همزمان بیشتری پاسخ داد. این موضوع یعنی استفاده از منابع محاسباتی بیشتر. خب اولین و ساده ترین راه زیاد کردن منابع سرور است. یعنی برای مثال محصول شما تا قبل روی یک سرور با ۸ گیگ حافظه مستقر بود و با زیاد شدن کاربرها شما محصول را به سروری قوی تر مثلا با ۸۰ گیگ رم انتقال می‌دهید. با زیاد تر شدن مشتری‌ها ارتفاع به سرور بزرگ‌تر به صرفه نیست چرا که سرورهای خیلی بزرگ نایاب هستند. چاره‌ی شما این است که محصول را روی چند سرور مستقر کنید. برای مثال به جای اینکه سروری با ۴۰۰ گیگ رم خریداری کنید ۴ سرور دیگر با همان ۸۰ گیگ رم خریداری می‌کنید و روی هر کدام یک کپی از محصول قرار می‌دهید و با ابزارهایی مثل nginx یا haproxy بار را بین آنها تقسیم کنید. در اصطلاح به این کار scale out گفته می‌شود.
خب حالا فرض کنیم این دو تا کار را انجام دادیم ولی باز هم در لحظه‌های پیک ترافیک محصول دچار اختلال می‌شود. اکثرا بخشی از کد وجود دارد که نسبت به بقیه بار خیلی بیشتری را تحمل می‌کند. اگر آن بخش را شناسایی کنیم و به صورت یک سرویس مستقل از بقیه پروژه جدا کنیم به صورتی که حداقل وابستگی را به کل پروژه داشته باشد باعث می‌شود در لحظه‌ی پیک فقط آن سرویس از دسترس خارج شود و بقیه محصول سالم بماند. حتی می‌شود این مایکروسرویس را روی یک سرور دیگر مستقر کرد تا در لحظه‌های اوج ترافیک بقیه سرویس ها کاملا ایزوله باشند و دچار اختلال نشوند. برای مثال قابلیت جستجو در یک فروشگاه خیلی بیشتر از خرید استفاده می‌شود. حال اگر جستجو یک مایکروسرویس باشد و پروژه زیر بار برود سایت به درستی باز می‌شود و خرید محصول با مشکلی رو به رو نخواهد شد. فقط جستجو دچار مشکل می‌شود.
البته دقت کنید که مایکروسرویس جدید شما نباید به ازای هر درخواست از دیتابیس مشترک با پروژه اصلی استفاده کند یک راه حل استفاده از cache است و راه دیگر جدا کردن دیتابیس ها و تکرار اطلاعات مورد نیاز در دیتابیس جدید. همینطور مایکروسرویس جدید نباید به ازای هر درخواست پروژه اصلی را از طریق api فراخوانی کند چرا که همان باری که قرار بود مایکروسرویس جدید تحمل کند روی بقیه پروژه تاثیر می‌گذارد. راه حل این مشکل استفاده از task queue به جای استفاده از پروتکلی مانند http یا rpc است. ابزاری مثل Kafka یا RabbitMQ پیام‌ها را در خود ذخیره می‌کنند و میکروسرویس مقصد در فرصت مناسب آن‌هارا پردازش می‌کند. با این کار زیر بار رفتن اولی باعث فشار آمدن به میکروسرویس دوم نمی‌شود.

۲- بهینه سازی خواندن/نوشتن

این مورد بسیار شبیه مورد قبلی است ولی از زاویه متفاوتی به قضیه نگاه شده است. فرض کنید اطلاعاتی در محصول خود دارید که نرخ خواندن آن نسبت به نوشتن آن بسیار متفاوت است برای مثال اطلاعاتی هست که در روز ۱۰ بار تغییر می‌کنند ولی در ثانیه ۱۰۰۰ بار خوانده می‌شوند. برای مثال در سایتی مثل دیوار، خیلی بیشتر از ثبت آگهی استفاده می‌شود. این مسئله می‌تواند برعکس باشد برای مثال ۱۰۰۰ بار در ثانیه اطلاعات تغییر میکند ولی فقط ۱۰۰ بار در روز خوانده می‌شود مثالش می‌تواند ذخیره اطلاعات رفتارهای کاربر‌ها باشد. مثلا میخوایم وقتی کاربری از محصولی بازدید کرد جایی ذخیره شود تا مدیر محصول بتواند آمار دقیقی از کاربر ها داشته باشد. مدیر محصول شاید فقط روزی یک بار به این اطلاعات نیاز پیدا کند.
در این شرایط الگویی معروفی به اسم Command-Query Responsibility Segregation وجود دارد. یعنی جداسازی بخش نوشتن و خواندن اطلاعات. در مثال‌های گفته شده نوشتن و خواندن یک داده تفاوت زیادی دارند و احتمالا ما می‌خواهیم از تکنولوژی‌های مختلفی برای این دو استفاده کنیم. مثلا وقتی نرخ نوشتن اطلاعات بالاست دوست داریم آن را روی مموری ذخیره کنیم تا سریع باشد یا آن را روی kafka قرار دهیم تا بعدا پردازش شود. یا وقتی نرخ خواندن بالاست می‌خواهیم از ابزار هایی مثل memcache یا varnish استفاده کنیم. به مرور تفاوت این دو بخش و دغدغه‌های آن زیاد و زیاد تر خواهند شد که برای راحتی در توسعه و تمیزتر شدن کدها مجبوریم این دو را به عنوان دو مایکروسرویس جدا بنویسیم.

۳- استفاده از یک زبان دیگر

در مجموع من با بزرگ کردن استک تکنولوژی موافق نیستم چرا که در بلند مدت از نظر منابع انسانی به پروژه ضربه‌هایی وارد میکند. ولی فرض کنید مجبور هستید بخشی از پروژه خود را با زبانی دیگر بنویسید. برای مثال پروژه شما با php نوشته شده اما در بخشی نیاز به یک کتابخانه هوش‌مصنوعی دارید که فقط در زبان پایتون است و هیچ مشابهی در php ندارد. یا خرید سرور بیشتر برای شما دیگر به صرفه نیست و برای افزایش کارایی بخشی از پروژه که همروندی زیادی دارد را می‌خواهید با Go یا Erlang باز نویسی کنید. اگر بستر مایکروسرویس در پروژه‌ی شما وجود داشته باشد جدا کردن یک بخش و بازنویسی آن با یک زبان جدید کار بسیار راحتی خواهد بود. برای خود من زبان پایتون برای توسعه یک محصول جدید اولویت دارد چرا که بسیار راحت است و با کتابخانه‌ها و چهارچوب‌هایی که دارد توسعه را خیلی سریع می‌کند. این در حالیست که پایتون در لود بالا نمی‌تواند خوب عمل کند که انتخاب من برای هندل کردن درخواست‌های زیاد زبان گو است. ولی توسعه یک محصول با زبان گو بسیار وقتگیر است. استفاده از مایکروسرویس این آزادی را می‌دهد که هر بخش برنامه با یک زبان نوشته شود.

۴- استفاده از کدهای قدیمی

تعداد پروژه‌هایی که چندین سال است در حال توسعه هستند بسیار کم است. در این پروژه‌ها قسمت‌هایی وجود دارد که معمولا همه برنامه نویس ها از دست زدن به آن واهمه دارند. یک کد بسیار بزرگ و پیچیده که معمولا از پروتکل‌های قدیمی استفاده می‌کند مانند SOAP یا RMI. اگر در چنین پروژه‌ای قصد بازنویسی یا ریفکتور کردن را بگیرید شاید وجود بخش های بزرگ و پیچیده شما را منصرف کند. در این شرایط یکی از راه‌های متداول این است که بخشی از پروژه به عنوان یک سرویس مستقل بازنویسی شود و بخش قدیمی و جدید می‌توانند از طریق api با هم ارتباط برقرار کنند. مثلا پروژه کافه‌بازار کدهایی دارد که تاریخ کامیت آنها برای ۸ سال پیش است که بازنویسی کامل آن به صرفه نیست.

۵- تیم های محصولی

خیلی از برنامه نویس‌ها دوست دارند به تنهایی روی محصول کار کنند. معمولا بین برنامه نویس‌های تازه کار خیلی دیده می‌شود چون کار تیمی برایشان سخت است. از نظر خود من برنامه‌نویسی خیلی به نقاشی شباهت دارد و فکر نمی‌کنم چند نقاش با هم بتوانند تابلوی زیبایی را بکشند. مباحث مهندسی‌نرم افزار هم بیشتر راجع به همین موضوع است که چطور برنامه نویس‌ها بتوانند با هم کنار بیایند و روی یک محصول کار کنند. مواردی مثل git workflow و code style و design pattern و هزاران مورد دیگر به ما کمک می‌کنند تا راحت‌تر با هم کار کنیم. ولی قبول کنید که این هم حدی دارد. هرچه تعداد برنامه نویسان یک محصول بیشتر می‌شوند چابکی کمتر می‌شود باگ‌ها افزایش پیدا می‌کند و در نهایت محصول دچار لختی‌ای می‌شود که توسعه‌ی آن کار بسیار سخت و زمان‌بری خواهد شد و تعداد زیادی نفر فقط برای نگهداری از پروژه وقت خود را صرف می‌کنند.
خب اگه بیایید کامپوننت‌های پروژه را تک به تک از پروژه اصلی جدا کنید و به عنوان مایکروسرویس از آن استفاده کنید باعث می‌شود بتوانید تیم را شکسته و تعداد کسانی که روی یک پروژه کار میکنند را کم کنید.
این مدل شکاندن پروژه از نظر من چالشی‌ترین حالت است. اول باید دقت کنید مایکروسرویس جدید کمترین وابستگی را به بقیه پروژه داشته باشه. یک مفهومی مطرح می‌شود به اسم bounded context که بد نیست راجع به آن مطالعه کنید. یادتان نرود هدف ما این بود که تیم ها را جدا کنیم پس اگه دوتا مایکروسرویس به هم وابستگی داشته باشند ما از هدفمان دور شده‌ایم چرا دو تیم جدا سر هر موضوعی مجبور می‌شوند با تیم دیگر همکاری کنند که در این حالت به جای آسان کردن روابط آن را دو چندان کرده‌ایم. این دو مایکروسرویس حتی می‌توانند ادبیات جدا از خودشان را داشته باشند مثلا یک سایت فروش لباس می‌تواند یک مایکروسرویس پرداخت داشته باشد و لزومی نباشد هیچ کدام از برنامه‌نویس‌های تیم پرداخت از اینکه چه چیز به فروش می‌رسد اطلاع داشته باشند. در این حالت تصمیمات محصولی تیم نمایش لباس به تیم فروش تاثیری نخواهد گذاشت. مسئله دوم راجع به این مدل از شکاندن پروژه این است که معمولا باعث افت پرفورمنس (بعضی موقع ها شدید) می‌شود. سعی بر این است که یک user story یا به بیان دیگر یه business feature تنها توسط یک مایکروسرویس انجام شود ولی خب زیاده روی در این اصل هم باعث افزایش کد های تکراری و دیتای تکراری می‌شود.

حرف آخر

آینده را پیشبینی نکنید! این دام بزرگی است که خیلی ها گرفتارش می‌شوند (از جمله خود من). هر وقت به مشکلی بر خوردید دنبال راه حل باشید. و تمام افکارتون راجع به اینکه این پروژه قراره تو آینده بار زیادی بیاد روش یا برنامه‌نویس‌های زیادی قراره روش کار کنند را بریزید دور (خیلی دور). در پروژه آریو ما با ۳ برنامه‌نویس بکند، پروژه را ۱۱ میکروسرویس محصولی شکسته بودیم که با دیدن سربار زیاد آن مجبور شدیم تعدادی از مایکروسرویس‌ها را ترکیب کنیم و تعداد را به ۷ مایکروسرویس برسانیم. جمله‌ای معروفی از دونالد نوت در کتاب «برنامه نویسی کامپیوتر به عنوان یک هنر» می‌گوید «Premature optimization is the root of all evil». بهینه سازی یک پروژه نابالغ ریشه تمام مشکلات است.

همونطور که اول گفتم با bigbang rewrite کردن پروژه مخالفم. از مونولیتیک به مایکروسرویس رفتن کار یک شبه نیست. هر وقت با مشکلات مونولیتیک مواجه شدید تک به تک آنها را حل کنید. شاید وسط راه حس کردید مایکروسرویس به دردسرش نمی‌ارزد.

نکته آخر اینکه مایکروسرویس بودن زیرساخت خاص خودش را نیاز دارد. هرچه تعداد مایکروسرویس‌ها بیشتر می‌شود مدیریت آنها وقت بیشتری خواهد برد. ابزارهای زیادی هست که به ما کمک میکنند مثل داکر یا کوبرنیتیز. درباره‌ی آنها تحقیق کنید و از هر کدام که کار شما را راحت می‌کرد (توجه کنید که همه آنها نه! فقط آنهایی که به درد شما می‌خورد) استفاده کنید.