?作者 | Shai Almog
策劃 | 云昭
歷史總會重演。一切剛過去的,又會被重新提起。開源項目Codename One的聯合創(chuàng)始人Shai,曾是Sun Microsystems開源LWUIT項目的共同作者,參與了無數開源項目。作為最早一批Java開發(fā)者,最近感慨道:單體,又回來了!
Shai說道:我已經在這個圈子里很久時間了,看到了一次次被拋棄、被重新發(fā)現的想法,超越“時髦詞匯”,并凱旋而歸。
他進一步舉例,“近年來,SQL也掙扎過后,死而復生。我們再次熱愛關系數據庫。我認為單體架構將再次迎來奇幻之旅。微服務和無服務器是云供應商推動的趨勢,目的當然是在向我們兜售更多的云計算資源。然而對于大多數用例來說,微服務在財務上意義不大。是的,供應商當然也可以降低成本。但當他們擴大規(guī)模時,他們會以股息來覆蓋掉成本。單是可觀測性成本的增加,就讓‘大型云’供應商的腰包鼓起來了!”
作為從業(yè)近30年的資深技術大神,為何做此感嘆?本文通過一場“利用模塊降低架構成本”的探討,幫助大家梳理現在的架構設計難題,希望對諸君有所啟發(fā)。
1、問題背景
我最近領導了一個會議小組,討論了微服務與單體服務的主題。組內認為,單塊的規(guī)模不如微服務。這對于亞馬遜、eBay等所取代的那些龐然大物來說可能是正確的。這些確實是巨大的代碼庫,其中的每一次修改都是痛苦的,而且它們的擴展都是具有挑戰(zhàn)性的。但這不是一個公平的比較。較新的方法通常優(yōu)于舊的方法。但如果我們用更新的工具構建一個整體,我們會得到更好的可擴展性嗎?它的局限性是什么?現代的單體(也稱巨石)到底該是什么樣子?
2、單體回歸范例:Modulith
Spring Modulith是一個模塊化的單體結構,可以讓我們使用動態(tài)隔離件構建單體結構。通過這種方法,我們可以分離測試、開發(fā)、文檔和依賴項。這有助于微服務開發(fā)的獨立方面,而所涉及的開銷很少。它消除了遠程調用和功能復制(存儲、身份驗證等)的開銷。
Spring Modulith不是基于Java平臺模塊化(Jigsaw)。他們在測試期間和運行時強制分離,這是一個常規(guī)的Spring Boot項目。它有一些額外的運行時功能,可以實現模塊化的可觀測性,但它主要是“最佳實踐”的執(zhí)行者。這種分離的價值超出了我們通常使用微服務的價值,但也有一些權衡。
舉個例子,傳統(tǒng)的Spring monolith將采用分層架構,其包如下:
這很有價值,因為它可以幫助我們避免層之間的依賴關系;例如,DB層不應依賴于服務層。我們可以使用這樣的模塊,并有效地將依賴關系圖推向一個方向:向下。但隨著我們的成長,這沒有多大意義。每一層都將充滿業(yè)務邏輯類和數據庫復雜性。
有了Modulith,我們的架構看起來更像這樣:
這看起來非常接近一個合適的微服務架構。我們根據業(yè)務邏輯分離了所有部分。在這里,可以更好地控制交叉依賴關系,團隊可以專注于自己的孤立區(qū)域,而不必互相踩腳。這是微服務的價值之一,且沒有開銷。
我們可以使用注釋進一步深入地和聲明性地實現分離。我們可以定義哪個模塊使用哪個并強制單向依賴關系,因此人力資源模塊將與發(fā)票無關。客戶模塊也不會。我們可以在客戶和發(fā)票之間建立單向關系,并使用事件進行反饋。Modulith中的事件是簡單、快速和事務性的。它們消除了模塊之間的依賴關系,無需麻煩。這可以用微服務實現,但很難實現。比如,開票需要向不同的模塊公開接口。如何防止客戶使用該界面?
有了模塊,我們就可以做到。對用戶可以更改代碼并提供訪問權限,但這需要經過代碼審查,這會帶來自己的問題。請注意,對于模塊,我們仍然可以依賴常見的微服務,如功能標志、消息傳遞系統(tǒng)等。您可以在文檔和Nicolas Fr?nkel的博客中閱讀有關Spring Modulith的更多信息。
模塊系統(tǒng)中的每個依賴項都被映射并記錄在代碼中。Spring實現包括使用方便的最新圖表自動記錄所有內容的能力。你可能會認為,依賴性是Terraform的原因。對于這樣的“高級”設計來說,這是正確的地方嗎?
對于Modulith部署,像Terraform這樣的基礎設施即代碼(IaC)解決方案仍然存在,但它們會簡單得多。問題是責任的劃分。正如下圖所展示,微服務并沒有消除整體結構的復雜性。我們只把“難啃的骨頭”踢給了DevOps團隊。更糟糕的是,我們沒有給他們正確的工具來理解這種復雜性,所以他們不得不從外部管理。
圖源:Twitter
這就是為什么我們行業(yè)的基礎設施成本在上升,而傳統(tǒng)行業(yè)的基礎設施價格卻在下降。當DevOps團隊遇到問題時,他們會投入資源。這顯然不是正確的做法。
3、其他模塊
我們可以使用標準Java平臺模塊(Jigsaw)來構建Spring Boot應用程序。這樣做的好處是可以分解應用程序和標準Java語法,但有時可能會很尷尬。當使用外部庫或將一些工作拆分為通用工具時,可能會更有效。
另一個選項是Maven中的模塊系統(tǒng)。這個系統(tǒng)允許我們將構建分解為多個單獨的項目。這是一個非常方便的過程,可以讓我們省去大量項目的麻煩。每個項目都是獨立的,易于使用。它可以使用自己的構建過程。然后,當我們構建主項目時,這些全部都變成了一個單體。在某種程度上,這才是我們真正想要的。
4、單體架構:擴展,有解嗎
可以使用大多數微服務擴展工具來擴展我們的單體們。許多與擴展和集群相關的開發(fā)都是在單體架構的情況下進行的。這是一個更簡單的過程,因為只有一個移動部分:應用程序。我們復制其他實例并觀察它們。沒有哪項服務是失敗的。我們有細粒度的性能工具,所有的功能都可以作為一個統(tǒng)一的版本。
我認為擴展單體為微服務比直接構建微服務更簡單——
- 我們可以使用分析工具,并獲得瓶頸的合理近似值。
- 我們的團隊可以輕松地(并且經濟實惠地)設置運行測試的登臺環(huán)境。
- 我們擁有整個系統(tǒng)及其依賴關系的單一視圖。
- 我們可以單獨測試單個模塊并驗證性能假設。
跟蹤和可觀測性工具非常棒。但它們也會影響生產,有時還會產生噪音。當我們試圖解決伸縮瓶頸或性能問題時,這些工具可能會讓設計者踩一些坑。
我們可以將Kubernetes與monolits一起使用,就像將其與微服務一起使用一樣有效。鏡像尺寸會更大(如果我們使用GraalVM這樣的工具,則可能不會太大)。有了這一點,我們可以跨區(qū)域復制monolith ,并提供與微服務相同的故障轉移行為。相當多的開發(fā)人員將monolics部署到Lambdas。筆者不太喜歡這種方法,因為非常昂貴。
5、單體的瓶頸問題:有解
但仍有一點是巨大的障礙:數據庫。由于微服務固有地具有多個獨立的數據庫,因此它們實現了巨大的規(guī)模。單體架構通常與單個數據存儲一起工作。這通常是應用程序的真正瓶頸。有多種方法可以擴展現代數據庫。集群和分布式緩存是強大的工具,可以讓我們達到在微服務架構中很難達到的性能水平。
在一個單體結構中,也并不需要單個數據庫。例如:在使用Redis進行緩存時,選擇使用SQL數據庫也是很常見的事情。但我們也可以為時間序列或空間數據使用單獨的數據庫。我們也可以使用單獨的數據庫來提高性能,盡管根據筆者經驗,這種情況從未發(fā)生過。將數據保存在同一數據庫中的好處是巨大的。
6、回歸單體的好處
事實上,這樣做有一個驚人的好處,我們可以在不依賴“最終一致性”的情況下完成交易。當我們嘗試調試和復制分布式系統(tǒng)時,可能會遇到一個很難在本地復制的過渡狀態(tài),甚至很難通過查看可觀測性數據來完全理解。
原始性能消除了大量網絡開銷。通過適當調整的二級緩存,我們可以進一步刪除80-90%的讀IO。在微服務中,要實現這一點要困難得多,而且可能不會刪除網絡調用的開銷。
正如我之前提到的,應用程序的復雜性在微服務架構中不會消失。我們只是把它搬到了另一個地方。所以從這個層面講,微服務并不算真正的進步,因為在此過程中平白添加了許多移動部件,增加了整體復雜性。因此,回歸更智能、更簡單的統(tǒng)一架構更有意義。
7、再看微服務的賣點
編程語言的選擇是微服務親和力的首要指標之一。微服務的興起與Python和JavaScript的興起相關。這兩種語言非常適合小型應用程序,對于較大型的應用就不太適用了。
Kubernetes使得擴展此類部署相對容易,因此為已經增長的趨勢增添了動力。微服務也有一些相對快速的升降能力。這可以以更細粒度的方式控制成本。在這方面,微服務被出售給組織,作為降低成本的一種方式。
這并非完全沒有優(yōu)點。如果以前的服務器部署需要強大(昂貴)的服務器,那么這一論點可能有一定道理。這可能適用于極端使用的情況,比如:突然面臨非常高的負載,但隨后沒有堵塞。在這些情況下,可以從托管的Kubernetes提供商動態(tài)(廉價)獲取資源。
微服務的主要賣點之一是組織調度方面。這使得各個敏捷團隊能夠在不完全了解“大局”的情況下解決小問題。問題也在于此,這就會創(chuàng)造一種“單干”文化,讓每個團隊都“自己做自己的事情”。在縮減規(guī)模的過程中,尤其是在代碼“腐爛”的情況下,問題更甚。系統(tǒng)可能仍能工作數年,但實際上無法維護。
8、互聯網建立在單體之上
為什么要離開呢
筆者組內中的一個共識是,我們應該始終從單體開始。它更容易構建,如果我們選擇使用微服務,我們可以稍后將單體分解。
提及具體某個軟件相關的復雜性,我們討論單個模塊而不是單個應用程序要更有意義些。二者在資源使用和財務浪費上的差異是巨大的。在這個追求“降本”的時代,為什么人們還要不知變通地默認構建微服務,而不是動態(tài)的模塊化單體架構呢?
我們可以從這兩大“架構陣營”學到很多東西。誠然,微服務為亞馬遜創(chuàng)造了奇跡。但公平地說,他們的云成本已包含在這個奇跡之中。所以,一位的搞微服務教條肯定是有問題的。
另一方面,互聯網是建立在單體之上的。它們中的大多數都不是模塊化的。兩者都有普遍適用的技術。因此,筆者看來,正確的選擇是構建一個模塊化的單體結構,先搭建好合適的身份驗證基礎設施,如果我們想在未來轉向微服務,我們可以利用這些基礎設施來進行解構拆分。
9、后記
在設計應用時,我們目前更多是面臨“二選一”的架構選擇:單體和微服務。它們二者通常被視為相反的方法。
在小型系統(tǒng)演進過程中,有這樣一個不爭的事實:單體應用程序往往會隨著時間的推移而在架構上降級,即使在其生命周期開始階段就定義其為架構。隨著時間的推移,各種架構的禁止事項會不知不覺地進入項目,久而久之,系統(tǒng)變得更難改變,進化性受到影響。
另一方面,微服務提供了更強的分離手段,但同時也帶來了許多復雜性,因為即使對于小型應用程序,團隊也必須應對分布式系統(tǒng)的挑戰(zhàn)。
單體回歸,也是具體的有條件的回歸。我們看到,趨勢的改變,代表著某段時期具體任務或者目標正在變化。出于目標的變化,我們對于微服務和單體架構的二選一的選擇問題,也不能再教條式的看待。
事物往往都在螺旋式的演進,對于架構而言,亦如是。
原文鏈接:https://dzone.com/articles/is-it-time-to-go-back-to-the-monolith