系列文章目錄:
通常,我們可能已有有一個巨大的單塊系統,如何實現微服務,我們需要把它分解。
從哪里開始拆分:接縫
接縫:從接縫處可以抽取相對獨立的一部分代碼,對這部分代碼的修改不會影響系統的其他部分。這些接縫就可以作為服務的邊界。
那如何識別出接縫呢?我們可以使用前面所提到的限界上下文,也可通過程序中的命名空間來幫助我們,也可以通過工具來幫助我們,如structure101這樣的工具來可視化包之間的依賴。
雜亂依賴的根源:數據庫
為什么這么說?因為,通常情況下,我們在業務層的代碼已經通過分層組織到相應的包中了,但是只有數據庫是共用的,數據庫對所有的代碼都允許訪問,是一個巨大的API。我們舉例說明:有一張倉儲表,它被“產品目錄”、“倉庫”、“財務”等服務所共用,那么在單塊應用程序中,通常會是下面的結構:
對於同一張表被多個限界上下文使用的場景,我們應該如何處理?以下是一些處理的步驟和原則 :
一.分清代碼中對數據庫進行讀寫的部分
我們需要厘清代碼是如何訪問數據庫的,在什么地方讀,在什么地方寫?他們分別位於什么樣的上下文中。
二.打破外鍵關系
對於表與表之間的外鍵關系,如果這兩張表需要被拆分至兩個微服務中,我們可能需要放棄外鍵關系,同時把這個約束關系放到代碼中實現,我們可能還需要實現跨服務的一致性檢查,或者周期性觸發清理數據的任務。
我們可以通過類似於SchemeSpy這樣的工具來分析數據庫表之間的依賴關系。
三.共享靜態數據
比如,國家、部門之類的數據都是各個微服務之間經常使用的,這些數據的特征是不會經常變,而且是通用性高。這些數據在微服務划分之后該如何處理呢?
方法一:我們可以為每個微服務復制一份這樣的數據,但是這個會導致數據的一致性問題;
方法二:把共享的數據放入代碼之中,比如放在屬性文件 中,或者簡單地放在一個枚舉中,但數據一致性仍然存在。
方法三:把這些靜態數據放在一個單獨的服務中。
四.共享數據
如果不同的微服務都使用了同一張表,比如倉庫和財務都用到了客戶信息表,這種情況下該如何分享?其實這種情況很常見:領域概念不是在代碼中建模,相反是在數據庫中隱式地進行建模。這里缺失的領域概念是客戶,因而我們需要提供一個新的服務:客戶服務。
五.共享表
與共享數據不同的是:不同的微服務也會使用同一張表,但兩者修改的部分不一樣,這樣的情況下,我們可以把這張表拆分出兩張表,分別供兩個微服務使用。
六.實施拆分
通常,我們推薦先分離數據庫結構然后對代碼進行拆分。表結構分離之后,對於原先的某個動作而言,對數據庫的訪問次數可能會變多。這也是我們需要考慮的問題,這里涉及到分布式事務的相關問題。
另外,先拆分數據庫但不分離代碼的好處在於,可以隨時選擇回退這些修改或是繼續,而不影響服務的任何消費者。
分布式事務
一個事務可以幫助系統從一個一致性的狀態遷移到另一個一致的狀態,要么全部都做,要么什么都不做。
在單塊結構中,所有的創建或者更新都可以在一個事務邊界內完成,分離數據庫之后,這種好處就沒有了。在分布式事務中,我們有可能面臨一個操作成功,而另一個操作失敗的局而,我們該如何處理這些問題?
方法1:補償機制——最終一致性
對於失敗的動作,我們進行重復觸發,只要在系統可接受的時間范圍內,最終一致性是可以接受的。
方法2:回滾機制
對於失敗的動作,我們可以選擇回滾。但是回滾也失敗的呢?這個時間,要么我們在某個時間重試回滾操作,或者提供一些自動化的操作或界面操作來清除這些不一致的狀態。
方法3:分布式事務
我們可以使用事務管理器來統一編排橫跨多個服務的事務,分布式的事務會保證整個系統處於一致的狀態,唯一不同的是,這里的事務會運行在不同系統的不同進程中,通常它們之間使用網絡進行通信。
分布式事務的常見算法是兩段提交,在這種方式中,首先是投票階段,在這個階段,每個參與者都會告訴事務管理器是否應該繼續,如果事務管理器收到所有的投票都是成功,則事務管理器會告知各個參與者執行提交操作,只要收到一個否定的投標,事務管理器就會讓所有的參與者回退。
但兩段提交也有缺點,首先所有的參與者都等待中央協調進程的指令,從而很容易導致系統的中斷,如果事務管理器宕機了,處於等待狀態的事務就永遠無法完成;如果有一個參與者在投票階段發送消息失敗,則所有的其他參與者都會被阻,投票之后的提交也可能會失敗;另外中央協調進程也可能使用鎖,這樣會對系統的擴展帶來影響。因而這種算法並不是萬無一失的。
如果確實存在保持一致懷的場合,應該盡量避免把它們放在不同的地方。
又一個難點:報表數據庫
報表通常需要來自組織內各個部分的數據,在以往的單塊結構來說,這是很方便的。但也存在一些缺點:首先是修改表結構的風險增大;再者則是報表系統的優先手段有限,比如關系型數據庫對於海量的數據不能呈現很好的支持,而MongoDB則地文檔存儲有其獨特的優越性。
一.通過服務調用來獲取數據
報表數據通常需要大量的數據,通過服務提供接口來一條條調用很顯示是不太合適的,這樣非常低效而且對服務來說負載過重。
我們可以一次性返回分頁的多條記錄,或者在本地將數據導出到文件文件的地址返回給調用方以供使用。
二.數據導出
我們也可以把報表數據周期性的導出,推送到報表數據庫,但這樣不同微服務的數據又集成到一起的,這時我們可以使用一些技術來屏蔽這些耦合,比如視圖
三.事件數據導出
在每次數據發生變化時,數據提供方也會提供一些事件,數據訂閱方可以將這些數據導出到報表數據庫中,這樣源數據與數據之間的耦合就消除掉了,我們只需要綁定到服務所發送的事件即可。
不同前面的周期性導出數據,這里的事件數據是實時,所以能讓數據更快地流入報表系統。
另外,我們只需要對新事件產生的新數據進行處理,即處理增量數據,這樣的操作會更加高效。
而缺點在於事件數據必須以事件的形式廣播出去,同時在數據量時,不容易進行擴展,而前面的數據導出的方式,可以在數據庫級別進行擴展。
四.數據導出的備份
這里是整庫備份,因而也會造成不同微服務之間數據的耦合。
參考
《微服務設計》(Sam Newman 著 / 崔力強 張駿 譯)