برای سالیان زیادی ما در حال ساخت سیستم هایی بودیم و در این کار بهتر میشدیم. تعدادی فناوری، الگوی های معماری و روش های ایده آل ( بست پرکتیس ها ) در طول سالیان ظهور پیدا کردند. مایکروسرویس یکی از همان الگو های معماری هست که از دنیای طراحی دامنه محور ( Domain driven design )، continuous delivery، خودکار سازی زیرساخت و پلتفرم ها، سیستم های اسکیلبل، polyglot programming و ماندگاری ظهور پیدا کرده است.
رابرت مارتین عبارت "اصل تک مسعولیتی ( single responsibility principle )" را ابداع کرد که توضیح میدهد که "تمام چیز هایی که به یک دلیل تغییر میکنند را کنار هم جمع کنید و چیز هایی که به دلایل مختلف از هم جدا میشوند را از هم جدا کنید."
معماری مایکروسرویس نیز همین روش را در پیش میگیرد و آنرا به سرویس هایی که پیوستگی محکمی ندارند توسعه میدهد. این سرویس ها میتوانند به تنهایی توسعه داده شده و راه اندازی و نگهداری شود. هر کدام از این سرویس ها مسعولیت یک تسک ( وظیفه ) مجزا را دارند و به وسیله API های ساده با هم ارتباط برقرار میکنند تا به کمک هم مشکل پیچیده و بزرگی را حل کنند.
به دلیل اینکه سرویس های تشکیل دهنده کوچک هستند میتوان آنها را از ابتدا به صورت جدا شده با مرز های سرویسی( منظور مرز وظیفه ای است، اینکه هر سرویس چه وظیفه ای دارد )، با یک یا دو تیم کوچک ساخت که به معنای این است در صورت نیاز میتوان به راحتی سرعت توسعه را بالا برد.
موقعی که توسعه به اتمام رسید میتوان این سرویس ها را به صورت مستقل راه اندازی ( deploy ) کرد. با این روش میتوان به راحتی سرویس های زیر بار بالا را شناسایی و آنها را مستقل از کل اپلیکیشن اسکیل کرد (منابع بیشتری اختصاص داد ). مایکروسرویس ها همچنین در جداسازی خطا بهتر عمل میکنند. اگر در یک سرویس خطایی رخ دهد کل اپلیکیشن متوقف نمیشود. زمانی هم که خطا برطرف شد تنها یک سرویس دوباره دیپلوی میشود نه تمام اپلیکیشن.
مزایا بعدی این است که شما میتوانید بهترین تکنولوژی ها ( زبان برنامه نویسی، دیتابیس و ... ) برای اون سرویس استفاده کنید به جای این که از یک زبان برای کل اپلیکیشن استفاده کنید.
امیدوارم متقاعد شده باشید که معماری مایکروسرویس مزایای منحصر به فردی نسبت به معماری مرسوم دارد و دارید فکر میکنید که این روش را برای پروژه بعدی خودتون استفاده کنید.
سوال بعدی اینه که "چطوری شروع کنم؟" و "آیا اصول استانداردی برای رعایت و استفاده وجود دارد که به من کمک کند به روش بهتری معماری مایکروسرویس را بسازم؟"
متاسفانه جواب "نه" است.
با این که ممکنه این جواب نوید بخش نباشد، اما چندین مضامین مشترک وجود دارد که بسیاری از سازمان هایی که معماری مایکروسرویس را استفاده کردند، اقتباس کردند که موفقیت بزرگی هم داشته اند. تعدادی از این مضامین را بحث می کنیم:
یک روش که کار مارو راحت میکنه اینه که سرویس ها را با توجه به قابلیت های تجاری تعریف کنیم. یک قابلیت تجاری یک چیزی هست که یک کسب و کار انجام میده تا به کاربران ارزش هایی ارائه بده.
شناسایی این قابلیت های تجاری و سرویس های مرتبط نیاز به درک بالایی از آن کسب و کار ( business ) دارد. برای مثال قابلیت های تجاری اپلیکیشن یک فروشگاه آنلاین به این صورت است:
· مدیریت کاتالوگ محصولات
· مدیریت فهرست دارایی ها
· مدیریت خرید ها
· مدیریت ارسال و تحویل
· مدیریت کاربران
· پیشنهاد دهی محصولات
· مدیریت نظرات محصولات
وقتی قابلیت های تجاری شناسایی شدند، میتوان سرویس مورد نیاز را با توجه به این قابلیت های شناسایی شده ساخت. هر کدام از این سرویس ها میتواند در اختیار یه تیم جدا که متخصص حوزه آن سرویس و تکنولوژی های مرتبط با آن حوزه هستند، باشد. این باعث میشود تیم ها و API ها پایدار تر باشند.
بعد از مشخص کردن مرز های سرویس ها، یک یا دو تیم کوچک میتوانند آنها را با مناسب ترین تکنولوژی های آن حوزه بسازند. برای مثال، شما ممکن است تصمیم بگیرید سرویس مدیریت کاربران را با جاوا و MySQL بسازید و سرویس پیشنهاد دهی محصولات را با Scala/Spark.
زمانی که راه اندازی شد، خطوط CI/CD را میتوان با هر سرور در دسترس (Jenkins, TeamCity, GO) تنظیم کرد تا تست کیس ها به طور اتوماتیک اجرا شوند و این سرویس ها به طور مستقل به محیط های متفاوت دیپلوی شوند.
در هنگام طراحی سرویس ها، آنها را با دقت مشخص کنید و به این فکر کنید که چه چیز هایی افشا میشوند یا از چه پرتکل هایی برای ارتباط با سرویس استفاده میشود و ... .
خیلی مهم است که پیچیدگی ها و جزئیات پیاده سازی سرویس مخفی باشند و تنها چیز هایی که کاربران سرویس به آن نیاز دارند افشا شوند. اگر جزئیات اضافی افشا شوند، در آینده تغییر در آن سرویس سخت میشود چون کار پر زحمتی خواهید داشت تا بفهمید که چه کسی در کدام اجزای مختلف سرویس اتکا میکند. به علاوه، بخش بزرگی از انعطاف در دیپلوی مستقل از بین خواهد رفت. نمودار زیر یکی از اشتباهات متداول در طراحی مایکروسرویس ها را نشان میدهد:
همونطور که در این نمودار میبینید، ما سرویسی به نام سرویس یک داریم که اطلاعات مورد نیاز سرویس را در دیتابیس ذخیره میکنیم. در حالی که سرویس دیگری به نام سرویس دو ساختیم که همان اطلاعات را نیاز دارد، ما به صورت مستقیم به اطلاعات روی دیتابیس دسترسی داریم.
این روش ممکنه در بعضی موارد منطقی به نظر بیاد — ممکنه دسترسی یا نوشتن داده روی دیتابیس SQL راحت باشه یا شاید API های مورد نیاز سرویس دو به راحتی در دسترس نباشه.
از لحظه ای که این روش استفاده بشه، کنترل ما روی اینکه مشخص کنیم چه چیزی مخفی و یا نمایان هست از بین میره. در آینده اگر که طرح دیتابیس ( schema ) نیاز به تغییر داشته باشه، انعطاف برای تغییر از بین رفته چون شما نمیدونید که چه کسی از دیتابیس استفاده میکنه یا تغییرات باعث به مشکل خوردن سرویس دو میشه یا نه.
یک روش جایگزین این است: ( من روش درست برای استفاده از این را خواهم گفت )
سرویس دوم باید به سرویس اول دسترسی پیدا کند و از ارتباط مستقیم با دیتابیس خودداری کند، تا بیشترین انعطاف برای تغییرات مورد نیاز در اسکیما بدست آید. نگرانی برای دیگر بخش های سیستم از بین میرود تا مطمئن شوید تست های API های افشا شده موفق میشند.
همانطور که گفته شد با دقت پروتکل های ارتباط با سرویس را انتخاب کنید. برای مثال، اگر جاوا RMI انتخاب شده، نه تنها کاربر API محدود به تنها استفاده از زبان های JVM شده، اما در اضای آن، پروتکل کاملا شکننده میشود برای اینکه سازگاری با ورژن های قبلی سخت میشود.
در آخر، در هنگام ارائه کتابخانه کاربران به کاربر به خوبی و با دقت فکر کنید، چون بهتره که کد های ادغام تکرار نشوند. اگر به این اشتباه مرتکب شدید، ممکنه تغییر دادن API خیلی محدود بشه به خاطر اینکه کاربران بر روی جزئیات اضافی تکیه زدند.
سازمان های وجود دارد که در استفاده از مایکروسرویس ها موفق بودند و مدلی رو دنبال کردند که تیم ها سرویس را میسازند و هر چیز مرتبط با اون سرویس رو هم بر عهده میگیرند و انجام میدهند. آنها توسعه، دیپلوی، نگهداری و پشتیبانی را انجام میدهند. هیچ تیم پشتیبانی و نگهداری جداگانه ای وجود ندارد.
راه دیگری برای رسیدن به همان نتیجه داشتن مدل باز متن داخلی است. در این روش، توسعه دهنده ای که به تغییراتی در سرویس نیاز دارد میتواند کد را ببیند و بخواند، روی ویژگی جدیدی کار کند و خودش یک Pull Request ارسال کند به جای اینکه صبر کند تا مالک سرویس روی ویژگی جدید کار کند.
برای انکه این مدل به درستی کار کند، مستندات فنی در کنار روش نصب و راهنما برای هر سرویس نیاز است تا هر کسی بتواند روی سرویس کار کند.
یک مزایا مخفی دیگر این روش این است که توسعه دهنده را روی نوشتن کد با کیفیت متمرکز میکند چون دیگران هم این کد را خواهند دید.
تعدادی الگو معماری برای کمک به غیر متمرکز کردن چیز ها وجود دارد. برای مثال، شما ممکن است معماری داشته باشید که دسته ای از سرویس ها یه وسیله یک باس مرکزی پیام به یکدیگر ارتباط برقرار میکنند:
این باس مسیریابی پیام ها از سرویس های مختلف را هندل میکند. بروکر های پیام مثل ربیت کیو مثال های خوبی هستند.
چیزی که معمولا اتفاق میافته اینه که مردم شروع میکنن به اضافه کردن بیشتر منطق به این باس مرکزی و این باس اطلاعات بیشتری از دامنه شما خواهد دانست. همینطور که باهوش تر میشود ( منظور همونه که اطلاعات بیشتری میگیره ) ممکنه مشکلاتی را بوجود آورد. تغییراتی که نیاز به مختصات در تیم های مختلف میشود، نیز سخت تر میشود.
توصیه عمومی من برای همچین انواع معماری اینه که "احمق" نگهشون دارین و تنها مسیریابی را به اون ها بدین. معماری های بر اساس Event نیز در این سناریو ها خوب عمل میکنند.
مهمه که قرارداد های consumer driven برای هر API که استفاده میشه، نوشته بشود. این برای اینه که اطمینان حاصل بشه تغییرات جدید در API آنرا خراب نمیکند.
در قرارداد های Consumer Driven هر استفاده کننده انتظارات خود از ارائه دهنده را در قرارداد جدایی نشان میدهند. تمام این قرارداد ها با ارائه دهنده به اشتراک گذاشته میشوند تا ارئه دهنده مطلع باشد که چه وظایفی باید در مقابل هر مشتری انجام دهند.
قرارداد های Consumer Driven قبل از دیپلوی شدن و انجام هر تغییر در API، باید کاملا تایید شوند. این همچنین به ارائه دهنده این امکان را میدهد که بداند چه سرویس هایی و چگونه بر آن متکی خواهند شد.
در دیپلوی کردن سرویس های مستقل دو مدل متداول دیگر نیز وجود دارد:
چندین مایکروسرویس میتوانند روی یک سیستم عامل دیپلوی شوند. با این مدل با اتوماتیک کردن چندین چیز در زمان صرفه جویی میشود. مثلا، برای هر سرویس نیاز به ارائه میزبان نیست.
عیب این روش این هست که اسکیل کردن سرویس هارا به صورت مستقل محدود میکند. همچنین مدیریت Dependency ها را سخت تر میکند. برای مثال، تمام سرویس های روی یک میزبان فقط از یک ورژن جاوا میتوانند استفاده کنند. همچنین هر کدام از این مایکروسرویس ها میتوانند برای دیگر مایکروسرویس ها مشکلات ناخواسته ای ایجاد کنند که بازسازی و حل این مشکل ها سخت است.
بخاطر مشکلات بالا مدل دومی که یک مایکروسرویس در هر سیستم عامل هست دیپلوی میشود، ترجیه داده میشود.
با این روش هر سرویس جدا تر است که مدیریت دیپندنسی ها و اسکیل را آسان تر میکند. اما ممکن است از خود بپرسید "آیا این روش منابع بیشتری مصرف نمیکند؟" خب، نه واقعا!
راه حل مرسوم حل این مشکل استفاده از Hypervisor ها است. که چندین ماشین مجازی روی یک میزبان پیش بینی میشوند. این روش ممکنه به صرفه نباشه چون خود Hypervisor منابعی رو مصرف میکند و هر چقدر ماشین های مجازی بیشتری پیش بینی شوند منابع بیشتری استفاده میشوند. و اینجا جاییه که مدل کانتینری روش ترجیه داده شده است. داکر یکی از پیاده سازی های این روش است.
یک مشکل متداول دیگر که با مدل مایکروسرویس به آن بر میخوریم اینه که مشخص کنیم چگونه تغییرات توی API های موجود کنیم در حالی که سرویس در پروداکشن است و از آن استفاده میشود. تغییر در API ها ممکنه مایکروسرویس رو با مشکل رو به رو کند.
راه های مختلفی برای حل این مشکل وجود دارد.
ابتدا، API خود را ورژن بندی کنید و وقتی که نیاز به تغییر در API به وجود آمد، تغییرات جدید را با ورژن جدید دیپلوی کنید و API ورژن قبل را نگه دارید. سرویس های متکی به این API میتوانند بعد ها خود را به ورژن جدید ارتقا کنند. وقتی تمام سرویس ها به ورژن جدید API ارتقا یافتند میتوان ورژن قدیمی را خارج کرد.
یک مشکل این روش این است که نگهداری ورژن های مختلف سخت است. هر تغییر جدید و رفع باگ ها باید در هر ورژن انجام شود.
به همین دلیل، روش جایگزینی نیز وجود دارد. برای تغییرات اعمال شده یک اندپوینت جدا در همان سرویس ایجاد میکنیم. وقتی تمام سرویس ها به اندپوینت جدید ارتقا یافتند میتونید اندپوینت قدیمی رو حذف کنید.
مزایا این روش اینه که نگهداری سرویس راحت تره چون تنها یک ورژن از API وجود داره.
وقتی که تعدادی تیم چندین سرویس را به صورت مستقل به عهده میگیرند، بهتر است که چند استاندارد و Best Practice تعیین شود – برای مثال هندل کردن ارور ها.
همانطور که مورد انتظار ماست، استاندارد ها و Best Practice ها ارائه نمیشوند. به احتمال زیاد هر سرویس به روش خودش خطا ها را هندل کند و بی شک مقدار زیادی کد اضافه نوشته خواهد شد.
ساخت استاندارد مثل PayPal’s API Style Guide در بلند مدت مفید هستند. همچنین مهم است که به دیگران بگوییم چگونه مستندات API ها باید نوشته شوند. ابزار هایی مثل Swagger وجود دارند که به ما در چرخه طراحی تا مستندات API تا تست ها یا دیپلوی کمک میکنند. امکان ساخت متادیتا برای API تا کاربران با آن بازی کنند، باعث میشود که بیشتر درباره آن بفهمند و استفاده بهینه تری بکنند.
در معماری مایکروسرویس، هر سرویس در طول زمان بیشتر به سرویس های دیگر وابسته میشود. در حالی که سرویس بزرگ میشود، این ممکن است که مشکلاتی را به همراه داشته باشد. برای مثال، تعداد نمونه های یک سرویس و لوکیشن ( هاست + پورت ) ممکن است به صورت پویا تغییر کند. همچنین، پرتکل و فرمت دیتاهایی که به اشتراک گذاشته میشود ممکن است بین سرویس ها متفاوت باشد.
اینجا جایی است که دروازه های API و سرویس کشف خیلی مفید میشوند. پیاده سازی یک دروازه API تبدیل به تنها راه ورود برای کاربران میشود و سرور میتواند API های مختلفی برای هر کاربر افشا کند.
دروازه API همچنین میتواند امنیت را پیاده سازی کند مثلا اعتبارسنجی که کاربر میتواند عملیات خاصی را درخواست دهد. بعضی از ابزار ها مانند Zookeeper وجود دارند که میتوان برای کشف سرویس استفاده کرد ( با اینکه برای این هدف ساخته نشده است ). تعدادی ابزار مدرن تری مانند etcd و Hashicorp’s Consul وجود دارد که هدف اصلی آنها کشف سرویس است و قطعا میارزه که نگاهی به آنها بیاندازید.
نکته مهمی که باید بفهمید این است که مایکروسرویس ها پیشفرض ارتجاعی نیستند. خطاهایی در سرویس ها خواهد بود. خطاهایی به خاطر وجود خطا در وابستگی ها اتفاق خواهند افتاد. به علاوه، خطا و خرابی ها همچنین میتوانند به دلایلی همچون وجود باگ در کد و تایم اوت شبکه و ... اتفاق بیافتند.
چیز بسیار مهم این است که در معماری میکروسرویس مطمئن بشویم که با خرابی یا خطا در یک سرویس، سرویس های دیگر تاثیری نمیبینند یا از کار نمیافتند. الگو هایی مانند Bulkhead وCircuits Breaker وجود دارند که به شما کمک میکنند یه ارتجاع بیشتری برسید.
الگو بالکهد اجزای یک اپ را در استخر هایی مجزا میکند که اگر یکی به مشکل خورد بقیه به کار ادامه میدهند. به این اگو بالکهد میگویند چون آن تشبیه میشود به بدنه بخش شده یک کشتی. اگر بدنه به خطر بیافته فقط قسمت آسیب دیده از آب پر میشود، که از غرق شدن کشتی جلوگیری میکند.
الگو سرکت بریکر یک فانکشن محافظت شده را در یک آبجکت سرکت بریکر در بر میگیرد. وقتی خطا آستانه را رد میکند، سرکت بریکر متوقف میشود و کال ها بعد از این زمان به سرکت بریکر با خطا باز خواهند گشت، بدون اینکه کال محافظت شده ای انجام بشود تا تایم اوت تنظیم شده بگزرد.
بعد از انکه تایم اوت منقضی میشود، چند کال از سرکت بریکر رد میشود و اگر موفقیت آمیز بودند سرکت بریکر به حالت معمولی ادامه میدهد. در زمانی که سرکت بریکر به مشکل برخورده، میتوان به کاربران اطلاع داد که بخشی از سیستم قابل استفاده نیست ولی بقیه سیستم قابل استفاده است.
در ذهنتون داشته باشید که ارائه این سطح از ارتجاع ممکنه چالش چند بعدی باشد. — برای اطلاعات بیشتر به پست Bilgin Ibryam مراجعه کنید. " It takes more than a Circuit Breaker to create a resilient application "
جدا بودن در ذات مایکروسرویس ها است و دیدبانی و انجام لاگینگ سرویس های مستقل ممکنه یک چالش باشه. گشتن و مرتبط کردن لاگ های هر سرویس و پیدا کردن لاگ های منفرد سخته. درست مثل اپ های یکپارچه هیچ جای تک و مشخصی برای دیدبانی سرویس ها نیست.
برای حل این مشکل ها، یک روش ترجیه داده شده اینه که از سرویس لاگ مرکزی استفاده کرد که لاگ ها را از هر سرویس جمع خواهد کرد. کاربران میتوانند در این لاگ ها که در یک جای مرکزی هست سرچ کنند و تنظیم کنند که در صورت مشاهده لاگ های خاص هشدار بزند.
ابزار های استاندارد وجود دارند که توسط سازمان های بزرگ استفاده میشوند. ELK Logstack بیشترین راه حل استفاده شده است. یک دیمن لاگ کننده، Logstash، لاگ ها را جمع آوری و مجتمع میکند و از طریق داشبورد Kibana قابل سرچ است که با Elasticsearch ایندکس شده اند.
مانند جمع آوری لاگ ها، آمار استفاده CPU و Memory را نیز میتوان در جای مرکزی جمع آوری کرد. ابزار هایی مانند Graphite در جمع آوری آمار در یک مخزن مرکزی و ذخیره کردن بهینه آنها یه خوبی عمل میکنند.
وقتی که یکی از سرویس ها از جواب دادن به درخواست ها باز میماند، باید روشی برای روشن کردن هشدار باشد، و اینجاست که پیاده سازی APIهای چک سلامتی در هر سرویس مهم میشود. آنها اطلاعاتی درباره سلامتی سرویس میدهند.
یک کاربر چک سلامتی، که میتواند یک سرویس دیدبانی و یا یک متعادل کننده بار باشد، به طور منظم در اندازه های زمانی یکسان به اندپوینت چک سلامتی یک درخواست میفرستد. حتی اگر تمام سرویس های دانلود کننده سلامت باشند، باز هم ممکن هست یک مشکل ارتباطی بین سرویس های دانلود کننده باشد. ابزار هایی مانند Netflix’s Hystrix به شما امکان شناسایی چنین مشکلاتی را میدهد.
حالا که ما گفتیم معماری مایکروسرویس چیه، و چرا شما میخواین معماری مایکروسرویس دپلوی کنید و تعدادی نکته و اندیشه درباره اینکه چطوری شروع کنیم، یک توضیه آخر دارم:
وقتی شروع به ساخت معماری های مایکروسرویس میکنید، کوچک شروع کنید. از یک یا دو سرویس شروع کنید و همانطور که زمان میگذرد و تجربه بیشتری بدست میاورید تعداد سرویس هارا بیشتر کنید.
در این راه و جاده مهیج مایکروسرویس ها آرزوی بهترین ها رارو براتون دارم.
اگر خواستین میتونید این مقاله رو در کانال های دیگه شیر کنید و دنبال کننده محتوا های Hashmap در https://medium.com/hashmapinc باشید.
آقای Jetinder Singh توسعه دهنده ارشد Tempus IIoT/IoT در Hashmap است. او در صنایع مختلفی با گروه تکنولوژی های نوآور و تخصص های دامنه ارزش کار میکند که خروجی کسب و کار را برای مشتریان سرعت میبخشد.
متنی که خوندین ترجمه من از مقاله The What, Why, and How of a Microservices Architecture از سایت مدیوم بود.