微服務架構與實踐
王磊html
單一架構,不考慮服務器集羣,全部的代碼最終運行在一個進程當中算法
單層架構,用戶 --> 表示層、業務層、數據訪問層混合在一塊兒 --> 數據庫數據庫
二層架構,用戶 --> 表示層、業務層、數據訪問層(分離) --> 數據庫
三層架構,用戶 --> 表示層(分離)、業務層(分離)、數據訪問層(分離) --> 數據庫編程
分佈式架構瀏覽器
SOA,面向服務架構,WebService
微服務架構,業務獨立,高內聚,單一職責,輕量級通訊機制、與語言、平臺無關服務器
微服務通訊架構
在微服務架構中,服務和服務之間通訊時,一般是經過輕量級的通訊機制,實現彼此間的互通互聯,互相協做。所謂輕量級通訊機制,一般是指與語言無關、與平臺無關的這類協議。經過輕量級通訊機制,使服務與服務之間的協做變得更加簡單、標準化。併發
服務節點之間通訊,能夠採用同步通訊方式或異步通訊方式。框架
RPC,遠程過程調用,遠程過程調用採用客戶端/服務器端的模式,請求的發起者是客戶端,提供響應的是服務器端。客戶端經過客戶代理存根( Stub),傳遞函數參數,向服務器端發起函數調用。服務器端經過服務器代理存根( Skeleton),接收到客戶端的請求後,對請求進行處理,並在結束後向客戶端返回響應,從而完成一次通訊。異步
傳統的遠程過程調用框架主要包括 Sun RPC(主要基於 C語言實現,以函數調用爲主。隨着面嚮對象語言的快速發展,序列化/反序列化等特性的誕生,基於不一樣語言的遠程過程調用框架開始提供對象遠程訪問的功能,譬如 Java RMI、 Thrift 或者 protocol b uffers等。同傳統的遠程過程調用框架相比,有一類框架可以容許客戶端經過面向對象的調用方式,調用遠端的實現,咱們稱這類調用爲遠程方法調用( Remote Method Invocation, RMI)。換句話說,遠程方法調用是遠程過程調用的一種面向對象的實現。
優勢:遠程過程調用經過使用代理存根( Stub/ Skeleton)的方式,屏蔽了通訊雙方底層的調用細節,讓客戶端沒必要顯式地區分當前代碼級別的方法調用是本地調用仍是遠程調用,所以使分佈式節點間的通訊變得簡單。
弊端:一、耦合度高大部分遠程過程調用的實現機制,是依賴於編程語言或者特定平臺的,所以限制了客戶端和服務器端採用的技術,耦合度較高。一旦使用後,通訊的雙方就很難再切換到其餘語言或者平臺上。譬如,若是使用了 Java RMI做爲通訊機制,就意味着對應的通訊雙方都必須運行在 JVM平臺上,這一點在很大程度上限制了未來技術的替換。 Thrift和 protocol buffers支持多種語言,能夠稍稍地弱化這個缺點。
遠程過程調用,是一種典型的分佈式節點間同步通訊的實現方式。其爲客戶端提供了簡潔、透明的調用接口,但同時因爲其依賴於開發語言以及特定的平臺,所以耦合度較高,而且靈活性不太好,並不適合做爲一種輕量級的機制知足服務之間通訊的要求。
REST(表述性狀態傳遞)是近幾年使用較普遍的分佈式節點間同步通訊的實現方式。
REST從語義層面將響應結果定義爲資源,並使用 HTTP的標準動詞映射爲對資源的操做,造成了一種以資源爲核心、以 HTTP爲操做方式的,與語言無關、平臺無關的服務間的通訊機制。
REST列爲同步通訊的實現方式,主要是由於 REST是基於 HTTP的,而 HTTP的通訊是同步過程(雖然可使用 AJAX等方式將通訊的過程異步化,但其本質上仍是同步的過程),因此能夠認爲 REST的通訊過程是同步的。不過,也能夠將 REST的架構風格應用於異步通訊過程當中,譬如在後臺任務系統中,可使用 REST風格做爲服務調用的協議。
REST的核心資源( Resource)是一個抽象的概念,是指對某類信息實體的抽象。
實體是指服務器端須要處理的具體信息,它能夠是一段文本、一張圖片、一首歌曲,
與面向對象設計的概念相似,資源一般以名詞爲核心來定義。每一個資源對應一個特定的 URI做爲標識。對某個資源感興趣的客戶端應用,能夠訪問資源的 URI與其進行交互。
資源的表述( Representation)是對資源在某個特定時刻的狀態的描述。
咱們知道,資源是一種信息實體,實體在客戶端與服務器端進行信息交換時,能夠有多種表現形式。譬如,文本能夠用 TXT格式表現,也能夠用 HTML格式、 XML格式、 JSON格式表現,甚至能夠採用二進制格式;圖片能夠用 JPG格式表現,也能夠用 PNG格式表現。這種資源的表述格式能夠在客戶端與服務器端經過請求-響應的協商機制來肯定。
URI僅表明資源的實體,並不表明它的表述。譬如,有些 URL最後的. html後綴名並不屬於表述範疇,表述應該在 HTTP請求的頭信息中用 Accept和 Content- Type字段指定,這兩個字段纔是對「表述」的描述。
Accept表明發送端(客戶端)但願接受的數據類型。 好比:Accept:text/xml; 表明客戶端但願接受的數據類型是xml類型。 Content-Type表明發送端(客戶端|服務器)發送的實體數據的數據類型。 好比Content-Type:text/html; 表明發送端發送的數據格式是html。
統一接口( Uniform Interface)客戶端操做資源的方式,一般是基於 HTTP的 4個動詞( Verb): GET、 POST、 PUT、 DELETE。它們分別對應 4種資源的操做方式:一、 GET用來獲取資源。 二、 POST用來新建資源。 三、PUT用來更新資源。 四、DELETE用來銷燬資源。
隨着團隊或者組織業務的不斷增加,服務器端響應內容複雜度的增長, REST的使用面臨以下兩個挑戰: 一、如何標準化資源結構,返回的JSON字符串格式沒有標準 二、如何處理資源的相關連接,JSON最大的遺憾在於沒有對超連接處理作內置的支持。
HAL( Hypertext Application Language)是一種輕量級超文本應用描述協議。 HAL的實現基於 REST,並有效地解決了 REST中資源結構標準化和如何有效定義資源連接的問題。
在 HAL中,任何服務器端的響應都被定義成一種資源( Resource),這是遵循 REST原則對資源的定義的。同 REST不一樣的是,在每一個資源中, HAL又將其分紅了以下三個標準的部分。
狀態( State)一般是指資源自己固有的屬性。譬如,對於商品列表資源的響應而言,其狀態的定義可能以下所示:
連接( Links)定義了與當前資源相關的一組資源的連接的集合。一般列表頁返回的響應都是分頁的結果,所以須要在資源中定義上一頁、下一頁、第一頁以及最後一頁等相關連接。
子資源( Embedded Resource)描述在當前資源的內部,其嵌套資源的定義。
使用 HAL,還有一個很大的優點,那就是可使用 HAL瀏覽器( HAL Browser)來可視化資源的信息。咱們知道, HAL將資源分紅三個基本的部分:狀態、連接和子資源。基於這個標準的結構, HA L瀏覽器可以將資源的每一部分,經過可視化的方式顯示出來。
消息隊列( Message Queue)是一種處理節點之間異步通訊的實現方式。發送消息的一端稱爲發佈者,接收消息的一端稱爲消費者。經過消息隊列,發佈者將消息放入隊列中保存,而後消費者會在未來某個時間點獲取消息並處理。消息隊列使消息的發佈者和消費者不須要同時交互,從而達到異步通訊的效果。
消息隊列的核心特徵
消息隊列會指定相應的標準和算法,保障消息進入或者出隊列的優先級。譬如最多見的 FIFO(先進先出)算法。
消息隊列提供某種通知機制,幫助消息發佈者知道什麼時候部分或所有接收者收到了消息。
消息隊列的訪問,通常存在以下兩種方式。
拉模式( Pull M ode)要求消費者按期檢查隊列上的消息。
推模式( Push Mode)是每當發佈者將消息添加到隊列中時,會經過某種機制通知消費者。所以消費者可知道消息隊列的變化狀況,這意味着數據的處理更及時,但同時也意味着發佈者、消費者以及隊列必須依賴語言或者平臺的某些特性,作更多的開發和配置工做。一般在推模式下,通常存在多個消費者,也稱他們爲訂閱者。
對於微服務架構的系統而言,使用消息隊列的優勢很是明顯
異步通訊,當構建複雜的分佈式系統時,消息隊列是經常使用的異步通訊方式。
但消息隊列會增長整個系統的開發及維護成本,譬如,對一個用戶註冊的場景,當用戶單擊註冊按鈕後,但願後臺服務進行處理併發送歡迎電子郵件,但同時要求該操做不會影響用戶在頁面上的其餘操做。對於這個場景,若是須要安裝 ActiveMQ、 RabbitMQ,定義消息的發佈者和消費者,顯然太過複雜,所以可使用後臺任務處理系統。
經常使用的後臺任務處理系統有 Resque、 Sidekiq以及 Delayed_ job等。
後臺任務處理系統主要包括: ●任務 ●隊列 ●執行器 ●定時器
隊列( Queue)主要用於存儲任務,並提供任務執行失敗後的錯誤處理機制,譬如失敗重試、任務清理等,一般咱們也稱隊列爲任務隊列。每一個任務隊列都有一個全局惟一的名稱做爲標識。目前大多後臺任務處理系統一般都採用 Redis做爲隊列的實現機制,譬如像 Resque、 Sidekiq等。
對於微服務架構的系統而言,使用後臺任務系統的優勢很明顯,主要包括以下幾項。
請求服務經過定義任務,將交互的請求存在任務隊列中,執行器根據定時器的配置,從任務隊列中取出任務並執行,再回調相關的服務。在整個交互過程當中,後臺任務系統只是承擔了定時觸發回調請求的責任,具體的實現均由被調用服務完成,從開發、測試以及問題定位和調試的角度而言,能夠更集中在被調用服務自己。所以,相比消息隊列,後臺任務系統提供了更輕量級的方式,完成服務間的異步通訊。
一般,後臺任務系統的開發和維護成本比起消息隊列要低得多。
經過使用後臺任務系統,使不一樣服務之間可以有效地完成業務異步通訊。相比消息隊列的方式,後臺任務系統更簡潔易用,也更輕量級。
測試
單元測試是針對程序單元進行正確性的檢驗。理論上,程序單元是指應用中最小的可測部分,譬如函數、代碼片斷等。一般,單元測試都能經過自動化的方式運行。經常使用的單元測試框架有 Java語言的 JUnit,可以使用工具或腳本徹底自動化運行。
接口是服務間的通訊方式和協議,也能夠稱爲契約( Contract)。契約的兩端分別是消費者( Consumer)和提供者( Provider)。使用契約的一方稱爲消費者,提供契約的一方稱爲提供者。契約測試是針對服務接口進行的測試,它能驗證提供者提供的契約是否知足消費者的指望。
Pact是一個基於消費者驅動的契約測試框架。
Pact的使用主要包括兩步:消費者端生成契約與提供者端完成校驗。
消費者端使用 Pact框架提供的 DSL以及相關的配置文件,運行一個本地的模擬服務,模擬服務的提供者端。
提供者端使用 Pact框架提供的 DSL以及相關的配置文件,配置 Pact。
集成測試( Integration Testing),是將不一樣的單元按照指望組合起來,對其接口進行正確性檢驗的測試工做。一般,集成測試在單元測試以後、端到端測試以前進行。集成測試的本質是確保當多個單元組合在一塊兒時,可以按照指望協做運行。
組件的概念你們必定不陌生,當將龐大的系統拆分紅不一樣的部分時,每一個獨立的部分就是一個組件。在微服務架構中,每一個服務也能夠看做是一個組件。
對於微服務的組件測試而言,它關注的是當指定輸入時,被測服務是否能提供指望的輸出。實際上,組件測試並不會對服務內部的邏輯作任何模擬或者打樁。所以,經過組件測試,咱們能有效保證當前被測服務的功能是正常的。
端到端測試,又稱 End- To- End Testing或者 System Testing,是從用戶使用系統的角度出發,對系統的行爲進行正確性驗證的測試。
微服務架構的端到端測試,會對系統內多個服務之間的協做進行較全面的覆蓋,這同以前咱們描述的集成測試、消費者驅動的契約測試相比,對服務間協做的測試更加充分和全面。
在金字塔的頂端,是探索性測試。探索性測試並無具體的測試方法,一般是團隊成員基於對系統的理解以及基於現有測試沒法有效覆蓋的部分,作出的系統性的驗證。譬如跨瀏覽器的測試,或者一些視覺效果的測試。由於這類功能變動較頻繁,並且所有實現自動化成本較高。所以,小範圍的探索性測試仍是比較有效的。實際上,探索性測試強調測試人員的主觀能動性,其一般不太容易經過自動化的方式實現,更多的是由團隊成員手動完成。所以,探索性測試的成本最高,自動化難度大,測試以手動執行爲主,同時還須要根據具體的場景改變策略。另外,探索性測試的反饋週期也是最慢的。
使用微服務架構改造遺留系統
因爲是遺留系統,熟悉該代碼的人早已離職多時,新團隊對其望而卻步,只能作些周邊的修補工做。
在沒法中斷業務處理的狀況下,爲了解決當前面臨的問題,團隊制定了以下策略。 ●最小修改 ●功能剝離 ●數據解耦 ●數據同步 ●迭代替換
一、對於大部分非緊急的、非重要的任務,都禁止在原有的系統上進行修改。
二、在現有合同管理系統的外圍,逐步構建功能服務接口,將系統核心的功能分離出來。同時,使用代理機制,將用戶對原有系統的訪問,轉發到新的服務中,從而解耦原合同系統與用戶之間的依賴。
三、隨着部分功能的解耦,已經存在一些微服務可以獨立爲用戶提供功能。這時,逐漸考慮將數據解耦,從原有的單塊架構數據庫中剝離相關的業務數據。儘可能知足對於每一個服務,有獨立的、隔離的業務數據系統。
四、可能在很長一段時間內,因爲新的服務(業務邏輯、數據)獨立出來,致使沒法同現有系統協做。在這種狀況下,爲了持續交付價值,筆者一般會採用 ETL( Extract、 Transform、 Load)的機制,將服務中的業務數據同步到單塊架構的數據庫中,保障原有的功能可以被繼續使用。
五、經過不斷地迭代替換,最終將原有的合同管理系統替換成使用微服務架構解耦的新的合同管理系統。
在微服務的開發實踐中,團隊的微服務快速開發模板( Microservice Template)誕生了。該模板是一個幫助快速構建 Ruby微服務應用的開發框架,主要包括 4部分:快速開發模板、代碼生成工具、持續集成模板以及一鍵部署工具。