如何解决微服务的数据一致性分发问题
介紹
系統(tǒng)架構(gòu)微服務(wù)化以后,根據(jù)微服務(wù)獨(dú)立數(shù)據(jù)源的思想,每個(gè)微服務(wù)一般具有各自獨(dú)立的數(shù)據(jù)源,但是不同微服務(wù)之間難免需要通過(guò)數(shù)據(jù)分發(fā)來(lái)共享一些數(shù)據(jù),這個(gè)就是微服務(wù)的數(shù)據(jù)分發(fā)問(wèn)題。Netflix/Airbnb等一線互聯(lián)網(wǎng)公司的實(shí)踐[參考附錄1/2/3]表明,數(shù)據(jù)一致性分發(fā)能力,是構(gòu)建松散耦合、可擴(kuò)展和高性能的微服務(wù)架構(gòu)的基礎(chǔ)。
本文解釋分布式微服務(wù)中的數(shù)據(jù)一致性分發(fā)問(wèn)題,應(yīng)用場(chǎng)景,并給出常見(jiàn)的解決方法。本文主要面向互聯(lián)網(wǎng)分布式系統(tǒng)架構(gòu)師和研發(fā)經(jīng)理。
為啥要分發(fā)數(shù)據(jù)?場(chǎng)景?
我們還是要從具體業(yè)務(wù)場(chǎng)景出發(fā),為啥要分發(fā)數(shù)據(jù)?有哪些場(chǎng)景?在實(shí)際企業(yè)中,數(shù)據(jù)分發(fā)的場(chǎng)景其實(shí)是非常多的。假設(shè)某電商企業(yè)有這樣一個(gè)訂單服務(wù)Order Service,它有一個(gè)獨(dú)立的數(shù)據(jù)庫(kù)。同時(shí),周邊還有不少系統(tǒng)需要訂單的數(shù)據(jù),上圖給出了一些例子:
一個(gè)是緩存系統(tǒng),為了提升訂單數(shù)據(jù)的訪問(wèn)性能,我們可以把頻繁訪問(wèn)的訂單數(shù)據(jù),通過(guò)Redis緩存起來(lái);
第二個(gè)是Fulfillment Service,也就是訂單履行系統(tǒng),它也需要一份訂單數(shù)據(jù),借此實(shí)現(xiàn)訂單履行的功能;
第三個(gè)是ElasticSearch搜索引擎系統(tǒng),它也需要一份訂單數(shù)據(jù),可以支持前臺(tái)用戶、或者是后臺(tái)運(yùn)營(yíng)快速查詢訂單信息;
第四個(gè)是傳統(tǒng)數(shù)據(jù)倉(cāng)庫(kù)系統(tǒng),它也需要一份訂單數(shù)據(jù),支持對(duì)訂單數(shù)據(jù)的分析和挖掘。
當(dāng)然,為了獲得一份訂單數(shù)據(jù),這些系統(tǒng)可以定期去訂單服務(wù)查詢最新的數(shù)據(jù),也就是拉模式,但是拉模式有兩大問(wèn)題:
一個(gè)是拉數(shù)據(jù)通常會(huì)有延遲,也就是說(shuō)拉到的數(shù)據(jù)并不實(shí)時(shí);
如果頻繁拉的話,考慮到外圍系統(tǒng)眾多(而且可能還會(huì)增加),勢(shì)必會(huì)對(duì)訂單數(shù)據(jù)庫(kù)的性能造成影響,嚴(yán)重時(shí)還可能會(huì)把訂單數(shù)據(jù)庫(kù)給拉掛。
所以,當(dāng)企業(yè)規(guī)模到了一定階段,還是需要考慮數(shù)據(jù)分發(fā)技術(shù),將業(yè)務(wù)數(shù)據(jù)同步分發(fā)到對(duì)數(shù)據(jù)感興趣的其它服務(wù)。除了上面提到的一些數(shù)據(jù)分發(fā)場(chǎng)景,其實(shí)還有很多其它場(chǎng)景,例如:
第一個(gè)是數(shù)據(jù)復(fù)制(replication)。為了實(shí)現(xiàn)高可用,一般要將數(shù)據(jù)復(fù)制多分存儲(chǔ),這個(gè)時(shí)候需要采用數(shù)據(jù)分發(fā)。
第二個(gè)是支持?jǐn)?shù)據(jù)庫(kù)的解耦拆分。在單體數(shù)據(jù)庫(kù)解耦拆分的過(guò)程中,為了實(shí)現(xiàn)不停機(jī)拆分,在一段時(shí)間內(nèi),需要將遺留老數(shù)據(jù)同步復(fù)制到新的數(shù)據(jù)存儲(chǔ),這個(gè)時(shí)候也需要數(shù)據(jù)分發(fā)技術(shù)。
第三個(gè)是實(shí)現(xiàn)CQRS,還有去數(shù)據(jù)庫(kù)Join。這兩個(gè)場(chǎng)景我后面有單獨(dú)文章解釋,這邊先說(shuō)明一下,實(shí)現(xiàn)CQRS和數(shù)據(jù)庫(kù)去Join的底層技術(shù),其實(shí)也是數(shù)據(jù)分發(fā)。
第四個(gè)是實(shí)現(xiàn)分布式事務(wù)。這個(gè)場(chǎng)景我后面也有單獨(dú)文章講解,這邊先說(shuō)明一下,解決分布式事務(wù)問(wèn)題的一些方案,底層也是依賴于數(shù)據(jù)分發(fā)技術(shù)的。
其它還有流式計(jì)算、大數(shù)據(jù)BI/AI,還有審計(jì)日志和歷史數(shù)據(jù)歸檔等場(chǎng)景,一般都離不開數(shù)據(jù)分發(fā)技術(shù)。
總之,波波認(rèn)為,數(shù)據(jù)分發(fā),是構(gòu)建現(xiàn)代大規(guī)模分布式系統(tǒng)、微服務(wù)架構(gòu)和異步事件驅(qū)動(dòng)架構(gòu)的底層基礎(chǔ)技術(shù)。
雙寫?
對(duì)于數(shù)據(jù)分發(fā)這個(gè)問(wèn)題,乍一看,好像并不復(fù)雜,稍有開發(fā)經(jīng)驗(yàn)的同學(xué)會(huì)說(shuō),我在應(yīng)用層做一個(gè)雙寫不就可以了嗎?比方說(shuō),請(qǐng)看上圖右邊,這里有一個(gè)微服務(wù)A,它需要把數(shù)據(jù)寫入DB,同時(shí)還要把數(shù)據(jù)寫到MQ,對(duì)于這個(gè)需求,我在A服務(wù)中弄一個(gè)雙寫,不就搞定了嗎?其實(shí)這個(gè)問(wèn)題并沒(méi)有那么簡(jiǎn)單,關(guān)鍵是你如何才能保證雙寫的事務(wù)性?
請(qǐng)看上圖左邊的代碼,這里有一個(gè)方法updateDbThenSendMsgInTransaction,這個(gè)方法上加了事務(wù)性標(biāo)注,也就是說(shuō),如果拋異常的話,數(shù)據(jù)庫(kù)操作會(huì)回滾。我們來(lái)看這個(gè)方法的執(zhí)行步驟:
第一步先更新數(shù)據(jù)庫(kù),如果更新成功,那么result設(shè)為true,如果更新失敗,那么result設(shè)為false;
第二步,如果result為true,也就是說(shuō)DB更新成功,那么我們就繼續(xù)做第三步,向mq發(fā)送消息
如果發(fā)消息也成功,那么我們的流程就走到第四步,整個(gè)雙寫事務(wù)就成功了。
如果發(fā)消息拋異常,也就是發(fā)消息失敗,那么容器會(huì)執(zhí)行該方法的事務(wù)性回滾,上面的數(shù)據(jù)庫(kù)更新操作也會(huì)回滾。
初看這個(gè)雙寫流程沒(méi)有問(wèn)題,可以保證事務(wù)性。但是深入研究會(huì)發(fā)現(xiàn)它其實(shí)是有問(wèn)題的。比方說(shuō)在第三步,如果發(fā)消息拋異常了,并不保證說(shuō)發(fā)消息失敗了,可能只是由于網(wǎng)絡(luò)異常抖動(dòng)而造成的拋異常,實(shí)際消息可能是已經(jīng)發(fā)到MQ中,但是拋異常會(huì)造成上面數(shù)據(jù)庫(kù)更新操作的回滾,結(jié)果造成兩邊數(shù)據(jù)不一致。
模式一:事務(wù)性發(fā)件箱(Transactional Outbox)
對(duì)于事務(wù)性雙寫這個(gè)問(wèn)題,業(yè)界沉淀下來(lái)比較實(shí)踐的做法,其中一種,就是采用所謂事務(wù)性發(fā)件箱模式,英文叫Transactional Outbox。據(jù)說(shuō)這個(gè)模式是eBay最早發(fā)明和使用的。事務(wù)性發(fā)件箱模式不難理解,請(qǐng)看上圖。
我們?nèi)匀灰杂唵蜲rder服務(wù)為例。在數(shù)據(jù)庫(kù)中,除了訂單Order表,為了實(shí)現(xiàn)事務(wù)性雙寫,我們還需增加了一個(gè)發(fā)件箱Outbox表。Order表和Outbox表都在同一個(gè)數(shù)據(jù)庫(kù)中,對(duì)它們進(jìn)行同時(shí)更新的話,通過(guò)數(shù)據(jù)庫(kù)的事務(wù)機(jī)制,是可以實(shí)現(xiàn)事務(wù)性更新的。
下面我們通過(guò)例子來(lái)展示這個(gè)流程,我們這里假定Order Service要添加一個(gè)新訂單。
首先第一步,Order Service先將新訂單數(shù)據(jù)寫入Order表,然后它再向Outbox表中寫入一條訂單新增記錄,這兩個(gè)DB操作可以包在一個(gè)DB事務(wù)里頭,也就是可以實(shí)現(xiàn)事務(wù)性寫入。
然后第二步,我們?cè)僖胍粋€(gè)稱為消息中繼Message Relay的角色,它負(fù)責(zé)定期Poll拉取Outbox中的新數(shù)據(jù),然后第三步再Publish發(fā)送到MQ。如果寫入MQ確認(rèn)成功,Message Relay就可以將Outbox中的對(duì)應(yīng)記錄標(biāo)記為已消費(fèi)。這里可能會(huì)出現(xiàn)一種異常情況,就是Message Relay在將消息發(fā)送到MQ時(shí),發(fā)生了網(wǎng)絡(luò)抖動(dòng),實(shí)際消息可能已經(jīng)寫入MQ,但是Message Relay并沒(méi)有得到確認(rèn),這時(shí)候它會(huì)重發(fā),直到明確成功為止。所以,這里也是一個(gè)At Least Once,也就是至少交付一次的消費(fèi)語(yǔ)義,消息可能被重復(fù)投遞。因此,MQ之后的消費(fèi)方要做消息去重或冪等處理。
總之,事務(wù)性發(fā)件箱模式可以保證,對(duì)Order表的修改,然后將對(duì)應(yīng)事件發(fā)送到MQ,這兩個(gè)動(dòng)作可以實(shí)現(xiàn)事務(wù)性,也就是實(shí)現(xiàn)數(shù)據(jù)分發(fā)的事務(wù)性。
注意,這里的Message Relay角色既可以是一個(gè)獨(dú)立部署的服務(wù),也可以和Order Service住在一起。生產(chǎn)實(shí)踐中,需要考慮Message Relay的高可用部署,還有監(jiān)控和告警,否則如果Message Relay掛了,消息就發(fā)不出來(lái),然后,依賴于消息的各種消費(fèi)方也將無(wú)法正常工作。
Transactional Outbox參考實(shí)現(xiàn) ~ Killbill Common Queue
事務(wù)性發(fā)件箱的原理簡(jiǎn)單,實(shí)現(xiàn)起來(lái)也不復(fù)雜,波波這邊推薦一個(gè)生產(chǎn)級(jí)的參考實(shí)現(xiàn)。這個(gè)實(shí)現(xiàn)源于一個(gè)叫killbill的項(xiàng)目,killbill是美國(guó)高朋(GroupOn)公司開源的訂閱計(jì)費(fèi)和支付平臺(tái),這個(gè)項(xiàng)目已經(jīng)有超過(guò)8~9年的歷史,在高朋等公司已經(jīng)有不少落地案例,是一個(gè)比較成熟的產(chǎn)品。killbill項(xiàng)目里頭有一些公共庫(kù),單獨(dú)放在一個(gè)叫killbill-commons的子項(xiàng)目里頭,其中有一個(gè)叫killbill common queue,它其實(shí)是事務(wù)性發(fā)件箱的一個(gè)生產(chǎn)級(jí)實(shí)現(xiàn)。上圖有給出這個(gè)queue的github鏈接。
Killbill common queue也是一個(gè)基于DB實(shí)現(xiàn)的分布式的隊(duì)列,它上層還包裝了EventBus事件總線機(jī)制。killbill common queue的總體設(shè)計(jì)思路不難理解,請(qǐng)看上圖:
在上圖的左邊,killbill common queue提供發(fā)送消息API,并且是支持事務(wù)的。比方說(shuō)圖上的postFromTransaction方法,它可以發(fā)送一個(gè)BusEvent事件到DB Queue當(dāng)中,這個(gè)方法還接受一個(gè)數(shù)據(jù)庫(kù)連接Connection參數(shù),killbill common queue可以保證對(duì)事件event的數(shù)據(jù)庫(kù)寫入,和使用同一個(gè)Connection的其它數(shù)據(jù)庫(kù)寫入操作,發(fā)生在同一個(gè)事務(wù)中。這個(gè)做法其實(shí)就是一種事務(wù)性發(fā)件箱的實(shí)現(xiàn),這里的發(fā)件箱存的就是事件event。
除了POST寫入API,killbill common queue還支持類似前面提到的Message Relay的功能,并且是包裝成EeventBus + Handler方式來(lái)實(shí)現(xiàn)的。開發(fā)者只需要實(shí)現(xiàn)事件處理器,并且注冊(cè)訂閱在EventBus上,就可以接收到DB Queue,也就是發(fā)件箱當(dāng)中的新事件,并進(jìn)行消費(fèi)處理。如果事件處理成功,那么EvenbBus會(huì)將對(duì)應(yīng)的事件從發(fā)件箱中移走;如果事件處理不成功,那么EventBus會(huì)負(fù)責(zé)重試,直到處理成功,或者超過(guò)最大重試次數(shù),那么它會(huì)將該事件標(biāo)記為處理失敗,并移到歷史歸檔表中,等待后續(xù)人工檢查和干預(yù)。這個(gè)EventBus的底層,其實(shí)有一個(gè)Dispatcher派遣線程,它負(fù)責(zé)定期掃描DB Queue(也就是發(fā)件箱)中的新事件,有的話就批量拉取出來(lái),并發(fā)送到內(nèi)部EventBus的隊(duì)列中,如果內(nèi)部隊(duì)列滿了,那么Dispather Thread也會(huì)暫停拉取新事件。
在killbill common queue的設(shè)計(jì)中,每個(gè)節(jié)點(diǎn)上的Dispather線程只負(fù)責(zé)通過(guò)自己這個(gè)節(jié)點(diǎn)寫入的事件,并且在一個(gè)節(jié)點(diǎn)上,Dispather線程也只有一個(gè),這樣才能保證消息消費(fèi)的順序性,并且也不會(huì)重復(fù)消費(fèi)。
Reaper機(jī)制
killbill common queue,其實(shí)是一個(gè)基于集中式數(shù)據(jù)庫(kù)實(shí)現(xiàn)的分布式隊(duì)列,為什么說(shuō)它是分布式隊(duì)列呢?請(qǐng)看上圖,killbill common queue的設(shè)計(jì)是這樣的,它的每個(gè)節(jié)點(diǎn),只負(fù)責(zé)消費(fèi)處理從自己這個(gè)節(jié)點(diǎn)寫入的事件。比方說(shuō)上圖中有藍(lán)色/黃色和綠色3個(gè)節(jié)點(diǎn),那么藍(lán)色節(jié)點(diǎn),只負(fù)責(zé)從藍(lán)色節(jié)點(diǎn)寫入,在數(shù)據(jù)庫(kù)中標(biāo)記為藍(lán)色的事件。同樣,黃色節(jié)點(diǎn),只負(fù)責(zé)從黃色節(jié)點(diǎn)寫入,在數(shù)據(jù)庫(kù)中標(biāo)記為黃色的事件。綠色節(jié)點(diǎn)也是類似。這是一種分布式的設(shè)計(jì),如果處理容量不夠,只需按需添加更多節(jié)點(diǎn),就可以實(shí)現(xiàn)負(fù)載分?jǐn)偂?/p>
這里有個(gè)問(wèn)題,如果其中某個(gè)節(jié)點(diǎn)掛了,比方說(shuō)上圖的藍(lán)色節(jié)點(diǎn)掛了,那么誰(shuí)來(lái)繼續(xù)消費(fèi)數(shù)據(jù)庫(kù)中藍(lán)色的,還沒(méi)有來(lái)得及處理的事件呢?為了解決這個(gè)問(wèn)題,killbill common queue設(shè)計(jì)了一種稱為reaper收割機(jī)的機(jī)制。每個(gè)節(jié)點(diǎn)上都還住了一個(gè)收割機(jī)線程,它們會(huì)定期檢查數(shù)據(jù)庫(kù),看有沒(méi)有長(zhǎng)時(shí)間無(wú)人處理的事件,如果有,就搶占標(biāo)記為由自己負(fù)責(zé)。比方說(shuō)上圖的右邊,最終黃色節(jié)點(diǎn)上的收割機(jī)線程搶到了原來(lái)由藍(lán)色節(jié)點(diǎn)負(fù)責(zé)的事件,那么它會(huì)把這些事件標(biāo)記為黃色,也就是由自己來(lái)負(fù)責(zé)。
收割機(jī)機(jī)制,保證了killbill common queue的高可用性,相當(dāng)于保證了事務(wù)性發(fā)件箱中的Message Relay的高可用性。
Killbill PersistentBus表結(jié)構(gòu)
基于killbill common queue的EventBus,也被稱為killbill PersistentBus。上圖給出了它的數(shù)據(jù)庫(kù)表結(jié)構(gòu),其中bus_events就是用來(lái)存放待處理事件的,相當(dāng)于發(fā)件箱,主要的字段包括:
event_json,存放json格式的原始數(shù)據(jù)。
creating_owner,記錄創(chuàng)建節(jié)點(diǎn),也就是事件是由哪個(gè)節(jié)點(diǎn)寫入的。
processingowner,記錄處理節(jié)點(diǎn),也就是事件最終是由哪個(gè)節(jié)點(diǎn)處理的;通常由creatingowner自己處理,但也可能被收割,由其它節(jié)點(diǎn)處理。
processing_state,當(dāng)前的處理狀態(tài)。
error_count,處理錯(cuò)誤計(jì)數(shù),超過(guò)一定計(jì)數(shù)會(huì)被標(biāo)記為處理失敗。
當(dāng)前處理狀態(tài)主要包括6種:
AVAILABLE,表示待處理
IN_PROCESSING,表示已經(jīng)被dispatcher線程取走,正在處理中
PROCESSED,表示已經(jīng)處理
REMOVED,表示已經(jīng)被刪除
FAILED,表示處理失敗
REPEATED,表示被其它節(jié)點(diǎn)收割了
除了bus_events待處理事件表,還有一個(gè)對(duì)應(yīng)的bus-events-history事件歷史記錄表。不管成功還是失敗,最終,事件會(huì)被寫入歷史記錄表進(jìn)行歸檔,作為事后審計(jì)或者人工干預(yù)的依據(jù)。
上圖下方給出了數(shù)據(jù)庫(kù)表的github鏈接,你可以進(jìn)一步參考學(xué)習(xí)。
Killbill PersistentBus處理狀態(tài)遷移
上圖給出了killbill PersistentBus的事件處理狀態(tài)遷移圖。
剛開始事件處于AVAILABLE待處理狀態(tài);
之后事件被dispatcher線程拉取,進(jìn)入IN_PROCESSING處理中狀態(tài);
之后,如果事件處理器成功處理了事件,那么事件就進(jìn)入PROCESSED已經(jīng)處理狀態(tài);
如果事件處理器處理事件失敗,那么事件的錯(cuò)誤計(jì)數(shù)會(huì)被增加1,如果錯(cuò)誤計(jì)數(shù)還沒(méi)有超過(guò)最大失敗重試閥值,那么事件就會(huì)重新進(jìn)入AVAILABLE狀態(tài);
如果事件的錯(cuò)誤數(shù)量超過(guò)了最大失敗重試閥值,那么事件就會(huì)進(jìn)入FAILED失敗狀態(tài);
如果負(fù)責(zé)待處理事件的節(jié)點(diǎn)掛了,那么到達(dá)一定的時(shí)間間隔,對(duì)應(yīng)的事件會(huì)被收割進(jìn)入REAPED被收割狀態(tài)。
上圖有一個(gè)通過(guò)API觸發(fā)進(jìn)入的REMOVED移除狀態(tài),這個(gè)是給通知隊(duì)列用的,用戶可以通過(guò)API移除對(duì)應(yīng)的通知消息。順便提一下,除了事件/消息隊(duì)列,Killbill queue也是支持通知隊(duì)列(或者說(shuō)延遲消息隊(duì)列)的。
模式二:變更數(shù)據(jù)捕獲(Change Data Capture, CDC)
對(duì)于事務(wù)性雙寫這個(gè)問(wèn)題,業(yè)界沉淀下來(lái)比較實(shí)踐的做法,其中第二種,就是所謂的變更數(shù)據(jù)捕獲,英文稱為Change Data Capture,簡(jiǎn)稱CDC。
變更數(shù)據(jù)捕獲的原理也不復(fù)雜,它利用了數(shù)據(jù)庫(kù)的事務(wù)日志記錄。一般數(shù)據(jù)庫(kù),對(duì)于變更提交操作,都記錄所謂事務(wù)日志Transaction Log,也稱為提交日志Commit Log,比方說(shuō)MySQL支持binlog,Postgres支持Write Ahead log。事務(wù)日志可以簡(jiǎn)單理解為數(shù)據(jù)庫(kù)本地的一個(gè)文件隊(duì)列,它記錄了按時(shí)間順序發(fā)生的對(duì)數(shù)據(jù)庫(kù)表的變更提交記錄。
下面我們通過(guò)例子來(lái)展示這個(gè)變更數(shù)據(jù)捕獲的流程,我們這里假定Order Service要添加一個(gè)新訂單。
第一步,Order Service將新訂單記錄寫入Order表,并且提交。因?yàn)檫@是一次表變更操作,所以這次變更會(huì)被記錄到數(shù)據(jù)庫(kù)的事務(wù)日志當(dāng)中,其中內(nèi)容包括發(fā)生的變更數(shù)據(jù)。
第二步,我們還需要引入一個(gè)稱為Transaction Log Miner這樣的角色,這個(gè)Miner負(fù)責(zé)訂閱在事務(wù)日志隊(duì)列上,如果有新的變更記錄,Miner就會(huì)捕獲到變更記錄。
然后第三步,Miner會(huì)將變更記錄發(fā)送到MQ消息隊(duì)列。同之前的Message Relay一樣,這里的發(fā)送到MQ也是At Least Once語(yǔ)義,消息可能會(huì)被重復(fù)發(fā)送,所以MQ之后的消費(fèi)者需要做去重或者冪等處理。
總之,CDC技術(shù)同樣可以保證,對(duì)Order表的修改,然后將對(duì)應(yīng)事件發(fā)送到MQ,這兩個(gè)動(dòng)作可以實(shí)現(xiàn)事務(wù)性,也就是實(shí)現(xiàn)數(shù)據(jù)分發(fā)的事務(wù)性。
注意,這里的CDC一般是一個(gè)獨(dú)立部署的服務(wù),生產(chǎn)中需要做好高可用部署,并且做好監(jiān)控告警。否則如果CDC掛了,消息也就發(fā)不出來(lái),然后,依賴于消息的各種消費(fèi)方也將無(wú)法正常工作。
CDC開源項(xiàng)目(企業(yè)級(jí))
當(dāng)前,有幾個(gè)比較成熟的企業(yè)級(jí)的CDC開源項(xiàng)目,我這邊收集了一些,供大家學(xué)習(xí)參考:
第一個(gè)是阿里開源的Canal,目前在github上有超過(guò)1.4萬(wàn)顆星,這個(gè)項(xiàng)目在國(guó)內(nèi)用得比較多,之前在拍拍貸的實(shí)時(shí)數(shù)據(jù)場(chǎng)景,Canal也有不少成功的應(yīng)用。Canal主要支持MySQL binlog的增量訂閱和消費(fèi)。它是基于MySQL的Master/Slave機(jī)制,它的Miner角色是通過(guò)偽裝成Slave來(lái)實(shí)現(xiàn)的。這個(gè)項(xiàng)目的使用文檔相對(duì)比較完善,建議大家一步參考學(xué)習(xí)。
第二個(gè)是Redhat開源的Debezium,目前在github上有超過(guò)3.2k星,這個(gè)項(xiàng)目在國(guó)外用得較多。Debezium主要是在Kafka Connect的基礎(chǔ)上開發(fā)的,它不僅支持mysql數(shù)據(jù)庫(kù),還支持postgres/sqlserver/mongodb等數(shù)據(jù)庫(kù)。
第三個(gè)是Zendesk開源的Maxwell,目前在github上有超過(guò)2.1k星。Maxwell是一個(gè)輕量級(jí)的CDC Deamon,主要支持MySQL binlog的變更數(shù)據(jù)捕獲和處理。
第四個(gè)是Airbnb開源的SpinalTap,目前在github上有兩百多顆星。SpinalTap主要支持MySQL binlog的變更捕獲和處理。這個(gè)項(xiàng)目的星雖然不多,但是它是在Airbnb SOA服務(wù)化過(guò)程中,通過(guò)實(shí)踐落地出來(lái)的一個(gè)項(xiàng)目,值得參考。
對(duì)于上面的這些項(xiàng)目,如果你想生產(chǎn)使用的話,波波推薦的是阿里的Canal,因?yàn)檫@個(gè)項(xiàng)目畢竟是國(guó)內(nèi)大廠阿里落地出來(lái),而且在國(guó)內(nèi)已經(jīng)有不少企業(yè)落地案例。其它幾個(gè)項(xiàng)目,你也可以參考研究。
學(xué)習(xí)參考 ~ Eventuate-Tram
既然談到這個(gè)CDC,這里有必要提到一個(gè)人和一本書,這個(gè)人叫Chris Chardson,他是美國(guó)的老一輩的技術(shù)大牛,曾今是第一代的Cloud Foundry項(xiàng)目的創(chuàng)始人(后來(lái)Cloud Foundry被Pivotal所收購(gòu))。近幾年,Chris Chardson開始轉(zhuǎn)戰(zhàn)微服務(wù)領(lǐng)域,這兩年,他還專門寫了一本書,叫《微服務(wù)設(shè)計(jì)模式》,英文名是《Microservices Patterns》。這本書主要是講微服務(wù)架構(gòu)和設(shè)計(jì)模式的,內(nèi)容還不錯(cuò),是我推薦大家閱讀的。
Charis Chardson還專門開發(fā)了一個(gè)叫Eventuate-Tram的開源項(xiàng)目(這個(gè)項(xiàng)目也有商業(yè)版),另外他的微服務(wù)書里頭也詳細(xì)介紹了這個(gè)項(xiàng)目。這個(gè)項(xiàng)目可以說(shuō)是一個(gè)大集成框架,它不僅實(shí)現(xiàn)了DDD領(lǐng)域驅(qū)動(dòng)開發(fā)模式,CQRS命令查詢職責(zé)分離模式,事件溯源模式,還實(shí)現(xiàn)了Saga事務(wù)狀態(tài)機(jī)模式。當(dāng)然,這個(gè)項(xiàng)目的底層也實(shí)現(xiàn)了CDC變更數(shù)據(jù)捕獲模式。
波波認(rèn)為,Charis的項(xiàng)目,作為學(xué)習(xí)研究還是有價(jià)值的,但是暫不建議生產(chǎn)級(jí)使用,因?yàn)樗臇|西不是一線企業(yè)落地出來(lái)的,主要是他個(gè)人開發(fā)的。至于說(shuō)Charis的項(xiàng)目能否在一線企業(yè)落地,還有待時(shí)間的進(jìn)一步檢驗(yàn)。
Transactional Outbox vs CDC
好的,前面我介紹了解決數(shù)據(jù)的事務(wù)性分發(fā)的兩種落地模式,一種是事務(wù)性發(fā)件箱模式,另外一種是變更數(shù)據(jù)捕獲模式,這兩種模式其實(shí)各有優(yōu)劣,為了幫助大家做選型決策,我這邊對(duì)這兩種模式進(jìn)行一個(gè)比較,請(qǐng)看上面的比較表格:
首先比較一下復(fù)雜性,事務(wù)性發(fā)件箱相對(duì)比較簡(jiǎn)單,簡(jiǎn)單做法只需要在數(shù)據(jù)庫(kù)中增加一個(gè)發(fā)件箱表,然后再啟一個(gè)Poller線程拉消息和發(fā)消息就可以了。CDC技術(shù)相對(duì)比較復(fù)雜,需要你深入理解數(shù)據(jù)庫(kù)的事務(wù)日志格式和協(xié)議。另外Miner的實(shí)現(xiàn)也不簡(jiǎn)單,要保證不丟消息,如果生產(chǎn)部署的話,還要考慮Miner的高可以部署,還有監(jiān)控告警等環(huán)節(jié)。
第二個(gè)比較的是Polling延遲和開銷。事務(wù)性發(fā)件箱的Polling是近實(shí)時(shí)的,同時(shí)如果頻繁拉數(shù)據(jù)庫(kù)表,難免會(huì)有性能開銷。CDC是比較實(shí)時(shí)的,同時(shí)它不侵入數(shù)據(jù)庫(kù)和表,所以它的性能開銷相對(duì)小。
第三個(gè)比較的是應(yīng)用侵入性。事務(wù)性發(fā)件箱是有一定的應(yīng)用侵入性的,應(yīng)用在更新業(yè)務(wù)數(shù)據(jù)的同時(shí),還要單獨(dú)發(fā)送消息。CDC對(duì)應(yīng)用是無(wú)侵入的,因?yàn)樗〉氖菙?shù)據(jù)庫(kù)事務(wù)日志,這個(gè)和應(yīng)用是不直接耦合的。當(dāng)然,CDC和事務(wù)性發(fā)件箱模式并不排斥,你可以在應(yīng)用層采用事務(wù)性發(fā)件箱模式,同時(shí)仍然采用CDC到數(shù)據(jù)庫(kù)去捕獲和發(fā)件箱中的消息對(duì)應(yīng)的事務(wù)日志。這個(gè)方法對(duì)應(yīng)用有一定的侵入性,但是通過(guò)CDC可以獲得較好的數(shù)據(jù)同步性能。
第四點(diǎn)是適用場(chǎng)合。事務(wù)性發(fā)件箱主要適用于中小規(guī)模的企業(yè),因?yàn)樽龇ū容^簡(jiǎn)單,一個(gè)開發(fā)人員也可以搞定。CDC則主要適用于中大規(guī)模互聯(lián)網(wǎng)企業(yè),最好有獨(dú)立框架團(tuán)隊(duì)負(fù)責(zé)CDC的治理和維護(hù)。像Netflix/Airbnb這樣的一線互聯(lián)網(wǎng)公司,也是在中后期才引入CDC技術(shù)的[參考附錄1/2/3]。
Single Source of Truth
前面我解答了如何解決微服務(wù)的數(shù)據(jù)一致性分發(fā)問(wèn)題,也給出了可落地的方案。最后,我特別說(shuō)明在實(shí)踐中進(jìn)行數(shù)據(jù)分發(fā)的一個(gè)原則,叫Single Source of Truth,翻成中文就是單一真實(shí)數(shù)據(jù)源。它的意思是說(shuō),你要實(shí)現(xiàn)數(shù)據(jù)分發(fā),目標(biāo)服務(wù)可以有很多,但是一定要注意,數(shù)據(jù)的主人只能有一個(gè),它是數(shù)據(jù)的權(quán)威記錄系統(tǒng)(canonical system of record),其它的數(shù)據(jù)都是只讀的,非權(quán)威的拷貝(read-only, non-authoritative copy)。
換句話說(shuō),任何時(shí)候,對(duì)于某類數(shù)據(jù),它主人應(yīng)該是唯一的,它是Single Source of Truth,只有它可以修改數(shù)據(jù),其它的服務(wù)可以獲得數(shù)據(jù)拷貝,做本地緩存也沒(méi)問(wèn)題,但是這些數(shù)據(jù)都是只讀的,不能修改。
只有遵循這條原則,數(shù)據(jù)分發(fā)才能正常工作,不會(huì)產(chǎn)生不一致的情況。
結(jié)論
Netflix和Airbnb等一線互聯(lián)網(wǎng)公司的實(shí)踐證明,企業(yè)要真正實(shí)現(xiàn)松散耦合、可擴(kuò)展和高性能的微服務(wù)架構(gòu),那么底層的數(shù)據(jù)分發(fā)同步能力是非常關(guān)鍵的。
數(shù)據(jù)分發(fā)技術(shù),簡(jiǎn)單的可以采用事務(wù)性發(fā)件箱模式來(lái)實(shí)現(xiàn),重量級(jí)的可以考慮變更數(shù)據(jù)捕獲CDC技術(shù)來(lái)實(shí)現(xiàn)。事務(wù)性發(fā)件箱可以參考Killbill Queue的實(shí)現(xiàn),CDC可以參考阿里的Canal等開源產(chǎn)品來(lái)實(shí)現(xiàn)。
最簡(jiǎn)單的雙寫也是實(shí)現(xiàn)數(shù)據(jù)分發(fā)的一種方式,但是為了保證一致性,需要引入后臺(tái)校驗(yàn)補(bǔ)償程序。
最后,數(shù)據(jù)分發(fā)/同步的原則是:確保單一真實(shí)數(shù)據(jù)源(Single Source of Truth)。系統(tǒng)中數(shù)據(jù)的主人應(yīng)該只有一個(gè),只有主人可以寫入數(shù)據(jù),其它都是只讀拷貝。
總結(jié)
以上是生活随笔為你收集整理的如何解决微服务的数据一致性分发问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 机器学习模型在携程海外酒店推荐场景中的应
- 下一篇: 多业务融合推荐策略实践与思考