微服務架構設計

簡介

相關概念

  • 背景:隨着代碼庫愈來愈大,代碼修改困難 、 模塊之間界限模糊 、 類似代碼過多。
  • 內聚性 - 單一職責原則:相同緣由而變化的東西放在一塊兒,因不一樣緣由變化的東西分離開來;微服務將這個理念應用到獨立的服務上,根據業務的邊界來肯定服務的邊界。
  • 微服務是SOA的一種特定方法

特性:前端

  • 一個微服務就是一個獨立的實體,能夠獨立部署
  • 服務之間經過網絡進行通信
  • 服務彼此間能夠獨立的進行修改,服務的部署不該該引發消費方的變更
  • 服務暴露過多,會形成和消費方的緊耦合

優勢:算法

  • 技術異構性: 嘗試新技術,下降風險
  • 系統中組件不可用,不會形成級聯故障
  • 擴展:對服務進行鍼對性的擴展
  • 簡化部署:特定代碼部署,不影響系統總體,快速回滾
  • 組織結構匹配: 不一樣的團隊負責不一樣的服務
  • 可組合性: 對不一樣的場景組合服務

分解技術

微服務spring

  • 分佈式系統的複雜性
  • 部署、測試、監控的投入
  • 類型分佈式事務和CAP的考慮

共享庫數據庫

對重複代碼進行分包組織,工具類,重複業務代碼類。缺點以下:編程

  • 沒法使用異構技術
  • 每次更新,須要將相關的程序從新部署
  • 公共任務而且不屬於業務代碼,能夠這樣作,但若是涉及服務間的通信,會成爲耦合點

模塊json

Erlang的模塊化能力驚人;難度比較大,很容易會和其餘代碼耦合在一塊兒後端

微服務演化

須要注意細則瀏覽器

  • 架構師相似城市規劃師,專一在大方向上,有限狀況參與到具體的開發,不關注每一個區域內發生的事,更關注區域之間的事情(服務之間的交互)
  • 將來的變化很難預見,對全部可能性進行預測,不如作一個容許變化的計劃
  • 系統設計方面的決定一般是取捨。
  • 爲了和更大的目標保持一致,制定一些具體的規則,稱爲原則
  • 原則做爲指導,約束是很難被改變的。顯示指出二者,並按期回顧是否要修正。
  • 編寫文檔是有用的,配上真實的代碼範例
  • 架構師提供一些溫和的指導,讓團隊自行決定什麼時候償還債務,維護一個債務列表,並按期回顧
  • 偏離原則:針對某個場景記錄下來,當例外不少次出現,考慮修改原則
  • 架構師和團隊小組存在分歧,大部分狀況要認同小組的決定。

要求的標準緩存

  • 建議確保全部的服務使用一樣的方式報告健康狀態 及 監控相關的數據,標準化,隱藏具體技術實現, 日誌服務和監控服務同樣,要集中化
  • 使用統一的接口協議

如何建模服務

概念 & 準則

鬆耦合安全

  • 獨立修改部署而不影響系統的其餘部分
  • 限制服務間的調用數量,除了性能問題,過分通信會形成緊耦合

高內聚

改變某個行爲,只須要在一個地方進行修改,就能夠儘快發佈,快速修改,低風險發佈

bounded Context(限界上下文)

  • 每一個限界上下文分爲兩部分,一部分不須要和外部通信,一部分須要。 有明確的接口,決定了暴露哪些模型給其餘界限上下文
  • 模塊邊界就能夠成爲絕佳的微服務候選,熟練了以後,能夠省掉在單塊系統中先使用模塊的這個步驟,而直接使用單獨的服務
  • 思考限界上下文時,不該該從共享數據的角度來考慮,而應該從這些上下文能提供的功能來考慮,不然會演變成基於模型, 從而致使貧血、基於CRUD的操做
  • 逐步劃分上下文,一開始識別粗粒度的限界上下文,而這些限界上下文會嵌套一系列子限界上下文,兩種作法:

    • 嵌套上下文不直接對外可見,用的仍是粗粒度上下文的功能,但發出的請求被透明的映射到其餘服務上(更好的測試)
    • 將子限界上下文單獨拆分紅服務

共享的隱藏模型:

  • 財務和倉庫兩個限界上下文,會對倉庫的 庫存模型存在交集,針對庫存模型, 應該存在 內部和外部兩種表示方式,不暴露全部屬性
  • 共享特定模型,不共享內部表示能夠避免潛在的緊耦合,
  • 一旦發現了領域內部的限界上下文,必定要使用模塊對其進行建模,同時使用共享模型和隱藏模型

其餘

  • 對於一個新系統而言,可使用一段時間單系統,避免後期的修復代價。
  • 將一個已有的代碼庫劃分爲微服務,比從頭開始構建微服務要簡單

集成

集成技術選型:

  • 不該該選擇那種對微服務具體實現技術有限制的集成方式
  • 使服務易於消費方使用(提供客戶端庫能夠簡化使用,可是增長了耦合)
  • 隱藏內部實現,避免服務方的任何修改均可能影響到消費方

共享數據庫

  • 外部系統可以查看內部實現細節,並與其徹底綁定在一塊兒,全部服務均可以完成訪問該數據庫; 若是修改數據庫會致使消費方沒有辦法工做。須要大量的迴歸測試
  • 消費方和服務綁定在一塊兒,沒法輕易的替換技術

同步與異步

同步:及時的獲得操做的響應 ;請求/響應
異步:適用長時間的操做;基於事件

編排與協同

場景:建立用戶的操做, 須要發放優惠券、建立銀行帳戶、發送歡迎郵件

編排

  • 使用客戶服務做爲中心,同步順序的調用操做,能及時知道每一步是否成功
  • 客戶服務成爲中心控制承擔了太多職責,中心樞紐和不少邏輯的起點

協同

消除耦合,但沒有明顯的流程視圖,沒法保證每一步流程都正確執行,須要更多額外的工做,來構建一個與業務流程匹配的監控系統,

折中方案

使用異步回調的方式。

請求/響應的技術:

RPC

  • 核心特色,使用本地調用的方式和遠程進行交互。
  • 核心思想是隱藏遠程調用的複雜性,可是不少框架隱藏過頭了;使用本地調用不會形成性能問題,可是RPC花大量的時間來對負荷和解封裝,以及網絡通訊的時間,簡單的把一個遠程服務改形成跨服務的遠程API每每會帶來問題
  • 更糟的狀況是: 開發人員不知道調用時遠程調用,並對其進行使用
  • 網絡的出錯模式不止一種,很難對問題進行定位
  • 脆弱性:對象參數的修改,須要對客戶端從新生成打樁,應用這些修改須要同時部署客戶端和服務端
  • 選用RPC,必定不要對遠程調用過分抽象,確保能夠獨立的升級服務器,不要隱藏網絡調用的事實

REST

  • HTTP周邊有很大的生態系統,包含不少支撐工具和技術,好比 Varnish HTTP緩存代理 / mod_proxy 負載均衡 / 大量的監控工具
  • HTTP也能夠用來實現 RPC,好比soap就是基於HTTP進行路由的,只是使用了少許的HTTP特性
  • 對於有些接口來講,HTML既能夠作UI,也能夠作API,
  • 建議使用XML,在工具上有不少支持
  • springboot過多的約定帶來了緊耦合
  • 使用客戶端庫會增長複雜度,由於人們不自覺地回到基於HTTP的RPC思路上去了,而後構造出一堆共享庫,在客戶端和服務端之間共享代碼是很危險的,
  • 在低延遲要求的服務中,HTTP的封裝開銷須要注意
  • 低延遲通訊最好的選擇是TCP編程
  • REST獲得序列化和反序列化須要本身實現,會成爲消費者和服務端的耦合點

基於異步的實現

增長開發流程的複雜度,須要額外的系統才能開發及測試,須要額外的專業知識和機器保持基礎設計正常運行

  • 原則: 儘可能讓中間件簡單,將邏輯放在本身的服務中
  • 設置最大重試次數,失敗的消息統一發送到一個地方,進行查看和重試,
  • 確保使用監控機制保證每一個流程,而後對流程進行ID關聯 (zookeeper)
  • 把關鍵領域的生命週期顯示建模出來很是有用,不但能夠在惟一的地方處理狀態衝突,還能夠在這些狀態的基礎上封裝一些行爲

災難性故障轉移: 隊列中存放了任務,消費者A處理崩潰,消費者B處理也崩潰,一個異常元素致使一系列的消費者崩潰。

DRY:避免重複代碼

若是有相同代碼作一樣的事情,代碼規模就會變大,從而下降可維護性

建立一個隨處可用的共享庫?

  • 在微服務中是危險的,會致使耦合,客戶端和服務端須要同時更新部署
  • 但在服務間使用日誌庫代碼不是問題,由於對外是不可見的
  • 服務間使用共享庫比重複代碼還要可怕

客戶端庫

若是要使用,要保證只包含處理底層傳輸協議的代碼,好比服務發現和故障處理等等,千萬不要把與目標服務相關的邏輯代碼放到客戶端庫中

按引用訪問

  • 微服務應該包含核心領域實體的全生命週期的相關操做,服務應該是關於該領域的惟一可靠來源
  • 對服務發起一個資源的請求,而後保存在本地副本中,可能一段時間會失效,因此請求返回的結果,要保存一個指向原始資源的引用(好比一個資源URL),確保須要最新數據的時候能夠有辦法獲取
  • 老是經過一個服務去獲取某個領域的信息,會形成過多的負載,若是可以獲得該領域的有效時間是最好的

版本管理

  • 儘量不作破壞性修改,使用良好的架構設計
  • 鼓勵客戶端正確的行爲,例如json傳輸數據,一些強類型語言會使用綁定技術,會將全部的字段綁定,不管消費者是否須要,當修改接口數據結構的時候會影響到消費者,可使用XPath技術提取出想要的字段
  • 魯棒性原則,每一個模塊都應該 寬進嚴出,發送的東西要嚴格,接收的東西要寬容
  • 使用語義化的版本管理,格式以下:major.minor.patch ;major表明包含向後不兼容的修改; minor意味着新功能的增長 ; patch表明對缺陷的修改
  • 不一樣接口能夠共存,發佈一個破壞性修改的時候,能夠部署一個包含新老接口的版本;但更建議在V1接口中轉換後請求V2接口
  • 同時使用多個版本的服務

BFF(Backed for frontends)爲前端服務的後端

對於不一樣的客戶端,使用聚合接口,對後臺調用的服務進行編排,相似於一個專門的後臺服務,好比Node程序,對JAVA後臺的接口進行組合,也稱做

分解單塊系統

  • 首先識別出單塊後臺系統明顯的幾個上下文
  • 爲他們建立包結構來表示,把已有的代碼進行移動

解決橫跨不一樣上下文的表

  • 打破外鍵約束,將訪問變成邏輯外鍵,經過暴露的API進行交互
  • 共享的靜態數據,經過配置文件和代碼中進行配置,不要放在公有包中

共享數據

  • 不一樣的上下文會對同一張表進行讀寫操做:概念領域不是在代碼中進行建模,相反是在數據庫中隱式的建模,表明這個表是一個上下文,做爲一箇中間步驟,能夠建立一個新的包最終變成一個服務
  • 共享表:存在一個通用的行條目錄表,不一樣上下文都用到了部分數據:能夠分紅兩張表

重構數據庫

  • 先分離數據庫結構,不對服務進行分離
  • 對數據庫的訪問次數會變多,之前一個查詢得到全部數據,如今要內存中進行組裝

事務邊界

一個事務能夠幫助咱們的系統從一個一致性狀態 轉移到另一個一致性; 分離數據庫以後,沒有了原生的事務處理,解決方案:

  • 再試一次:把失敗的操做,記錄在日誌或者失敗隊列中,後面對他們嘗試觸發,要保證從新觸發可以成功,最終一致性
  • 終止整個操做:對上一個成功的操做進行補償事務來抵消以前的操做,可靠性不佳
  • 分佈式事務:外部的事務管理器統一編排執行,經常使用算法是兩階段提交,可靠性也不佳

總結:是否真的須要強一致性? 是否要跨業務進行操做? 是否能夠經過業務邏輯的處理避免事務,好比新增處理中的訂單狀態

報表:

  • 爲了防止對主系統的影響,報表的查詢使用副本; 缺點:共享數據庫結構會抑制修改表結構的積極性
  • 使用MongoDB或基於列的數據庫來 保存副本

數據庫分佈在不一樣的系統中

  • 經過服務調用來獲取數據:少許的數據能夠考慮在內存中進行組合
  • 大數據讀取:使用HTTP POST方法,攜帶一個位置信息,讓服務器返回200,把獲取的內容寫入到文件中,而後保存在請求的位置上,客戶端輪詢請求,直到返回201,這樣就減小了HTTP的開銷
  • 數據導出: 使用一個獨立的服務,直接訪問不一樣的微服務使用的數據庫,導出到單獨的報表系統中;在報表數據庫中包含了全部的服務數據結構,而後可使用視圖之類的技術來建立一個聚合。
  • 事件數據導出:在事件發生時就給報表系統發送數據,而不是週期性的導出,增量導入更高效。 缺點:數據量較大時不容易擴展
  • 對數據導出的備份進行處理:可使用Hadoop對數據處理後,儲存起來

部署

持續集成(CI)

  • 當構建失敗以後,把修復CI看成第一優先級要處理的事情
  • 集成須要測試,這樣才能保證集成代碼的正確性,否則只是對語法錯誤進行檢查
  • 每一個微服務要有一個專有的CI,包含測試代碼

構建流水線和持續交付(CD)

  • CD可以檢查每次提交是否到達了部署生產環境的要求,並持續的把這些消息反饋給咱們,把每次提交當成候選版本對待
  • 在CD中,會把多階段構建流水線的概念進行擴展,從而覆蓋軟件經過的全部階段
  • 編譯及快速測試 -> 耗時測試 -> 用戶驗收測試 -> 性能測試 -> 生產環境

測試

單元測試

一般只測試一個函數或者方法,經過TDD寫的測試就屬於這一類,不啓動服務,對外部網絡和文件使用也頗有限;面向技術,對功能正常給出快速反饋

服務測試

  • 對於包含多個服務的系統,一個服務測試只測試其中一個功能
  • 爲了達到隔離性,須要爲其餘服務打樁,MOCK

端到端測試

  • 會覆蓋整個系統,一般須要打開一個瀏覽器來操做圖形界面。
  • 測試類型的比例:應該是不一樣數量級的
  • 隨着測試的範圍擴大,遇到的可能狀況也越多,發現脆弱測試時,應該不遺餘力去解決,避免異常正常化(對事情出錯變得習覺得常);當不能當即修復的時候,從測試套件中移除。
  • 不要輕易刪掉測試代碼,除非你理解風險
  • 測試場景,而不是故事:測試的重點放在覈心的場景中,其餘場景在服務測試中進行。

CDC

消費者驅動測試:定義消費者的指望,服務端沒有達到預期將沒法部署,有助於不一樣團隊一塊兒來編寫代碼

部署後在測試:

  • 部署以前的測試不能保證零缺陷,部署只是在正式環境啓動,不表明引入正常流量。
  • 藍綠部署 -> 冒煙測試 -> 切換流量
  • 金絲雀發佈:少許流量引入新部署的服務中,而後不斷的調節流量來驗證咱們的功能性和非功能性。進行計分而後確認徹底切換,簡單的作法Nginx分流,複雜的複製生產環境請求
  • 性能測試:原來的單次調用可能會變成屢次調用,以及跨數據庫,會影響到整個微服務調用鏈,因此比單塊系統更加劇要

微服務規模化的挑戰:

瞭解真正的需求:響應時間、延遲、可用性、數據持久性的 權衡

功能降級:當出現問題的其餘處理方式

  • 程序使用HTTP鏈接池來處理下游連接:若是某個下游請求故障,可是HTTP設置了超時時間,就會致使大量的請求堆積,全部的worker都在等待超時,阻止創建新的HTTP請求,致使系統大範圍不可用
  • 在分佈式系統中,延遲是致命的

解決方案:

  • 正確的設置超時時間
  • 實現資源隔離,使用不一樣的鏈接池
  • 實現一個斷路器,快速失敗

斷路器

對下游資源請求失敗的次數到達必定數量,斷路器打開,全部請求快速失敗,一段時間發送請求成功,將會重置斷路器

資源隔離

分配不一樣的資源,當某個部分資源耗盡不影響其餘的組件

冪等

確保部分操做冪等安全性,Nginx的重試不包括POST請求。

擴展

  • 幫助處理失敗,額外的程序保證正常運行
  • 性能擴展,減小延遲增長負載

強大的主機

稱之爲垂直擴展,若是軟件沒有充分利用也是白搭

分散風險

不要把全部的微服務放在一個地方

負載均衡:SSL終止

經過HTTPS鏈接到負載均衡器後(Nginx),轉到http server ,變成HTTP鏈接,提升性能。HTTP鏈接在局域網中,全部外部請求經過一個路由訪問內部

擴展數據庫

  • 擴展讀操做:經過多個副本擴展,通常有一致性問題,確保能夠接受
  • 擴展寫操做:對數據進行哈希,基於哈希分配到一個數據庫中,缺點:查詢複雜(mongo map/reduce),擴展困難
  • 每一個微服務一個單獨的數據庫實例,避免一個數據庫實例分配多個數據庫

緩存

代理服務器緩存,介於客戶端和服務端之間; 客戶端緩存以及服務端緩存,通常都是三者混用

HTTP緩存:

  • 對客戶端的響應使用 cache-control指令:告訴是否緩存以及時限
  • 設置Expire頭部,指定一個日期,該日期以後失效
  • Etag用來標誌資源是否匹配,有一種請求方式叫作條件GET

緩存失效

後臺異步生成緩存,接受部分實時請求(服務端可能負載),其餘請求快速失敗,異步生成緩存

自動伸縮

不一樣的流量對服務進行自動伸縮。

CAP: consistency / availability / partition tolerance

通常是AP: 分區可用,最終一致性; CP:一致性 ,可是拒絕新請求

相關文章
相關標籤/搜索