分解單塊系統

 

1.關鍵是接縫算法

接縫的概念:從接縫處能夠抽取出相對獨立的一部分代碼,對這部分代碼進行修改不會影響系統的其餘部分。數據庫

那麼什麼樣的接縫纔是好接縫呢?限界上下文就是一個很是好的接縫,由於它的定義就是組織內高內聚和低耦合的邊界。安全

2.分解MusicCorp服務器

想象,如今有個巨大的後臺單塊服務,其中包含了MusicCorp在線系統所須要的全部行爲。網絡

假設識別出這個單塊後臺系統包含如下四個上下文。架構

  • 產品目標  -  與正在銷售的商品相關的元數據
  • 財務  -  帳號,支付,退款等項目的報告
  • 倉庫  -  分發客戶訂單、處理退貨、管理庫存等
  • 推薦  

3. 分解單塊系統的緣由框架

決定把單塊系統變小是一個很好的開始。分佈式

增量的方式可讓你在進行的過程當中學習微服務,同時也能夠限制出錯所形成的影響。微服務

接下來考慮一些指導因素。工具

3.1 改變的速度

接下來,咱們可能會對庫存管理方面的代碼作大量修改。

全部若是如今把倉庫接縫抽出來做爲一個服務,使其成爲一個自治單元,那麼後期開發的速度將大大加快。

3.2 團隊結構

MusicCorp的交付團隊事實上分佈在兩個不一樣的地區,能夠把大部分代碼分離出來,這樣就能對此全權負責。

3.3 安全

能夠對獨立的服務作監控,傳輸數據的保護和靜態的數據的保護等。

3.4 技術

4. 雜亂的依賴

當你已經識別出一些備選接縫,另外一個要考慮的點是:這部分代碼與系統剩餘部分之間的依賴有多亂。

咱們想要拉取出來的接縫應該儘可能少的被其餘組件所依賴。

一般時候,你會發現數據庫是全部雜亂依賴的源頭。

5.數據庫

前面討論了使用數據庫做爲服務之間集成方式的作法。

可是這種方式須要去找到數據庫中的接縫,這樣就能夠把它們分離乾淨。可是這會比較棘手。

6.找到問題的關鍵

第一步是看看代碼中對數據庫進行讀寫的部分。

一般這部分代碼會存在於一個倉儲層中。

 

 把數據庫映射相關的代碼和功能代碼放在同一個上下文中,能夠幫助咱們理解哪些代碼用到了數據庫中的哪些部分。

可是,有時候一張表可能會被分離到不一樣的限界上下文中,對於這種場景比較難回答。

7. 例子:打破外鍵關係

例如,咱們賣了400個產品,掙了3000元錢。

爲了作到這一點,財務包中生成報告的代碼,須要從行條目表中獲取產品標題名稱。總帳表和行條目表之間可能存在外鍵關係。

 

 快速的修改方式是:讓財務部分的代碼經過產品目錄服務暴露的API來訪問數據,而不是直接訪問數據庫。

 

 那外鍵關聯了怎麼辦?咱們只能放棄它了。

因此你可能須要把這個約束從數據庫移到代碼中來實現。

這也就意味着,咱們可能須要跨服務的一致性檢查,或者週期性觸發清理數據的任務。

8. 例子:共享靜態數據

這些將共享靜態數據存在數據庫中的例子很是多。

因此在咱們的音樂商店中,若是全部的服務都要從同一張像國家這樣的表中讀取數據,該怎麼辦?

 

 有這麼幾個解決方案可供選擇。

第一個方法是爲每一個包複製一份該表的內容,也就是說,將來每一個服務也都會保存這樣一份副本。

第二個方法是,把這些共享的靜態數據放入代碼,好比放在屬性文件中,或則簡單的放在枚舉中。

第三個方法有些極端,即把這些靜態數據放入一個單獨的服務。

在大部分場景下,均可以經過把這些數據放入配置文件或者代碼中來解決問題,並且它對於大部分場景來講都是很容易實現。

9. 例子:共享數據

共享的可變數據對於分離系統來講一般是一個大麻煩。

 

 不管是財務相關的代碼仍是倉庫相關的代碼,都會向同一個表寫入數據,

有時還會從中讀取數據。這種狀況下,如何作分離?

其實這種狀況很常見:領域概念不是在代碼中建模,相反是在數據庫中隱式的進行建模。這裏缺失的領域概念是客戶。

須要把客戶概念具象化。做爲一箇中間步驟,咱們能夠建立一個新的包Customer。

而後讓財務和倉庫這些包,經過API來訪問此新建立的包。如圖

 

 10.例子:共享表

 

 這裏能夠分紅兩個表。

 

 11.重構數據庫

實施分離

 

 表結構分離以後,對於原先的某個動做而言,對數據庫的訪問次數可能會變多。

由於之前簡單的用一個select語句就能獲得全部的數據,如今則須要分別從不一樣的地方拿到數據,

而後在內存中進行鏈接。還有,分紅兩個表結構會破壞事務完整性。

先分離數據庫結構但不分離服務的好處在於,可能隨時選擇回退這些修改或是繼續作,

而不影響服務的任何消費者。咱們對數據庫分離感到滿意以後,就能夠考慮對整個應用程序的分離了。

12.事務邊界

簡單的說,一個事務能夠幫助咱們的系統從一個一致的狀態遷移到另外一個一致的狀態:要麼所有作完,要麼什麼都不變。

使用單塊表結構時,全部的建立或者更新操做均可以在一個事務邊界內完成。

 

 分離數據庫以後,這種好處就沒有了。

下訂單操做如今跨越了兩個事務邊界,以下圖。

若是這個插入訂單表的操做失敗,咱們能夠顯式的清除全部的狀態,

從而保證系統狀態的一致性。可若是插入訂單表成功,但插入提取表失敗了呢?

 

 12.1 再試一次

咱們能夠把這部分操做放在一個隊列或者日誌文件中,以後再嘗試對其進行觸發。

對於某些操做來講這是合理的,但要保證重試可以修復這個問題。

不少地方會把這種形式叫作最終一致性。

相對於使用事務來保證系統處於一致的狀態,最終一致性能夠接受系統在將來的某個時間達到一致。

這種操做對於長時間的操做來講尤其有效。

12.2 終止整個操做

另外一個選擇是拒絕整個操做。

在這種狀況下,咱們須要把系統重置到某種一致的狀態。

提取表的處理比較簡單,由於插入失敗會致使事務的回退。

可是訂單表已經提交了事務該怎麼處理呢?

解決方案是,在發起一個補償事務來抵消以前的操做。對於咱們來講,可能就是簡單的一個delete操做來把訂單從數據庫中刪除。

而後還須要向用戶報告該操做失敗了。

 

那若是補償事務失敗了該怎麼辦呢?

在這種狀況下,你要麼重試補償事務,要麼使用一些後臺任務來清除不一致的狀態。

能夠給後臺維護人員提供一個界面來進行該操做,或者將其自動化。

不一樣狀況下的補償事務會很是難以理解,更不用說實現了。

12.3 分佈式事務

手動編配補償事務很是難以操做,一種替代方案是使用分佈式事務。

分佈式事務會跨越多個事務,而後使用一個叫作事務管理器的工具來同一編配其餘底層系統中運行的事務

這就像普通的事務同樣,一個分佈式事務會保證整個系統處於一致的狀態。

惟一不一樣的是,這裏的事務會運行在不一樣系統的不一樣進程中,一般它們之間使用網絡進行通訊。

 

處理分佈式事務(尤爲是上面處理客戶訂單這類的短事務)經常使用的算法是兩階段提交

在這種方式中,首先是投票階段

在這個階段,每一個參與者(在這個上下文中叫作cohort)會告訴事務管理器它是否應該繼續

若是事務管理器收到的全部投票都是成功的,則會告訴它們進行提交操做。

只要收到一個否認的投票,事務管理器就會讓全部的參與者回退。

 

這種方式會使得全部的參與者暫停並等待中央協調進程的指令,從而很容易致使系統的中斷

若是事務管理器宕機了,處於等待狀態的事務就永遠沒法完成。若是一個cohort在投票階段發送消息失敗,

則全部其餘參與者都會被阻塞,投票結束後的提交也有可能會失敗。

該算法隱式的任務上述這些狀況不會發生,即若是一個cohort在投票階段投了同意票,則它必定能提交成功。

cohort須要一種機制來保證這件事情的發生。這意味着此算法並非萬無一失的,而只是嘗試捕獲大部分的失敗場景。

 

分佈式事務在某些特定的技術棧上已有現成的實現,好比Java的事務API,該API容許你把相似數據庫和消息隊列這樣徹底不一樣的資源,

放在一個事務中進行操做。

 

12.4 應該怎麼辦呢

如你所見,分佈式事務很容易出錯,並且不利於擴展。

這種經過重試和補償達成最終一致性的方式,會使得定位問題更加困難,並且有可能須要其餘補償措施來修復潛在的數據的不一致。

 

若是你遇到的場景確實是須要保持一致性,那麼儘可能避免把它們放在不一樣的地方,必定要儘可能這樣作。

若是實在不行,那麼要避免僅僅從純技術的(好比數據庫事務)的角度考慮,而是顯示的建立一個概念來表示這個事務。

你能夠把這個概念當作一個句柄或者鉤子,在此之上,可以相對容易的進行相似補償事務這樣的操做,這也是在系統中

監控這些複雜概念的一種方式。

 

13.報表

把架構網微服務的方向進行調整會顛覆不少東西,但這並不意味着咱們須要拋棄現有的一切。

這裏並非說報表不能顛覆,可是首先應該搞清楚現有流程是如何工做的。

14.報表數據庫

報表一般須要來自組織內各個部分的數據生成有用的輸出。

在標準的單塊服務架構中,全部的數據都存儲在一個大數據庫中。

一般爲了防止對主系統性能產生影響,報表系統會從副本數據庫中讀取數據,如圖

 

 這種方式有一個很大的好處,即全部的數據存儲在同一個地方,所以可使用很是簡單的工具來作查詢。

但也存在一些缺點。

首先數據庫結構成了單塊服務和報表系統之間的共享API,因此對錶結構的修改須要很是當心。

其次,不管是在線上系統仍是報表系統的數據庫中,可用的優化手段都比較有限。

最後,來看看有哪些數據庫可供選擇。

標準的關係型數據庫使用SQL做爲查詢接口,它可以和不少現成的報表工具協同工做,

但不必定是適用產品數據庫的最佳選擇。

15.經過服務調用來獲取數據

這個模型有不少變體,但它們都依賴API調用來獲取想要的數據。

對於一個很是簡單的報表系統(好比展現過去15分鐘內下的訂單數量的系統)來講,這是可行的。

爲了從兩個或者多個系統中獲取數據,你須要進行屢次調用,而後進行組裝。

 

可是當你須要訪問大量數據時,這種方法就徹底不適用了。

你能夠經過批量API來簡化這個過程。發起調用的系統能夠POST一個BatchRequest,

其中攜帶一個位置信息,服務器能夠將全部數據寫入該文件。

經過這種方式能夠將大數據文件導出,而不須要HTTP之上的開銷,只是簡單的把一個CSV文件存儲到共享的位置而已。

16.數據導出

和報表系統拉取數據的方式相比,咱們還能夠把數據推送到報表系統中。

使用標準的HTTP來進行大量調用時,會帶來很大的額外開銷,更不用提爲報表系統建立專用API所帶來的開銷。

一種替代方式是,使用一個獨立的程序直接訪問其餘服務使用的那些數據庫,把這些數據導出到單獨的報表數據庫

 

 一開始,相應服務的維護團隊能夠負責數據導出工做。簡單的使用Cron去觸發一些命令行程序就能夠完成這個任務。

 

在報表數據庫中包含了全部服務的表結構,而後使用視圖之類的技術來建立一個聚合。

使用這種方式,導出數據的服務只須要知道本身的報表視圖結構便可。可是這種方式的

性能就取決於你所選用的數據庫系統了。

 

 另外一個方向,咱們把一系列數據以JSON格式導出到AWS S3 ,有效的把S3變成了一個巨大的數據集市。

17.事件數據導出

每一個微服務能夠在其管理的實體發生狀態改變時發送一些事件。

 

 使用這種方式的話,與源微服務底層數據庫之間的耦合就被消除掉了。咱們只須要綁定

到服務所發送的事件便可,而設計這些事件,原本就是用來暴露給外部消費者的。

這種方式主要的缺點是,全部須要的信息都必須以事件的形式廣播出去,

因此在數量比較大時,不容易像數據導出方式那樣直接在數據庫級別進行擴展。

18.數據導出的備份

19.走向實時

如今咱們愈來愈靠近可以把數據按需路由到多個不一樣地方的通用事件系統了。

20.修改的代價

咱們能夠,也必定會犯錯誤,須要接受這個事實。可是另一件咱們應該作的事情是,

理解如何下降這些錯誤形成的影響。

CRC(class-responsibility-collaboration,類-職責-交互)卡片

21.理解根本緣由

咱們作了不少關於如何把大服務拆分紅一些小服務的討論,可是這些大服務又是怎麼產生的呢?

第一件須要理解的事情是,服務必定會慢慢變大,直到大到須要拆分。咱們但願系統的架構隨着

時間的推移增量的變化。關鍵是要在拆分這件事情變得太過昂貴以前,意識到你須要作這個拆分。

儘管知道相比於巨大的怪獸,一系列的小服務更容易應對,但咱們仍然在一點點的幫助它成長。

 

對庫和輕量級服務框架的投資能減少建立新服務的代價。

22.小結

咱們經過尋找服務邊界把系統分解開來,且這能夠是一個增量的過程。

在最開始就要養成及時尋找接縫的好習慣,從而減小分隔服務的代價,這樣才能在將來遇到新需求時,

繼續演化咱們的系統。

相關文章
相關標籤/搜索