曾經(jīng)設計的一個供應鏈系統(tǒng)中,存在商品 、銷售訂單 、采購 這三個服務,它們的主數(shù)據(jù)的部分結(jié)構(gòu)如下所示:
商品 :
ID | 名稱 | 分類 | 型號 | 生產(chǎn)年份 | 編碼 |
---|---|---|---|---|---|
訂單和子訂單 :
訂單ID | 下單時間 | 客戶 | 總金額 | 子訂單ID | 商品ID | 單價 | 數(shù)量 |
---|---|---|---|---|---|---|---|
采購單和子訂單 :
采購單ID | 下單時間 | 供應商 | 總金額 | 采購子訂單ID | 商品ID | 單價 | 數(shù)量 |
---|---|---|---|---|---|---|---|
在設計這個供應鏈系統(tǒng)時,我們需要滿足以下兩個需求:
根據(jù)商品的型號/分類/生成年份/編碼 等查找訂單;
根據(jù)商品的型號/分類/生成年份/編碼 等查找采購訂單。
初期我們的方案是這樣設計的:嚴格按照的微服務劃分原則將商品相關的職責存放在商品系統(tǒng)中。因此,在查詢訂單與采購單時,如果查詢字段包含商品字段,我們需要按照如下順序進行查詢:
先根據(jù)商品字段調(diào)用商品的服務,然后返回匹配的商品信息;
在訂單或采購單中,通過 IN 語句匹配商品 ID,再關聯(lián)查詢對應的單據(jù)。
為了方便理解這個過程,訂單查詢流程圖如下圖所示:
初期方案設計完后,很快我們就遇到了一系列問題:
隨著商品數(shù)量的增多,匹配的商品越來越多,于是訂單服務中包含 IN 語句的查詢效率越來越慢
商品作為一個核心服務,依賴它的服務越來越多,同時隨著商品數(shù)據(jù)量的增長,商品服務已不堪重負,響應速度也變慢,還存在請求超時 的情況
由于商品服務超時,相關服務處理請求經(jīng)常失敗。
結(jié)果就是業(yè)務方每次查詢訂單或采購單時,只要帶上了商品這個關鍵字,查詢效率就會很慢而且老是失敗。于是,我們重新想了一個新方案——數(shù)據(jù)冗余 ,下面我們一起來看下。
1、數(shù)據(jù)冗余的方案
數(shù)據(jù)冗余說白了就是在訂單、采購單中保存一些商品字段 信息。
為了方便理解,我們借助上面實際業(yè)務場景具體說明下,看看兩者的區(qū)別。
商品 :
ID | 名稱 | 分類ID | 型號 | 生產(chǎn)年份ID | 編碼 |
---|---|---|---|---|---|
訂單和子訂單 :
訂單ID | 下單時間 | 客戶 | 總金額 | ||||
---|---|---|---|---|---|---|---|
子訂單ID | 商品ID | 單價 | 數(shù)量 | 商品名稱 | 商品分類ID | 商品型號 | 生產(chǎn)批次ID |
采購單和子訂單 :
采購單ID | 下單時間 | 供應商 | 總金額 | ||||
---|---|---|---|---|---|---|---|
采購子訂單ID | 商品ID | 單價 | 數(shù)量 | 商品名稱 | 商品分類ID | 商品型號 | 生產(chǎn)批次ID |
調(diào)整架構(gòu)方案后,每次查詢時,我們就可以不再依賴商品服務了 。
但是,如果商品進行了更新,我們?nèi)绾瓮饺哂嗟臄?shù)據(jù)呢?在此分享2種 解決辦法。
每次更新商品時,先調(diào)用訂單與采購服務,再更新商品的冗余數(shù)據(jù)。
每次更新商品時,先發(fā)布一條消息,訂單與采購服務各自訂閱這條消息后,再各自更新商品冗余數(shù)據(jù)。
那么這2種方案會出現(xiàn)哪些問題呢?
如果商品服務每次更新商品都要調(diào)用訂單與采購服務,然后再更新冗余數(shù)據(jù),則會出現(xiàn)以下兩種問題。
數(shù)據(jù)一致性問題 :如果訂單與采購的冗余數(shù)據(jù)更新失敗了,整個操作都需要回滾。這時商品服務的開發(fā)人員肯定不樂意,因為冗余數(shù)據(jù)不是商品服務的核心需求,不能因為邊緣流程阻斷了自身的核心流程。
依賴問題 :從職責來說,商品服務應該只關注商品本身,但是現(xiàn)在商品還需要調(diào)用訂單與采購服務。而且,依賴商品這個核心服務的服務實在是太多了,也就導致后續(xù)商品服務每次更新商品時,都需要調(diào)用更新訂單冗余數(shù)據(jù)、更新采購冗余數(shù)據(jù)、更新門店庫存冗余數(shù)據(jù)、更新運營冗余數(shù)據(jù)等一大堆服務。那么商品到底是下游服務還是上游服務?還能不能安心當?shù)讓雍诵姆眨?/p>
因此,第一個解決辦法直接被我們否決了,即我們采取的第二個解決辦法——通過消息發(fā)布訂閱的方案 ,因為它存在如下 2 點 優(yōu)勢:
商品無須調(diào)用其他服務,它只需要關注自身邏輯即可,頂多多生成一條消息送到 MQ 。
如果訂單、采購等服務的更新冗余數(shù)據(jù)失敗了,我們使用消息重試機制 就可以了,最終能保證數(shù)據(jù)的一致性。
此時,我們的架構(gòu)方案如下圖所示:
這個方案看起來已經(jīng)挺完美了,而且市面上基本也是這么做的,不過該方案存在如下幾個問題。
1、在這個方案中,僅僅保存冗余數(shù)據(jù)還遠遠不夠,我們還需要將商品分類與生產(chǎn)批號的清單進行關聯(lián)查詢。也就是說,每個服務不只是訂閱商品變更這一種消息,還需要訂閱商品分類、商品生產(chǎn)批號變更等消息。下面請注意查看訂單表結(jié)構(gòu)的紅色加粗部分內(nèi)容。
訂單ID | 下單時間 | 客戶 | 總金額 | ||||
---|---|---|---|---|---|---|---|
子訂單ID | 商品ID | 單價 | 數(shù)量 | 商品名稱 | 商品分類ID | 商品型號 | 生產(chǎn)批次ID |
以上只是列舉了一部分的結(jié)構(gòu),事實上,商品表中還有很多字段存在冗余,比如保修類型、包換類型等。為了更新這些冗余數(shù)據(jù),采購服務與訂單服務往往需要訂閱近十種消息,因此,我們基本上需要把商品的一小半邏輯復制過來。
2、每個依賴的服務需要重復實現(xiàn)冗余數(shù)據(jù)更新同步的邏輯。前面我們講了采購、訂單及其他服務都需要依賴商品數(shù)據(jù),因此每個服務需要將冗余數(shù)據(jù)的訂閱、更新邏輯做一遍,最終重復的代碼就會很多。
3、MQ 消息類型太多了:聯(lián)調(diào)時最麻煩的是 MQ 之間的聯(lián)動,如果是接口聯(lián)調(diào)還好說,因為調(diào)用哪個服務器的接口相對可控而且比較好追溯;如果是消息聯(lián)調(diào)就比較麻煩,因為我們常常不知道某條消息被哪臺服務節(jié)點消費了,為了讓特定的服務器消費特定的消息,我們就需要臨時改動雙方的代碼。不過聯(lián)調(diào)完成后,我們經(jīng)常忘了改回原代碼。
為此,我們不希望針對冗余數(shù)據(jù)這種非核心需求出現(xiàn)如此多的問題,最終決定使用一個特別的同步冗余數(shù)據(jù)方案,接下來我們進一步說明。
2、解耦業(yè)務邏輯的數(shù)據(jù)同步方案
解耦業(yè)務邏輯的數(shù)據(jù)同步方案的設計思路是這樣的:
將商品及商品相關的一些表(比如分類表、生產(chǎn)批號表、保修類型、包換類型等)實時同步到需要依賴使用它們的服務的數(shù)據(jù)庫,并且保持表結(jié)構(gòu)不變;
在查詢采購、訂單等服務時,直接關聯(lián)同步過來的商品相關表;
不允許采購、訂單等服務修改商品相關表。
此時,整個方案的架構(gòu)如下圖所示:
以上方案就能輕松解決如下兩個問題:
商品無須依賴其他服務,如果其他服務的冗余數(shù)據(jù)同步失敗,它也不需要回滾自身的流程;
采購、訂單等服務無須關注冗余數(shù)據(jù)的同步。
不過,該方案的“缺點 ”是增加了訂單、采購等數(shù)據(jù)庫的存儲空間(因為增加了商品相關表)。
仔細計算后,我們發(fā)現(xiàn)之前數(shù)據(jù)冗余的方案中每個訂單都需要保存一份商品的冗余數(shù)據(jù),假設訂單總數(shù)是 N,商品總數(shù)是 M,而 N 一般遠遠大于 M。因此,在之前數(shù)據(jù)冗余的方案中,N 條訂單就會產(chǎn)生 N 條商品的冗余數(shù)據(jù)。相比之下,解耦業(yè)務邏輯的數(shù)據(jù)同步方案更省空間,因為只增加了 M 條商品的數(shù)據(jù)。
此時問題又來了,如何實時同步相關表的數(shù)據(jù)呢?
我們直接找一個現(xiàn)成的開源中間件就可以了,不過它需要滿足支持實時同步、支持增量同步、不用寫業(yè)務邏輯、支持 MySQL 之間同步、活躍度高這五點要求。
根據(jù)這五點要求,我們在市面上找了一圈,發(fā)現(xiàn)了 Canal 、Debezium 、DataX 、Databus 、Flinkx 、Bifrost 這幾款開源中間件,它們之間的區(qū)別如下表所示:
從對比表中來看,比較貼近我們需求的開源中間件是 Bifrost ,原因如下:
它的界面管理不錯;
它的架構(gòu)比較簡單,出現(xiàn)問題后,我們可以自行調(diào)查,之后就算作者不維護了也可以自我維護,相對比較可控。
作者更新活躍;
自帶監(jiān)控報警功能。
因此,最終我們使用了 Bifrost 開源中間件,此時整個方案的架構(gòu)如下圖所示:
3、最終效果
整個架構(gòu)方案上線后,商品數(shù)據(jù)的同步還算比較穩(wěn)定,此時商品服務的開發(fā)人員只需要關注自身邏輯,無須再關注使用數(shù)據(jù)的人。如果需要關聯(lián)使用商品數(shù)據(jù)的訂單,采購服務的開發(fā)人員也無須關注商品數(shù)據(jù)的同步問題,只需要在查詢時加上關聯(lián)語句即可,實現(xiàn)了雙贏。
然而,唯一讓我們擔心的是 Bifrost 不支持集群,沒法保障高可用性。不過,到目前為止,它還沒有出現(xiàn)宕機的情況,反而是那些部署多臺節(jié)點負載均衡的后臺服務常常會出現(xiàn)宕機。
最終,我們總算解決了服務之間數(shù)據(jù)依賴的問題。
審核編輯:劉清
-
解耦控制
+關注
關注
0文章
29瀏覽量
10406 -
MYSQL數(shù)據(jù)庫
關注
0文章
96瀏覽量
10103 -
Bifrost架構(gòu)
+關注
關注
0文章
2瀏覽量
3373
原文標題:微服務之間的數(shù)據(jù)依賴問題,該如何解決?
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
如何用ACM簡化你的Spring Cloud微服務環(huán)境配置管理
使用阿里云ACM簡化你的Spring Cloud微服務環(huán)境配置管理
微服務架構(gòu)和CQRS架構(gòu)基本概念介紹
中斷系統(tǒng)涉及到哪些問題
數(shù)據(jù)鏈路層發(fā)送與接收的處理過程及涉及到的模塊
運維是如何看待微服務和容器的

自動駕駛技術(shù)涉及到的AI科技

微服務和容器之間的有何關系?
微服務循環(huán)依賴調(diào)用引發(fā)的血案
微服務架構(gòu)中的服務之間如何互相調(diào)用呢?
從分層架構(gòu)到微服務架構(gòu)介紹(五)

評論