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

تاریخ دوباره تکرار می شود. همه چیز قدیمی دوباره جدید است و من به اندازه کافی در اطراف بوده ام تا ببینم ایده ها کنار گذاشته شده اند، دوباره کشف شده اند و پیروزمندانه برمی گردم تا از مد پیشی بگیرم. در سال های اخیر SQL بازگشتی فوق العاده از مردگان داشته است. ما دوباره عاشق پایگاه داده های رابطه ای هستیم. من فکر می کنم یکپارچه دوباره لحظه ادیسه فضایی خود را خواهد داشت. میکروسرویسها و بدون سرور، روندهایی هستند که توسط فروشندگان ابری پیش میروند و برای فروش بیشتر منابع رایانش ابری به ما طراحی شدهاند. میکروسرویس ها از نظر مالی برای اکثر موارد استفاده بسیار کمی معنا دارند. بله، آنها می توانند پایین بیایند. اما زمانی که آنها افزایش می یابند، هزینه ها را به صورت سود سهام پرداخت می کنند. افزایش هزینه های مشاهده پذیری به تنهایی جیب فروشندگان “ابر بزرگ” را پوشانده است.
می توانید نسخه ویدیویی این پست را در اینجا مشاهده کنید:
https://www.youtube.com/watch?v=NWu7AJJlLM8
من اخیراً یک پانل کنفرانسی را رهبری کردم که موضوع میکروسرویس ها در مقابل یکپارچه ها را پوشش می داد. اتفاق نظر در پانل (حتی با شخص طرفدار یکپارچه) این بود که مونولیت ها به اندازه ریزسرویس ها مقیاس نمی شوند.
این احتمالاً در مورد یکپارچههای هیولایی قدیمی مانند آمازون، eBay و همکاران صادق است. جایگزین شده است. آنها در واقع مبانی کد عظیمی بودند که هر تغییری در آنها دردناک بود و مقیاسبندی آنها چالشبرانگیز بود. اما این یک مقایسه منصفانه نیست. رویکردهای جدیدتر معمولاً رویکردهای قدیمی را شکست می دهند. اما اگر یک مونولیت با ابزارهای جدیدتر بسازیم، آیا مقیاس پذیری بهتری خواهیم داشت؟
محدودیت ها چه خواهد بود و یک مونولیت مدرن حتی چگونه به نظر می رسد؟
مدولیت
برای درک قسمت دوم می توانید پروژه Spring Modulith را بررسی کنید. این یک مونولیت مدولار است که به ما امکان می دهد با استفاده از قطعات ایزوله پویا یک مونولیت بسازیم. با این رویکرد می توانیم تست، توسعه، مستندسازی و وابستگی ها را از هم جدا کنیم. این به جنبه ایزوله توسعه میکروسرویس با اندکی از سربار درگیر کمک می کند. سربار تماس های راه دور و تکرار عملکرد (ذخیره سازی، احراز هویت و غیره) را حذف می کند.
Spring Modulith بر اساس ماژولارسازی پلتفرم جاوا (Jigsaw) نیست. آنها جداسازی را در طول آزمایش و در زمان اجرا اعمال می کنند، این یک پروژه بهار بوت معمولی است. دارای برخی قابلیتهای زمان اجرا اضافی برای مشاهدهپذیری مدولار است، اما عمدتاً مجری «بهترین شیوهها» است. این مقدار از این جداسازی فراتر از چیزی است که ما معمولاً با میکروسرویس ها به آن عادت کرده ایم، اما برخی معاوضه ها نیز دارد. بیایید یک مثال بزنیم. یک یکپارچه سنتی بهار دارای یک معماری لایه ای با بسته هایی مانند:
com.debugagent.myapp
com.debugagent.myapp.services
com.debugagent.myapp.db
com.debugagent.myapp.rest
این با ارزش است زیرا می تواند به ما کمک کند از وابستگی بین لایه ها جلوگیری کنیم. به عنوان مثال لایه DB نباید به لایه سرویس بستگی داشته باشد. ما میتوانیم از ماژولهایی مانند آن استفاده کنیم و نمودار وابستگی را به طور مؤثر در یک جهت تحمیل کنیم: رو به پایین. اما با رشد ما این چندان منطقی نیست. هر لایه با کلاس های منطق تجاری و پیچیدگی های پایگاه داده پر می شود. با یک Modulith، ما یک معماری خواهیم داشت که بیشتر شبیه این است:
com.debugagent.myapp.customers
com.debugagent.myapp.customers.services
com.debugagent.myapp.customers.db
com.debugagent.myapp.customers.rest
com.debugagent.myapp.invoicing
com.debugagent.myapp.invoicing.services
com.debugagent.myapp.invoicing.db
com.debugagent.myapp.invoicing.rest
com.debugagent.myapp.hr
com.debugagent.myapp.hr.services
com.debugagent.myapp.hr.db
com.debugagent.myapp.hr.rest
این به نظر می رسد بسیار نزدیک به یک معماری میکروسرویس مناسب است. همه قطعات را بر اساس منطق تجاری جدا کردیم. در اینجا میتوان وابستگیهای متقاطع را بهتر مهار کرد و تیمها میتوانند بدون اینکه روی انگشتان یکدیگر قدم بگذارند، روی منطقه ایزوله خودشان تمرکز کنند. این مقدار زیادی از ارزش میکروسرویس ها بدون سربار است.
ما میتوانیم با استفاده از حاشیهنویسی، جداسازی را عمیقاً و بهطور آشکار اعمال کنیم. ما می توانیم تعریف کنیم که کدام ماژول از کدام استفاده می کند و وابستگی های یک طرفه را اعمال می کنیم. بنابراین ماژول منابع انسانی هیچ ارتباطی با صورتحساب نخواهد داشت. ماژول مشتریان نیز این کار را نخواهد کرد. ما می توانیم یک رابطه یک طرفه بین مشتریان و صورتحساب برقرار کنیم و با استفاده از رویدادها ارتباط برقرار کنیم. رویدادهای درون یک Modulith بی اهمیت، سریع و تراکنشی هستند. آنها بدون دردسر وابستگی های بین ماژول ها را جدا می کنند. انجام این کار با میکروسرویس ها امکان پذیر است اما اجرای آن دشوار است. بگوییم که صورتحساب باید یک رابط را در معرض ماژول دیگری قرار دهد. چگونه از استفاده مشتریان از آن رابط جلوگیری می کنید؟
با ماژول ها می توانیم. آره. یک کاربر می تواند کد را تغییر دهد و دسترسی را فراهم کند، اما این باید از طریق بررسی کد انجام شود و این مشکلات خود را ایجاد می کند. توجه داشته باشید که با ماژولها هنوز هم میتوانیم به قطعات اصلی میکروسرویس مانند پرچمهای ویژگی، سیستمهای پیامرسانی و غیره تکیه کنیم. میتوانید اطلاعات بیشتری درباره Spring Modulith در اسناد و وبلاگ Nicolas Fränkels بخوانید.
هر وابستگی در یک سیستم ماژول ترسیم شده و در کد ثبت می شود. پیادهسازی Spring شامل امکان مستندسازی خودکار همه چیز با نمودارهای بهروز و مفید است. ممکن است فکر کنید، وابستگی ها دلیل Terraform هستند. آیا این مکان مناسب برای چنین طراحی “سطح بالا” است؟
یک راه حل زیرساخت به عنوان کد (IaC) مانند Terraform هنوز می تواند برای استقرار Modulith وجود داشته باشد. اما آنها بسیار ساده تر خواهند بود. مشکل تقسیم مسئولیت هاست. همانطور که در تصویر زیر می بینید پیچیدگی یکپارچه با میکروسرویس ها از بین نمی رود (برگرفته از این موضوع). ما فقط آن قوطی کرم را به تیم DevOps فرستادیم و زندگی آنها را سختتر کردیم. بدتر از آن، ما ابزار مناسبی برای درک این پیچیدگی به آنها ندادیم، بنابراین آنها باید این موضوع را از بیرون مدیریت کنند.
به همین دلیل است که هزینههای زیرساخت در صنعت ما در حال افزایش است، جایی که بهطور سنتی قیمتها باید رو به پایین روند… وقتی تیم DevOps با مشکلی مواجه میشود، منابع را به سمت آن پرتاب میکند. این کار در همه موارد درست نیست.
ماژول های دیگر
ما میتوانیم از ماژولهای پلتفرم استاندارد جاوا (Jigsaw) برای ساخت یک برنامه Spring Boot استفاده کنیم. این مزیت شکستن برنامه و یک نحو استاندارد جاوا را دارد. اما ممکن است گاهی اوقات ناخوشایند باشد. این احتمالاً هنگام کار با کتابخانه های خارجی یا تقسیم برخی از کارها به ابزارهای رایج بهترین کار را دارد.
گزینه دیگر سیستم ماژول در maven است. این سیستم به ما اجازه می دهد که ساخت خود را به چندین پروژه جداگانه تقسیم کنیم. این یک فرآیند بسیار راحت است که ما را از دردسر پروژه های عظیم نجات می دهد. هر پروژه مستقل است و کار با آن آسان است. می تواند از فرآیند ساخت خود استفاده کند. سپس همانطور که پروژه اصلی را می سازیم همه چیز به یک تک سنگ تبدیل می شود. به نوعی، این همان چیزی است که بسیاری از ما واقعاً می خواهیم…
در مورد Scale چطور؟
ما میتوانیم از اکثر ابزارهای مقیاسبندی میکروسرویس برای مقیاسبندی یکپارچههای خود استفاده کنیم. بخش زیادی از تحقیقات مربوط به مقیاس بندی و خوشه بندی با در نظر گرفتن یکپارچه ها توسعه داده شد. این یک فرآیند ساده تر است زیرا تنها یک بخش متحرک وجود دارد: برنامه. ما نمونه های اضافی را تکرار می کنیم و آنها را مشاهده می کنیم. هیچ سرویس فردی وجود ندارد که با شکست مواجه شود. ما ابزارهای عملکرد دقیق داریم و همه چیز به عنوان یک نسخه یکپارچه کار می کند.
من استدلال می کنم که مقیاس بندی ساده تر از میکروسرویس های معادل است. ما می توانیم از ابزارهای پروفایل استفاده کنیم و یک تقریب معقول از تنگناها بدست آوریم. تیم ما می تواند به راحتی (و مقرون به صرفه) محیط های مرحله بندی را برای اجرای آزمایش ها راه اندازی کند. ما یک دید واحد از کل سیستم و وابستگی های آن داریم. ما می توانیم یک ماژول فردی را به صورت مجزا آزمایش کنیم و مفروضات عملکرد را تأیید کنیم.
ابزارهای ردیابی و مشاهده فوق العاده هستند. اما بر تولید نیز تأثیر می گذارند و گاهی اوقات نویز تولید می کنند. وقتی سعی می کنیم گلوگاه پوسته پوسته شدن یا یک مشکل عملکرد را دنبال کنیم، آنها می توانند ما را به سوراخ خرگوش اشتباهی بفرستند.
ما میتوانیم از Kubernetes با یکپارچهها به همان اندازه مؤثر استفاده کنیم که میتوانیم از آن با Microservices استفاده کنیم. اندازه تصویر بزرگتر خواهد بود اما اگر از ابزارهایی مانند GraalVM استفاده کنیم، ممکن است خیلی بزرگتر نباشد. با این کار میتوانیم یکپارچه را در سراسر مناطق تکرار کنیم و همان رفتار خرابی را که در میکروسرویسها داریم ارائه دهیم. تعداد کمی از توسعه دهندگان مونولیت ها را روی Lambdas نصب می کنند، من طرفدار این رویکرد نیستم زیرا ممکن است بسیار گران شود. ولی کار میکنه…
گلوگاه
اما هنوز یک نقطه وجود دارد که یک مونولیت به دیوار مقیاسپذیر برخورد میکند: پایگاه داده. میکروسرویس ها به لطف این واقعیت که ذاتاً چندین پایگاه داده جداگانه دارند، به مقیاس زیادی دست می یابند. یک مونولیت معمولاً با یک فروشگاه داده کار می کند. این اغلب گلوگاه واقعی برنامه است. راه هایی برای مقیاس بندی یک DB مدرن وجود دارد. خوشه بندی و حافظه پنهان توزیع شده ابزارهای قدرتمندی هستند که به ما اجازه می دهند به سطوحی از عملکرد برسیم که تطبیق آن در معماری میکروسرویس بسیار دشوار است.
همچنین نیازی به یک پایگاه داده واحد در یک مونولیت وجود ندارد. داشتن یک پایگاه داده SQL در حین استفاده از Redis برای حافظه پنهان، غیرعادی نیست. اما می توانیم از یک پایگاه داده مجزا برای سری های زمانی یا داده های مکانی نیز استفاده کنیم. ما میتوانیم از یک پایگاه داده جداگانه برای عملکرد نیز استفاده کنیم، اگرچه در تجربه من این اتفاق نیفتاده است. مزایای نگهداری داده های ما در یک پایگاه داده فوق العاده است.
مزایا
این واقعیت که ما میتوانیم یک معامله را بدون تکیه بر «ثبات نهایی» انجام دهیم، یک مزیت شگفتانگیز است. وقتی میخواهیم یک سیستم توزیعشده را اشکالزدایی و تکرار کنیم، ممکن است حالت موقتی داشته باشیم که تکثیر محلی یا حتی درک کامل آن از بررسی دادههای مشاهدهپذیری بسیار سخت است.
عملکرد خام مقدار زیادی از سربار شبکه را حذف می کند. با تنظیم مناسب حافظه پنهان سطح 2، میتوانیم 80 تا 90 درصد از IO خوانده شده را حذف کنیم. این در یک میکروسرویس امکان پذیر است، اما انجام آن بسیار سخت تر است و احتمالاً سربار تماس های شبکه را حذف نخواهد کرد.
همانطور که قبلاً اشاره کردم، پیچیدگی برنامه در معماری میکروسرویس از بین نمی رود. ما فقط آن را به مکان دیگری منتقل کردیم. در تجربه من تا کنون، این یک پیشرفت نیست. ما قطعات متحرک زیادی را به ترکیب اضافه کردیم و پیچیدگی کلی را افزایش دادیم. بازگشت به یک معماری یکپارچه هوشمندتر و ساده تر منطقی تر است.
چرا از میکروسرویس ها استفاده کنیم؟
انتخاب زبان برنامه نویسی یکی از اولین شاخص های تمایل به میکروسرویس ها است. ظهور میکروسرویس ها با ظهور پایتون و جاوا اسکریپت مرتبط است. این دو زبان برای برنامه های کوچک عالی هستند. برای بزرگترها چندان عالی نیست.
Kubernetes مقیاس بندی چنین استقرارهایی را نسبتاً آسان کرد، بنابراین بنزین را به روند رو به رشد اضافه کرد. میکروسرویس ها همچنین دارای قابلیت بالا و پایین رفتن نسبتاً سریع هستند. این می تواند هزینه ها را به روشی دقیق تر کنترل کند. در این راستا ریزسرویس ها به عنوان راهی برای کاهش هزینه ها به سازمان ها فروخته شد. این کاملاً خالی از لطف نیست. اگر استقرار سرور قبلی به سرورهای قدرتمند (گران قیمت) نیاز داشت، این استدلال ممکن است کمی آب را حفظ کند. این ممکن است برای مواردی که استفاده شدید است، یک بار بسیار زیاد و ناگهانی و بدون ترافیک صادق باشد. در این موارد، منابع ممکن است به صورت پویا (ارزان) از ارائه دهندگان میزبان Kubernetes بدست آید.
یکی از نکات اصلی فروش میکروسرویس ها جنبه لجستیکی است. این به تیمهای چابک منفرد اجازه میدهد تا مشکلات کوچک را بدون درک کامل «تصویر بزرگ» حل کنند. مشکل این است که فرهنگی را فعال می کند که در آن هر تیم “کار خود” را انجام می دهد. این امر به ویژه در زمان کوچک سازی که پوسیدگی کد ایجاد می شود مشکل ساز است.
با Monolith شروع کنید، چرا ترک کنید؟
یک نکته اجماع در پانل این بود که ما همیشه باید با یکپارچه شروع کنیم. ساختن آن آسانتر است و اگر بخواهیم از میکروسرویس استفاده کنیم، میتوانیم بعداً آن را خراب کنیم. اما چرا باید؟
پیچیدگیهای مربوط به تک تک نرمافزارها به عنوان ماژولهای مجزا معقولتر است. نه به عنوان برنامه های کاربردی. تفاوت در استفاده از منابع و اتلاف مالی بسیار زیاد است. در این زمانه کاهش هزینه ها، چرا مردم همچنان به جای یکپارچه پویا و مدولار، ساخت میکروسرویس ها را انتخاب می کنند؟
فکر می کنم از هر دو اردو چیزهای زیادی برای یادگیری داریم. جزم گرایی مشکل ساز است، همان طور که دلبستگی مذهبی به یک رویکرد است. میکروسرویس ها برای آمازون معجزه کردند. انصافاً هزینه های ابری آنها پوشش داده شده است…
از سوی دیگر، اینترنت بر روی یکپارچه ساخته شده است. اکثر آنها به هیچ وجه ماژولار نیستند. هر دو تکنیک هایی دارند که به طور جهانی کاربرد دارند. من فکر می کنم انتخاب درست این است که یک مونولیت ماژولار با زیرساخت احراز هویت مناسب بسازیم که اگر بخواهیم به میکروسرویس ها روی بیاوریم، بتوانیم در آینده از آن استفاده کنیم.