search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

一個經過優化的微服務架構案例

作者|Joakim Tengstrand編譯|楊雷

大家都知道,基於單體(Monolith)和微服務(Microservice)架構的爭論已經存在多年,正如我們對胖客戶端、瘦客戶端孰好孰壞的爭論一樣,有必然的歷史演化,也有各自的優缺點。架構師們總是在考慮,我們是要一個中心化、全能多才的單體,還是百花齊放、各自為政的微服務群體,各種基於成本、交互、部署等等的討論應該不會停下腳步。這裡,我們不做過多的深入探討和介紹,而本文正是這些討論中的一個很好的例子,供大家思考。

Joakim Tengstrand 近期在他的推特上提到,看到 Robert C. Martin(Bob 大叔)多年前描述的基於 JAR 包微服務的不足之處,於是寫下了這篇有趣的稱為微單體(micro monolith)的文章,並在Git上發布了基於 Java 和 Clojure 的兩種源碼包供下載。接下來將逐一介紹微單體的概念、工作方式、各自的優缺點、示例、實踐等環節。

微單體是什麼

用成千上萬或數百萬行代碼來編寫高質量的軟體,可能是開發人員所能承擔的最具挑戰性和最複雜的任務之一。Tengstrand 利用軟體模塊化這種非常簡單的方式,實現了一個方案並期望能有助於完成這項挑戰,提供了基於 Java 和 Clojure 的代碼供參考。

代碼見:

隨著系統變得越來越大,最終會達到一個臨界點,作為一個單體(monolith)它變得難以管理。每一行代碼的添加,都會讓系統變得更加難以理解、變更和重用。雖然微服務(microservice)試圖解決這些問題,但也帶來了額外的複雜性以及集成的成本。

微單體架構的核心原則是保持硬體、軟體和數據緊密地結合在「一個地方」。這樣處理可以簡化事情,擺脫不必要的協調工作。如果我們從同一個地方直接訪問數據,性能也會得到改善。當設計系統時,可以像微服務一樣使用小的、孤立的、可組合的構建塊(building blocks),又可以像單體一樣通過一個地方執行它們,就能從兩方面都達到最優。

從上面的介紹可以看出,單體就像一個胖服務,微單體利用微服務架構的優勢將處理工作從邏輯上分拆出去,在此基礎上增加一層調度(編排服務)來管理所有的微服務。這樣一來,使得業務的完整性和一致性得到了較好的保證,也解決了跨服務集成的問題。用一個簡單的例子來描述,單體就好像把所有的文件放在了一個文件夾里,微服務則試圖將它們分類並放在不同文件夾中,而微單體的方法是生成了一個叫做 development 的文件夾,裡面保存了所有文件的快捷方式(shortcut),這樣更易於根據不同的業務場景來管理和訪問這些「文件」。

原理與思路

在版本控制系統里為每個服務生成一個項目,這樣可以得到各自的 JAR 包(假設在JVM上運行,或者類似平台)。它們就是構成系統的構建塊,並最終組成整個生產系統。生成一個 development 項目,通過服務把所有源代碼連接起來,這樣就可以在裡面直接運行源代碼,就像是只有這一個項目一樣。

接下來介紹微單體架構的優缺點。

優點

  • 簡單性(關注點分離,代碼直接調用,消除了網路 API 的複雜度)

  • 卓越的性能(沒有訪問服務的網路調用)

  • 模塊化、可組合的服務(在不同的系統中重複利用)

  • 跨服務事務的數據一致性(無需考慮最終一致性)

  • 減少 DevOps 和硬體/託管的費用(在單機上運行的系統)

  • 易於測試(可以對整個系統進行一體化測試)

  • 更快和更有效的開發體驗(跨服務導航、重構和調試 + 變更無需重建服務)

####缺點

  • 必須在所有服務中使用相同的編程語言

  • development 項目需要一些額外的設置(創建符號鏈接 symbolic links)

  • 操作系統必須支持符號鏈接 symbolic links

  • 共享相同路徑的資源在所有服務中必須具有唯一的名稱(它們有不同的內容)

  • IDE 內置的版本控制失效(因為微單體架構可能不支持 IDE)

當然其實並沒有強迫只使用一種編程語言,我們只是想讓 development 項目發揮其優勢(例如跨服務的重構和調試),這時最好的選擇是使用一種語言。其次是混合使用,可以使用在同一個平台(比如 JVM)上運行的語言,像 Java、Scala、JRuby、 Clojure(如果使用本地介面JNI,還可以選擇 C),但如果一個服務做了變更,就需要生成一個新的 JAR 包並共享給其他服務。

上面介紹了微單體方法的概念,還沒有提到它是如何在實踐中工作的,現在讓我們通過 Java 和 Clojure 的兩個示例來進行演示。所有示例的代碼可以在這裡找到。註:在實際系統中,它們被存儲在單獨的資源庫里並且彼此隔離,這裡為了方便起見,它們被存儲在同一個資源庫中。

下面 Java 和 Clojure 的例子會實現相同的「解決方案」,利用一個假造的 REST API 來編排一些服務,並暴露findaddresses,douserstuff 和 domoreuserstuff 供調用。

Java - 示例代碼

Java 是一種流行語言,這裡將展示在面向對象語言里的微單體架構是什麼樣子的。

示例:

在處理 development 項目時,你在大部分時間裡會是一個開發人員。雖然所有的服務都被存為各自獨立的項目(Git),這裡我們通過一個技巧,利用符號鏈接把所有源碼「放到」一個單獨的項目中。IDE 並不關心地址是「真正的」還是一個鏈接,都採用箭頭來標記它們(至少在這個例子里是這樣的):

項目在本地check out后,這些鏈接在 Linux 或 Unix 上 馬上就能工作。如果是其它平台,可以參考這樣類似的腳本,手動地創建 development 項目。

建立了這個項目,通常意義的甚至跨服務的開發環境所具備的調試、重構和搜索等等方面的好處,我們都可以實現。這一點非常強大並且節省了時間,每次代碼變更不需要重建服務,這樣使得工作流程非常高效和快樂。

依賴性

在設計系統時,需要決定是否允許服務獲得外部庫(external libraries)具體實現的信息。在本文的例子里,我們選擇信息透明,最好在所有服務中使用相同版本的外部庫。

另一種選擇是通過在內部服務與外部庫之間添加介面來集成,這樣一個服務就不用知道具體庫的信息,比如 log4j-1.2.17.jar,只需要生成一個介面 log4j-api,這樣編排服務(orchestrator service)就能把它注入到所需的服務中去了。

編排服務(The orchestrator service)

編排服務就是把所有服務都放到一起的那個服務。一個系統可以有多個編排服務,這裡的例子只有一個 RestService,它依賴地址,電子郵件和用戶這幾個服務,並在 pom.xml 里進行指定。

如果服務 A 需要調用服務 B 里的函數 f,可以通過編排服務把函數 f 注入到服務 A 中。這裡沒有強制要求一次只注入一個函數,但是這樣做可以降低服務間的耦合,增加可變性(changeability)和可測性(testability)。我們將使用「微注入」這個術語,特指一次只注入一個函數的意思。

測試

微單體架構鼓勵讓測試變得簡單、容易,就像微服務一樣,要讓每個服務的獨立測試更方便。相比於微服務, 微單體就像一體化部署在一台機器上一樣,它讓測試變得更加的簡單(比如本例的 REST API)。

例子里包含了一個測試數據生成器,它幫助我們在已知的狀態下建立資料庫。可以存在一個用戶表和一個地址表相關聯,然後就得到一個 UserService 和一個 AddressService。測試數據生成器可以方便地設置資料庫的已知狀態,這有助於編寫集成測試。這可以在某個服務中完成並且實現跨服務調用,例如 AddressServiceTest 和 UserServiceTest。

Clojure - 示例代碼

Clojure 是一個功能強大的語言,能在 JVM 上運行。下面將展示在 Clojure 這樣的函數式語言中如何使用微單體架構。所有 Clojure 代碼可以在GitHub找到。

鏈接:

它的 development 項目看起來是這樣的:

Clojure 版本的結構基本類似 Java 版本,但函數都存儲在不同命名空間而不是類中,也不需要像Java一樣為地址和電子郵件服務添加額外的 API 層。Clojure 版本更加簡潔,可以用大約 200 行代碼實現 Java 里 400 行代碼能完成的工作。

Clojure 里的微注入會更簡單,可以用宏注入(inject macro)的方式來注入函數。這裡的例子里,在命名空間rest.service 的第8行,就是用函數 email/send-pdf-email! 替換了原來的 user.service/send-pdf-email!。

實踐經驗

Tengstrand 和他的團隊已經把微單體架構應用到了一個真正的生產系統。他們搭建了這樣一個架構,每一個服務在 Git上 有各自的資源庫。遷移到微單體是非常順利的,要做的就是丟棄 30% 的代碼,並把所有 REST 服務調用替換成簡單的函數調用。這個過程中消失的不僅僅是 REST 部分,還有很多複雜的狀態和錯誤處理。

從一開始,就像生產環境一樣設置開發環境,每個服務就是一個 Java 存檔(JAR 文件)。缺點是每次的服務變更,必須重建這個 JAR 文件,以便它可以供其他服務使用。另一個缺點是,我們必須重新啟動 REPL(是的,使用的是Clojure!),這樣做耗費了時間並把在 REPL 上工作的一些快樂也帶走了。

於是,Tengstrand 的團隊想出了新的方法來設置 development 項目,只需啟動一次 REPL ,然後可以繼續工作而不會被打斷,這樣一來開發人員就幸福多了。另一件事是,他們意識到服務里有一些灰色標記的死掉(dead)的代碼,現在也可以去掉了。

另一個設計上的選擇是採用 Datomic 資料庫,它真的與微單體架構很適用,既簡單又強大。你可以在這裡了解它的更多架構細節。

他們使用測試數據生成器來處理幾乎所有的服務,讓集成測試更簡單。之前他們發現可以改進服務中的一些變數和函數的命名,但更改之前必須手動搜索和替換所有的服務,這樣做費時又容易出錯。最後他們並沒有做這些小的改動,而是通過development 項目,利用 IDE 對重構的支持瞬間就做到了變數和函數的重命名。

實踐總結

微單體提出了如何構建系統的一個簡單模式,雖然和微服務競爭但並不能完全取代它,因為後者肯定有它的位置。如果有需要的話,隨時可以同時使用它們。

最後 Tengstrand 建議,如果在構建系統時非常關注簡單性和可組合性,那麼一定要嘗試下微單體架構,享受這個架構帶來的高效,以及測試、生產環境的簡潔。

谷歌新發布的分散式資料庫服務,是要打破CAP定理了嗎?

推薦一個對技術人員的成長很有幫助的線下會議,將於4月16~18日舉行的QCon全球軟體開發大會(北京站),目前已經邀請來自Google、Facebook、LinkedIn、Airbnb、百度、阿里巴巴、騰訊等公司的100多位一線技術專家,是難得的線下交流學習的機會。具體詳戳「 閱讀原文 」驚喜不停!



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦