میکروسرویس ها. خوب یا بد؟ مزایا و معایب میکروسرویس



در این مقاله قصد داریم نگاه کلی به فواید و معایب استفاده از معماری میکروسرویس و مقایسه‌ی آن با معماری یکپارچه (monolith) که بسیاری از افراد با آن آشنایی دارند ارائه دهیم.

شرکت‌هایی مانند نتفلیکس (Netflix)، آمازون (Amazon) و دیگر شرکت‌ها ایده‌ی استفاده از میکروسرویس‌ها را در محصول‌هایشان به کار گرفته‌اند. میکروسرویسها یکی از جدیدترین موضوع‌های صنعت نرم‌افزار هستند و حتی میتوان گفت یکی از داغ ترین مباحث مهندسی نرم افزار و بسیاری از شرکت‌ها خواهان استفاده از آن‌ها هستند. نکته‌ی مهمی که وجود دارد این است که میکروسرویس و DevOps هماهنگی خوبی با یکدیگر می‌توانند داشته باشند و در بسیاری از شرکت ها طراحی معماری میکروسرویس جزو وظایف تیم DevOps محسوب میشود.

با این وجود میکروسرویس چیست؟ چرا یک شرکت باید از آن استفاده کند؟

به منظور فهم آن‌ها ابتدا اجازه دهید نگاهی به معماری Monolitic  بیندازیم.

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

  • لایه‌ی نمایش (Presentation layer)
  • لایه‌ی کسب و کار(Business layer)
  • لایه‌ی دسترسی به داده‌ها (Data access layer)

فرض کنید یک کلاینت سنتی (یک مرورگر) درخواستی را ارسال می‌کند. لایه‌ی کسب و کار منطق کسب و کار را اجرا می‌کند، پایگاه داده (Database) داده‌های ماندگار مختص اپلیکیشن (application specific persistence data‌) را جمع آوری یا ذخیره می‌کند و رابط کاربری (UI) داده‌ها را به کاربر نشان می‌دهد.

با این حال این نوع سیستم دارای چند مشکل است. همه‌ی کدها (لایه‌ی نمایش، لایه‌ی کسب و کار و لایه‌ی دسترسی به داده‌ها) در یک بخش و قسمت حفظ می‌شوند. یعنی یک اپلیکیشن یا فریمورک وجود دارد و تمامی بخش ها در آن قسمت نگهداری و حفظ میشوند. اگرچه ما قاعدتاً سرویس‌هایی مانند سرویس JMS و سرویس دسترسی داده‌ها (Data-Access service) را تقسیم می‌کنیم، آن‌ها در همان فریمورک یا مجموعه کدها هستند ولی به صورت مجزا توسعه داده میشوند. ممکن است هربخشی تیم مجزایی داشته باشد که روی یک سرویس یا بخش کار کنند ولی تمام کدها یک جا مستقر هستند.

در معماری مونولیتیک حتی با اینکه پروژه‌ای چند ماژولی ایجاد کرده‌ایم، یک ماژول به ماژول دیگری وابسته است و از این گذشته ممکن است این ماژول به ماژول‌های وابسته‌ در مسیر کلاس خود نیاز داشته باشد. یعنی ممکن است مثلا کلاس پرداخت از یک نمونه از کلاس کاربر (Instance )  یا محصول نیاز داشته باشد که در همان مسیر باشد و یا در مسیر دیگر.

شاید حتی در این معماری از محیط توزیع شده (Distributed) استفاده می‌کنیم، ولی خب این ماژول  یا ماژول ها تحت شرایط فرآیندی واحدی اجرا می‌شود.

بنابراین در یک فرآیند واحد (منظور یک فرآیندی است که قرار است یک کار یکتا انجام دهد) سرویس‌های گوناگونی با یکدیگر در ارتباط هستند. برای دستیابی به این امر تمامی برنامه‌ها و کتابخانه‌های مورد نیاز آن‌ها در هر بخشی (scope) از اپلیکیشن مورد نیاز است.

به عنوان مثال فرض کنید یک سرویس JMS میخواهد از لایه دسترسی به داده ها استفاده کند. کانتینر JMS به فایل های JAR لایه دسترسی به داده ها و نیز فایل های JAR که در لایه دسترسی به داده ها وابسته هستند (وابستگی های سطح دوم ) نیاز پیدا میکند. در این معماری ایرادهای زیادی وجود دارد و معماری در ماهیت خود انعطاف‌ پذیر نیست.

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

ایراد اول

گفتیم که در این معماری یک فریمورک واحد وجود دارد و به تدریج رشد می‌کند (برای اینکه برایتان روشن تر شود فرض کنید یک اپلیکیشن با فریمورک Spring یا لاراول نوشته شده است و تمام تیم ها بر روی همان فریمورک کار میکنند. تیم های بکند و فرانت جداگانه بر روی بخش های مختلف آن در حال کار هستند). هر برنامه نویس، چه یک توسعه دهنده‌ی رابط کاربری باشد یا یک توسعه دهنده‌ی لایه‌ی کسب و کار، در یک جا یعنی همان پایگاه کد یکسان کامیت می‌کنند که باعث بسیاری از مشکلات خواهد شد. ممکن است تضاد پیش بیاید و یا کدها کارآیی خودشان را بخاطر خطاهای انسانی از دست بدهند و در نتیجه مدیریت آن بسیار مشکل خواهد بود. فرض کنید که یک توسعه دهنده‌ای به تنهایی بر روی ماژول JMS کار می‌کند. او مجبور است که کل کدها و فریمورک نوشته شده را بر روی کامپیوتر خودش pull کند و کل ماژول را طوری تنظیم کند که تنها روی سرور لوکال (local server) خودش کار کند. چرا؟ او تنها باید روی ماژول JMS تمرکز کند ولی وضعیت فعلی به او این اجازه را نمی‌دهد.

ایراد دوم

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

فرض کنید در یک پروژه‌ی چند ماژولی که ماژول JMS و ماژول کسب و کار به ماژول دسترسی به داده‌ها وابسته هستند. یک تغییر ساده در ماژول دسترسی به داده‌ها بدین معنی است که ما باید دوباره ماژول JMS و ماژول کسب و کار را پیکربندی و آن‌ها را روی تمام سرورهای موجود مستقر کنیم.

ایراد سوم

همان طور که نرم‌افزار یکپارچه از معماری سه لایه استفاده می‌کند سه تیمی که توانایی اجرای وظایف مختلف دارند در توسعه‌ی یک ویژگی دخیل هستند و یک هدف مشترک دارند. با این که یک معماری سه لایه اجازه‌ی تقسیم وظایف را می‌دهد در دراز مدت مرزها جا به جا می‌شوند و لایه‌ها روان بودنشان را از دست می‌دهند و سخت و انعطاف ناپذیر می‌شوند.

فرض کنید که یک ویژگی مدیریت فهرست اموال توسعه داده شده است. رابط کاربری (UI)، لایه‌ی کسب و کار و لایه‌ی دسترسی به داده‌ها وظایف خود را دارند اما همه خواهان به دست گرفتن کنترل بخش کسب و کار اصلی هستند تا وقتی که خطاها مشاهده شدند بتوانند آن‌ها را برطرف کنند و به لایه‌ی توسعه دهنده‌ی دیگری وابسته نباشند. به خاطر این رقابت مرزها به جا به جا شدن ختم می‌شوند که منجر به ناکارآمدی معماری می‌شود.

ایراد چهارم

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

ایراد پنجم

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

نتفلیکس و آمازون این مشکل را با راه حلی به نام میکروسرویس‌ها بر طرف کرده‌اند.  شکل زیر یک شمای کامل و مفهومی ازین معماری ارائه میدهد:

معماری میکروسرویس به ما می‌گوید که محصول یا پروژه‌ای را به سرویس‌های مستقل تقسیم کنیم تا بتواند به تنهایی در آن سطح مستقر و مدیریت شود و به سرویس‌های دیگر وابسته نباشد.

سوالی که بعد از دیدن این تعریف به ذهن می آید این است که بر چه اساسی پروژه‌ام را به سرویس‌های مستقل تقسیم کنم؟

بسیاری از افراد تفکر غلطی راجع به میکروسرویس‌ها دارند. مفهوم میکروسرویس‌ها این نیست که پروژه‌تان را بر اساس لایه مانند JMS، رابط کاربری، لاگینگ (logging) و… تقسیم کنید.

قطعاً این طور نیست. ما باید بر اساس قابلیت و کاربرد آن‌ را تقسیم کنیم. یک قابلیت کامل ممکن است شامل رابط کاربری، کسب و کار، لاگینگ، JMS، دسترسی به داده‌ها، سرویس جست‌وجوی JNDI و… باشد.

قابلیت نباید قابل تقسیم باشد یعنی سرویس یا کاربرد مورد نظر را در حد بهینه کوچک و کاربردی در نظر بگیرید و همچنین نباید به قابلیت‌های دیگر وابسته باشد.

البته شکستن پروژه به سرویس های کوچک همیشه آسان نیست و تصمیم اینکه چه قابلیت هایی وجود داشته باشد تصمیم آسانی نخواهد بود. همچنین باید به قدر کافی تجربه این کار را داشته باشید که سرویس های کوچک زیاد تولید نکنید که مدیریت آنها بسیار سخت تر از قبل باشد.

اگر پروژه از ماژول‌های انبارداری، ثبت سفارش، ایجاد فاکتور، ارسال و سبد خرید تشکیل شده باشد می‌توانیم هر سرویس را به ماژول‌های قابل استقرار مستقل تقسیم کنیم. هر کدام تیم نگهداری، مانیتورینگ مخصص به خود، کش (cache) مربوط به خود، سرورها و پایگاه داده‌ی خود را دارند. بنابراین به وسیله‌ی میکروسرویس‌ها پایگاه داده‌ی متمرکزی وجود ندارد و هر ماژول پایگاه داده‌ی مخصوص به خود را دارد.

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

مهم‌ترین بعد فرهنگ میکروسرویس این است که هرکسی که سرویس را توسعه می‌دهد این وظیفه‌ی تیم است که آن را مدیریت کند. این عمل از ایده‌ی واگذاری و مشکلات همراه با آن جلوگیری می‌کند.

مزایا و معایب میکروسرویس

مزیت اول

درمعماری مونولیتیک شما تنها در قالب یک زبان مانند جاوا (Java) به عنوان کد بیس اصلی برنامه خودتان توسعه می‌دهید اما به وسیله‌ی میکروسرویس‌ها همان طور که هر سرویس مستقل و هر سرویس پروژه‌ای جدید است هر سرویس می‌تواند به هر زبانی که مناسب آن است توسعه داده شود.

مزیت دوم

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

مزیت سوم

هنگامی که سرویسی نیاز به صحبت کردن با سرویس دیگری را دارد این کار را با استفاده از پروتکل های شبکه و API ها مخصوصا REST APIها انجام میدهند. یک سرویس REST وسیله‌ای برای برقراری ارتباط است. بنابراین تبدیل کوچکی به وجود می‌آید. برخلاف SOA پیام‌های میکروسرویس نسبت به ESB حجم کمتری دارند زیرا ESB اطلاعات زیادی برای تبدیل، دسته بندی و مسیریابی پیام‌ها ذخیره می‌کند.

پیوست : ESB به مثابه یک مخزن تمامی سرویس‌های ارتباطی نرم‌افزارها را در خود نگهداری می‌کند و هرگاه نیاز به اطلاعاتی از اجزای مختلف سیستم اطلاعاتی باشد ESB سرویس مورد نیاز را در اختیار درخواست کننده قرار می‌دهد.

اگر میخواهید بیشتر در مورد ESB بدانید از این لینک استفاده کنید

مزیت چهارم

هیچ پایگاه داده‌ی متمرکزی وجود نخواهد داشت. هر ماژول پایگاه داده‌ی خودش را دارد پس داده‌ها دیگر متمرکز نیستند. می‌توانید از پایگاه داده‌ی NoSQL یا رابطه‌ای بسته به ماژول استفاده کنید که همان طور که قبلاً اشاره کردم امکان استفاده از فناوری‌های ذخیره سازی مختلف وجود دارد.

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

با این وجود پیام‌هایی که بین میکروسرویس‌ها منتقل میشوند در زمان انتقال تغییر نمی‌کنند (مایکروسرویس ها از یک درگاه پیام گنگ استفاده میکنند و فقط ورودی ها رو از یک سرویس به سرویس دیگر انتقال میدهند) اما گیرنده به اندازه کافی هوشمند است که بتواند کارهایی که ذکر کردیم را انجام دهد. میکروسرویس‌ یک درگاه پیام گنگ دارد اما گیرنده‌های هوشمندی دارد.

از آن جایی که میکروسرویس‌ها به وسیله‌ی REST ارتباط برقرار می‌کنند حوزه‌ی تبدیل بسیار کوچک است و تنها یک سرویس از طریق تماس API به سرویس دیگر متصل می‌شود.

معایب میکروسرویس‌ها

از آن جا که هر بخشی از نرم افزار و هر کاربرد اصلی یک سرویس جداگانه است پس در یک پروژه‌ی بزرگ سرویس‌های بسیار زیادی وجود دارد. مانیتور کردن این سرویس‌ها باعث افزایش سربار می‌شود.

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

سرویس‌ها با یکدیگر مدام تماس می‌گیرند و در نتیجه ردیابی مسیر (Tracking path) و عیب یابی را دشوار می‌کند.

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

به وسیله‌ی میکروسرویس‌ها هر سرویس به وسیله‌ی API یا فراخوانی از راه دور (remote calls) ارتباط برقرار می‌کند که سربار بیشتری نسبت به فراخوانی‌ هایی که بین فرآیندها در نرم‌افزار مونولینیک صورت می‌گیرند دارد.

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

در مقالات آینده سعی میکنیم تک تک اجزای مایکروسرویس ها را به تفصیل توضیح دهیم.

لینک مقاله

وبسایت آموزشی PHPADMIN