1. 程式人生 > >一個經過優化的微服務架構案例

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

http://www.infoq.com/cn/articles/an-optimized-micro-service-architecture-case?utm_source=tuicool&utm_medium=referral

前言

大家都知道,基於單體(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 程式碼可以在這裡找到。

它的 development 專案看起來是這樣的:

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

實踐經驗

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

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

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

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

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

總結

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

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