看完這篇異地多活的改造,我決定和架構師battle一下|得物技術

得物技術發表於2022-06-03

簡述

異地多活的概念以及為什麼要做異地多活這裡就不進行概述了。概念性的很多,像什麼同城雙活、兩地三中心、三地五中心等等概念。如果有對這些容災架構模式感興趣的可以閱讀下這篇文章進行了解:《淺談業務級災備的架構模式》

閱讀本篇文章之前,我們先明確一下背景,這樣大家後續在看的時候就不會產生困惑。

1.1 機房劃分

得物多活改造一期目前有兩個機房,分別是機房A和機房B。文章中大部分圖中都會有標識,這就說明是兩個不同的機房。

A機房我們定義為中心機房,也就是多活上線之前正在使用的機房。如果說到中心機房那指的就是A機房。另一個B機房,在描述的時候可能會說成單元機房,那指的就是B機房。

1.2 單元化

單元化簡單點我們直接就可以認為是一個機房,在這個單元內能夠完成業務的閉環。比如說使用者進入APP,瀏覽商品,選擇商品確認訂單,下單,支付,檢視訂單資訊,這整個流程都在一個單元中能夠完成,並且資料也是儲存在這個單元裡面。

做單元化無非就兩個原因,容災和提高系統併發能力。但是也得考慮機房建設的規模和技術,硬體等投入的成本。具體的就不多講了,大家大概理解了就行。

2. 改造點

瞭解改造點之前我們先來看下目前單機房的現狀是什麼樣子,才能更好的幫助大家去理解為什麼要做這些改造。

如上圖所示,客戶端的請求進來會先到SLB(負載均衡),然後到我們內部的閘道器,通過閘道器再分發到具體的業務服務。業務服務會依賴Redis, Mysql, MQ, Nacos等中介軟體。

既然做異地多活,那麼必然是在不同地區有不同的機房,比如中心機房,單元機房。所以我們要實現的效果如下圖所示:

大家看上面這張圖可能會感覺很簡單,其實也就是一些常用的中介軟體,再多一個機房部署罷了,這有什麼難度。如果你這樣想我只能說一句:格局小了啊

2.1 流量排程

使用者的請求,從客戶端發出,這個使用者的請求該到哪個機房,這是我們要改造的第一個點。

沒做多活之前,域名會解析到一個機房內,做了多活後,域名會隨機解析到不同的機房中。如果按照這種隨機的方式是肯定有問題的,對於服務的呼叫是無所謂的,因為沒有狀態。但是服務內部依賴的儲存是有狀態的呀。

我們是電商業務,使用者在中心機房下了一個單,然後跳轉到訂單詳情,這個時候請求到了單元機房,底層資料同步有延遲,一訪問報個錯:訂單不存在。 使用者當場就懵了,錢都付了,訂單沒了。

所以針對同一個使用者,儘可能在一個機房內完成業務閉環。為了解決流量排程的問題,我們基於OpenResty二次開發出了DLB流量閘道器,DLB會對接多活控制中心,能夠知道當前訪問的使用者是屬於哪個機房,如果使用者不屬於當前機房,DLB會直接將請求路由到該使用者所屬機房內的DLB。

如果每次都隨機到固定的機房,再通過DLB去校正,必然會存在跨機房請求,耗時加長。所以在這塊我們也是結合客戶端做了一些優化,在DLB校正請求後,我們會將使用者對應的機房IP直接通過Header響應給客戶端。這樣下次請求的時候,客戶端就可以直接通過這個IP訪問。

如果使用者當前訪問的機房掛了,客戶端需要降級成之前的域名訪問方式,通過DNS解析到存活的機房。

2.2 RPC框架

當使用者的請求達到了單元機房內,理論上後續所有的操作都是在單元機房完成。前面我們也提到了,使用者的請求儘量在一個機房內完成閉環,只是儘量,沒有說全部。

這是因為有的業務場景不適合劃分單元,比如庫存扣減。所以在我們的劃分裡面,有一個機房是中心機房,那些不做多活的業務只會部署在中心機房裡面,那麼庫存扣減的時候就需要跨機房呼叫。

請求在中心機房,怎麼知道單元機房的服務資訊?所以我們的註冊中心(Nacos)要做雙向同步,這樣才能拿到所有機房的服務資訊。

當我們的註冊資訊採用雙向複製後,對於中心服務,直接跨機房呼叫。對於單元服務會存在多個機房的服務資訊,如果不進行控制,則會出現呼叫其他機房的情況,所以RPC框架要進行改造。

2.2.1 定義路由型別

  1. 預設路由

請求到中心機房,會優先呼叫中心機房內的服務,如果中心機房無此服務,則呼叫單元機房的服務,如果單元機房沒有此服務則直接報錯。

  1. 單元路由

請求到單元機房,那麼說明此使用者的流量規則是在單元機房,接下來所有的RPC呼叫都只會呼叫單元機房內的服務,沒有服務則報錯。

  1. 中心路由

請求到單元機房,那麼直接呼叫中心機房的服務,中心機房沒有服務則報錯。請求到中心機房,那麼就本機房呼叫。

2.2.2 業務改造

業務方需要對自己的介面(Java interface)進行標記是什麼型別,通過@HARoute加在介面上面。標記完成後,在Dubbo介面進行註冊的時候,會把路由型別放入到這個介面的後設資料裡面,在Nacos後臺可以檢視。後面通過RPC呼叫介面內部所有的方法都會按照標記型別進行路由。

如果標記為單元路由,目前我們內部的規範是方法的第一個引數為小寫的long buyerId,RPC在路由的時候會根據這個值判斷使用者所在的機房。

路由邏輯如下:

2.2.3 改造過程

  1. 介面複製一份,命名為UnitApi,第一個引數加long buyerId。在新介面的實現裡面呼叫老介面,新舊介面共存。
  2. 將UnitApi釋出上線,此時沒有流量。
  3. 業務方需要升級其他域的API包,將老介面的呼叫切換為新的UnitApi,此處增加開關控制。
  4. 上線後,通過開關控制呼叫走UnitApi,有問題可關閉開關。
  5. 下線老的API,完成切換。

2.2.4 遇到的問題

2.2.4.1 其他場景切單元介面

除了RPC直接呼叫的介面,還有一大部分是通過Dubbo泛化過來的,這塊在上線後也需要將流量切到UnitApi,等老介面沒有請求量之後才能下線。

2.2.4.2 介面分類

介面進行分類,之前沒有多活的約束,一個Java interface中的方法可能各種各樣,如果現在你的interface為單元路由,那麼裡面的方法第一個引數都必須加buyerId,其他沒有buyerId場景的方法要挪出去。

2.2.4.3 業務層面調整

業務層面調整,比如之前查詢訂單隻需要一個訂單號,但是現在需要buyerId進行路由,所以接入這個介面的上游都需要調整。

2.3 資料庫

請求順利的到達了服務層,接下來要跟資料庫打交道了。資料庫我們定義了不同的型別,定義如下:

  1. 單元化

此庫為單元庫,會同時在兩個機房部署,每個機房都有完整的資料,資料採用雙向同步。

  1. 中心化

此庫為中心庫,只會在中心機房部署。

  1. 中心單元化

此庫為中心單元庫,會同時在兩個機房部署,中心可以讀寫,其他機房只能讀。中心寫資料後單向複製到另一個機房。

2.3.1 代理中介軟體

目前各個業務方用的都是客戶端形式的Sharding中介軟體,每個業務方的版本還不一致。在多活切流的過程中需要對資料庫禁寫來保證業務資料的準確性,如果沒有統一的中介軟體,這將是一件很麻煩的事情。

所以我們通過對ShardingSphere進行深度定製,二次開發資料庫代理中介軟體 彩虹橋。各業務方需要接入彩虹橋來替換之前的Sharding方式。在切換過程中,如何保證穩定平滑遷移,出問題如何快速恢復,我們也有一套成功的實踐,大家可以看下我之前寫的這篇文章《客戶端分片到Proxy分片,如絲般順滑的平穩遷移》,裡面有實現方式。

2.3.2 分散式ID

單元化的庫,資料層面會做雙向同步複製操作。如果直接用表的自增ID則會出現下面的衝突問題:

這個問題可以通過設定不同機房id有不同的自增步長來解決,但比較麻煩,後續可能會增加更多的機房。我們採用了一種一勞永逸的方式,接入全域性唯一的分散式ID來避免主鍵的衝突。

2.3.2.1 客戶端接入

目前,接入分散式ID有兩種方式,一種是應用內通過基礎架構提供的jar包接入,具體邏輯如下:

2.3.2.2 彩虹橋接入

另一種就是在彩虹橋中對具體的表配置ID的生成方式,支援對接分散式ID服務。

2.3.3 業務改造

2.3.3.1 單元化庫寫請求必須攜帶ShardingKey

在Dao層對錶進行操作的時候,會通過ThreadLocal設定當前方法的ShardingKey,然後通過Mybatis攔截器機制,將ShardingKey通過Hint的方式放入SQL中,帶給彩虹橋。彩虹橋會判斷當前的ShardingKey是否屬於當前機房,如果不是直接禁寫報錯。

這裡跟大家簡單的說明下為什麼切流過程中要禁寫,這個其實跟JVM的垃圾回收有點相似。如果不對操作禁寫,那麼就會不斷的產生資料,而我們切流,一定要保證當前機房的資料全部同步過去了之後才開始生效流量規則,否則使用者切到另一個機房,資料沒同步完,就會產生業務問題。除了彩虹橋會禁寫,RPC框架內部也會根據流量規則進行阻斷。

2.3.3.2 資料庫連線指定連線模式

連線模式的定義有兩種,分別是中心和單元。

如果應用的資料來源指定了連線模式為中心,那麼在中心機房可以正常初始化資料來源。在單元機房不會初始化資料來源。

如果應用的資料來源指定了連線模式為單元,那麼在中心機房和單元機房都可以正常初始化資料來源。

這裡解釋下為什麼要有連線模式這個 設計

在我們的專案中,會出現同時連線2個庫的情況,一個單元庫,一箇中心庫。如果沒有連線模式,上層程式碼是一份,這個專案會在中心和單元兩個機房同時部署,也就是兩個地方都會去建立資料來源。

但實際上,我的中心庫只需要在中心機房連線就可以了,因為中心庫所有的操作都是中心介面,流量必定會走中心,我在單元機房去連線是沒有意義的。另一個問題就是我不需要在單元機房維護中心庫的資料庫資訊,如果沒有連線模式,那麼單元機房的彩虹橋也必須要有中心庫的資訊,因為專案會進行連線。

2.3.4 遇到的問題

2.3.4.1 單元介面中不能訪問中心資料庫

如果介面標記成了單元介面,那麼只能操作單元庫。在以前沒有做多活改造的時候,基本上沒有什麼中心和單元的概念,所有的表也都是放在一起的。多活改造後,我們會根據業務場景對資料庫進行劃分。

劃分後,中心庫只會被中心機房的程式使用,在單元機房是不允許連線中心庫。所以單元介面裡面如果涉及到對中心庫的操作,必定會報錯。這塊需要調整成走中心的RPC介面。

2.3.4.2 中心介面不能訪問單後設資料庫

跟上面同樣的問題,如果介面是中心的,也不能在介面裡面操作單元庫。中心介面的請求都會強制走到中心機房,如果裡面有涉及到另一個機房的操作,也必須走RPC介面進行正確的路由,因為你中心機房不能操作另一個機房的資料庫。

2.3.4.3 批量查詢調整

比如批量根據訂單號進行查詢,但是這些訂單號不是同一個買家。如果隨便用一個訂單的買家作為路由引數,那麼其他一些訂單其實是屬於另一個單元的,這樣就有可能存在查詢到舊資料的問題。

這樣批量查詢的場景,只能針對同一個買家可用,如果是不同的買家需要分批呼叫。

2.4 Redis

Redis在業務中用的比較多,在多活的改造中也有很多地方需要調整。對於Redis首先我們明確幾個定義:

不做雙向同步

Redis不會和資料庫一樣做雙向同步,也就是中心機房一個Redis叢集,單元機房一個Redis叢集。每個機房的叢集中只存在一部分使用者的快取資料,不是全量的。

Redis型別

Redis分為中心和單元,中心只會在中心機房部署,單元會在中心和單元兩個機房部署。

2.4.1 業務改造

2.4.1.1 Redis多資料來源支援

多活改造之前,每個應用都有一個單獨的Redis叢集,多活改造後,由於應用沒有進行單元化和中心的拆分,所以一個應用中會存在需要連線兩個Redis的情況。一箇中心Redis,一個單元Redis。

基礎架構提供的Redis包需要支援多資料來源的建立,並且定義通用的配置格式,業務方只需要在自己 的配置裡面指定叢集和連線模式即可完成接入。此處的連線模式跟資料庫的一致。

具體的Redis例項資訊會在配置中心統一維護,不需要業務方關心,這樣在做機房擴容的時候,業務方是不需要調整的,配置如下:

spring.redis.sources.carts.mode=unit 
spring.redis.sources.carts.cluster-name=cartsCuster 

同時我們在使用Redis的時候要指定對應的資料來源,如下:

@Autowired 
@Qualifier(RedisTemplateNameConstants.REDIS_TEMPLATE_UNIT) 
private RedisTemplate<String, Object> redisTemplate; 
2.4.1.2 資料一致性

資料庫快取場景,由於Redis不會雙向同步,就會存在資料的不一致性問題。比如使用者一開始在中心機房,然後快取了一份資料。進行切流,切到單元機房,單元機房又快取了一份資料。再進行切回中心機房的操作,此時中心機房裡的快取是舊的資料,不是最新的資料。

所以在底層資料變更的時候,我們需要對快取進行失效操作,這樣才能保證資料的最終一致性。單純依靠快取的失效時間來達到一致性不是一個合適的方案。

這裡我們的方案是採用訂閱資料庫的binlog來進行快取的失效操作,可以訂閱本機房的binlog,也可以訂閱其他機房的binlog來實現所有機房的快取失效。

2.4.2 遇到的問題

2.4.2.1 序列化協議相容

在接入新的Redis Client包後,測試環境出現了老資料的相容問題。大部分應用都沒問題,有個別應用雖然用了統一的底層包,但是自己定製了序列化方式,導致Redis按新的方式裝配後沒有用到自定義的協議,這塊也是進行了改造,支援多資料來源的協議自定義。

2.4.2.2 分散式鎖的使用

目前專案中的分散式鎖是基於Redis實現,當Redis有多個資料來源之後,分散式鎖也需要進行適配。在使用的地方要區分場景,預設都是用的中心Redis來加鎖。

但是單元介面裡面的操作都是買家場景,所以這部分需要調整為單元Redis鎖物件進行加鎖,這樣能夠提高效能。其他的一些場景有涉及到全域性資源的鎖定,那就用中心Redis鎖物件進行加鎖。

2.5 RocketMQ

請求到達服務層後,跟資料庫和快取都進行了互動,接下來的邏輯是要發一條訊息出去,其他業務需要監聽這個訊息做一些業務處理。

如果是在單元機房發出的訊息,發到了單元機房的MQ中,單元機房的程式進行消費,是沒有問題的。但如果中心機房的程式要消費這個訊息怎麼辦?所以MQ跟資料庫一樣,也要做同步,將訊息同步到另一個機房的MQ中,至於另一個機房的消費者要不要消費,這就要讓業務場景去決定。

2.5.1 定義消費型別

2.5.1.1 中心訂閱

中心訂閱指的是訊息無論是在中心機房發出的還是單元機房發出的,都只會在中心機房進行消費。如果是單元機房發出的,會將單元的訊息複製一份到中心進行消費。

2.5.1.2 普通訂閱

普通訂閱就是預設的行為,指的是就近消費。在中心機房傳送的訊息就由中心機房的消費者進行消費,在單元機房傳送的訊息就由單元機房的消費進行消費。

2.5.1.3 單元訂閱

單元訂閱指的是訊息會根據ShardingKey進行訊息的過濾,無論你在哪個機房傳送訊息,訊息都會複製到另一個機房,此時兩個機房都有該訊息。通過ShardingKey判斷當前訊息應該被哪個機房消費,符合的才會進行消費,不符合的框架層面會自動ACK。

2.5.1.4 全單元訂閱

全單元訂閱指的是訊息無論在哪個機房發出,都會在所有的機房進行消費。

2.5.2 業務改造

2.5.2.1 訊息傳送方調整

訊息傳送方,需要結合業務場景進行區分。如果是買家場景的業務訊息,在發訊息的時候需要將buyerId放入訊息中,具體怎麼消費由消費方決定。如果消費方是單元消費的話那麼必須依賴傳送方的buyerId,否則無法知道當前訊息應該在哪個機房消費。

2.5.2.2 訊息消費方指定消費模式

前面提到了中心訂閱,單元訂閱,普通訂閱,全單元訂閱多種模式,到底要怎麼選就是要結合業務場景來定的,定好後在配置MQ資訊的時候指定即可。

比如中心訂閱就適合你整個服務都是中心的,其他機房都沒部署,這個時候肯定適合中心訂閱。比如你要對快取進行清除,就比較適合全單元訂閱,一旦資料有變更,所有機房的快取都清除掉。

2.5.3 遇到的問題

2.5.3.1 訊息冪等消費

這個點其實根據多活沒有多大關係,就算不做多活,訊息消費場景,肯定是要做冪等處理的,因為訊息本身就有重試機制。單獨拎出來說是因為在多活場景下除了訊息本身的重試會導致訊息重複消費,另外在切流的過程中,屬於切流這部分使用者的訊息會被複制到另一個機房重新進行消費,在重新消費的時候,會基於時間點進行訊息的重新投放,所以有可能會消費到之前已經消費了的訊息,這點必須注意。

再解釋下為什麼切流過程中會有訊息消費失敗以及需要複製到另一個機房去處理,如下圖所示:

使用者在當前機房進行業務操作後,會產生訊息。由於是單元訂閱,所以會在當前機房進行消費。消費過程中,發生了切流操作,消費邏輯裡面對資料庫進行讀寫,但是單元表的操作都攜帶了ShardingKey,彩虹橋會判斷ShardingKey是否符合當前的規則,發現不符合直接禁寫報錯。這批切流使用者的訊息就全部消費失敗。等到流量切到另一個機房後,如果不進行訊息的重新投遞,那麼這部分訊息就丟失了,這就是為什麼要複製到另一個機房進行訊息的重新投遞。

2.5.3.2 切流場景的訊息順序問題

上面講到了在切流過程中,會將訊息複製到另一個機房進行重新消費,然後是基於時間點去回放的,如果你的業務訊息本身就是普通的Topic,在訊息回放的時候如果同一個場景的訊息有多條,這個順序並不一定是按照之前的順序來消費,所以這裡涉及到一個消費順序的問題。

如果你之前的業務場景本身就是用的順序訊息,那麼是沒問題的,如果之前不是順序訊息,這裡就有可能有問題,我舉個例子說明下:

有個業務場景,觸發一次功能就會產生一條訊息,這個訊息是使用者級別的,也就是一個使用者會產生N條訊息。消費方會消費這些訊息進行儲存,不是來一次訊息就儲存一條資料,而是同一個使用者的只會儲存一條,訊息裡面有個狀態,會根據這個狀態進行判斷。

比如下面的訊息總共投遞了3條,按正常順序消費最終的結果是status=valid。

10:00:00  status=valid 
10:00:01  status=invalid 
10:00:02  status=valid 

如果訊息在另一個機房重新投遞的時候,消費順序變成了下面這樣,最終結果就是status=invalid。

10:00:00  status=valid 
10:00:02  status=valid 
10:00:01  status=invalid 

解決方案有下面幾種:

  1. Topic換成順序訊息,以使用者進行分割槽,這樣就能保證每個使用者的訊息嚴格按照傳送順序進行消費
  2. 對訊息做冪等,已消費過就不再消費。但是這裡跟普通的訊息不同,會有N條訊息,如果對msgId進行儲存,這樣就可以判斷是否消費過,但是這樣儲存壓力太大,當然也可以只儲存最近N條來減小儲存壓力。
  3. 訊息冪等的優化方式,讓訊息傳送方每傳送一次,都帶一個version,version必須是遞增。消費方消費訊息後把當前version儲存起來,消費之前判斷訊息的version是否大於儲存的version,滿足條件才進行消費,這樣既避免了儲存的壓力也能滿足業務的需求。

2.6 Job

Job在我們這邊用的不多,而且都是老的邏輯在用,只有幾個凌晨統計資料的任務,新的都接入了我們自研的TOC(超時中心)來管理。

2.6.1 業務改造

2.6.1.1 中心機房執行

由於Job是老的一套體系,目前也只有個位數的任務在執行,所以在底層框架層面並沒有支援多活的改造。後續會將Job的邏輯遷移到TOC中。

所以我們必須在業務層面進行改造來支援多活,改造方案有兩種,分別介紹下:

  1. 兩個機房同時執行Job,資料處理的時候,比如處理使用者的資料,通過基礎架構提供的能力,可以判斷使用者是否屬於當前機房,如果資料就執行,否則就跳過這條資料。
  2. 從業務場景出發,Job都是凌晨去執行的,不屬於線上業務,對資料一致性要求沒那麼高。即使不按單元化去處理資料,也沒什麼問題。所以只需要在中心機房執行Job即可,另一個機房我們可以通過配置讓Job任務不進行生效。

但是這種方式需要去梳理Job裡的資料操作,如果有對中心庫操作的,沒關係,本身就是在中心機房跑。如果有對單元庫操作的,需要調整為走RPC介面。

2.7 TOC

TOC是我們內部用的超時中心,當我們有需求需要在某個時間點進行觸發業務動作的時候都可以接入超時中心來處理。

舉個例子:訂單建立後,N分鐘內沒有支付就自動取消。如果業務方自己實現,要麼定時掃表進行處理,要麼用MQ的延遲訊息。有了TOC後,我們會在訂單建立之後,往TOC註冊一個超時任務,指定某個時間點,你要回撥我。在回撥的邏輯邏輯裡去判斷訂單是否已完成支付,如果沒有則取消。

2.7.1 業務改造

2.7.1.1 任務註冊調整

在註冊超時中心任務的時候,業務方需要識別任務是否要符合單元化的標準。如果此任務只是對中心資料庫進行操作,那麼這個任務回撥在中心機房即可。如果此任務是對單後設資料庫操作,那麼在註冊任務的時候就需要指定buyerId,超時中心在觸發回撥的時候會根據buyerId進行路由到使用者所屬機房進行處理。

目前超時中心是隻會在中心機房進行部署,也就是所有的任務都會在中心機房進行排程。如果任務註冊的時候沒有指定buyerId,超時中心在回撥的時候就不知道要回撥哪個機房,預設回撥中心機房。要想讓超時中心根據多活的路由規則進行回撥,那麼註冊的時候必須指定buyerId。

3. 服務劃分

閱讀完上面的改造內容,相信大家還有一個疑惑點就是我的服務該怎麼劃分呢?我要不要做單元化呢?

3.1 整體方向

首先要根據整個多活的一個整體目標和方向去梳理,比如我們的整體方向就是買家交易的核心鏈路必須實現單元化改造。那麼這整個鏈路所有依賴的上下游都需要改造。

使用者瀏覽商品,進入確認訂單,下單,支付,查詢訂單資訊。這個核心鏈路其實涉及到了很多的業務域,比如:商品,出價,訂單,支付,商家等等。

在這些已經明確了的業務域下面,可能還有一些其他的業務域在支撐著,所以要把整體的鏈路都梳理出來,一起改造。當然也不是所有的都必須做單元化,還是得看業務場景,比如庫存,肯定是在交易核心鏈路上,但是不需要改造,必須走中心。

3.2 服務型別

3.2.1 中心服務

中心服務只會在中心機房部署,並且資料庫也一定是中心庫。可以對整個應用進行打標成中心,這樣外部訪問這個服務的介面時都會被路由到中心機房。

3.2.2 單元服務

單元服務會在中心機房和單元機房同時部署,並且資料庫也一定是單元庫。單元服務是買家維度的業務,比如確認訂單,下單。

買家維度的業務,在介面定義上,第一個引數必須是buyerId,因為要進行路由。使用者的請求已經根據規則進行分流到不同的機房,只會操作對應機房裡面的資料庫。

3.2.3 中心單元服務

中心單元服務也就是說這個服務裡面既有中心的介面也有單元的介面。並且資料庫也是有兩套。所以這種服務其實也是要在兩個機房同時部署的,只不過是單元機房只會有單元介面過來的流量,中心介面是沒有流量的。

一些底層的支撐業務,比如商品,商家這些就屬於中心單元服務。支撐維度的業務是沒有buyerId的,商品是通用的,並不屬於某一個買家。

而支撐型別的業務底層的資料庫是中心單元庫,也就是中心寫單元讀,寫請求是在中心進行,比如商品的建立,修改等。操作後會同步到另一個機房的資料庫裡面。這樣的好處就是可以減少我們在核心鏈路中的耗時,如果商品不做單元化部署,那麼瀏覽商品或者下單的時候查詢商品資訊都必須走中心機房進行讀取。而現在則會就近路由進行介面的呼叫,請求到中心機房就調中心機房的服務,請求到單元機房就調單元機房的服務,單元機房也是有資料庫的,不需要跨機房。

從長遠考慮,還是需要進行拆分,把中心的業務和單元的業務拆開,這樣會比較清晰。對於後面新同學在定義介面,運算元據庫,快取等都有好處,因為現在是混合在一起的,你必須要知道當前這個介面的業務屬於單元還是中心。

拆分也不是絕對的,還是那句話得從業務場景出發。像訂單裡面的買家和賣家的業務,我覺得可以拆分,後續維護也比較方便。但是像商品這種,並不存在兩種角色,就是商品,對商品的增刪改成在一個專案中也方便維護,只不過是要進行介面的分類,將新增,修改,刪除的介面標記為中心。

4. 切流方案

前面我們也提到了再切流過程中,會禁寫,會複製MQ的訊息到另一個機房重新消費。接下來給大家介紹下我們的切流方案,能夠幫助大家更深刻的理解整個多活的異常場景下處理流程。

  1. 下發禁寫規則

當需要切流的時候,操作人員會通過雙活控制中心的後臺進行操作。切流之前需要先進行已有流量的清理,需要下發禁寫規則。禁寫規則會下發到中心和單元兩個機房對應的配置中心裡面,通過配置中心去通知需要監聽的程式。

  1. 彩虹橋執行禁寫邏輯

彩虹橋會用到禁寫規則,當禁寫規則在配置中心修改後,彩虹橋能立馬感知到,然後會根據SQL中攜帶的shardingkey進行規則的判斷,看當前shardingkey是否屬於這個機房,如果不屬於則進行攔截。

  1. 反饋禁寫生效結果

當配置變更後會推送到彩虹橋,配置中心會感知到配置推送的結果,然後將生效的結果反饋給雙活控制中心。

  1. 推送禁寫生效時間給Otter

雙活控制中心收到所有的反饋後,會將全部生效的時間點通過MQ訊息告訴Otter。

  1. Otter進行資料同步

Otter收到訊息會根據時間點進行資料同步。

  1. Otter同步完成反饋同步結果

生效時間點之前的資料全部同步完成後會通過MQ訊息反饋給雙活控制中心。

  1. 下發最新流量規則

雙活中心收到Otter的同步完成的反饋訊息後,會下發流量規則,流量規則會下發到DLB,RPC,彩虹橋。

後續使用者的請求就會直接被路由到正確的機房。

5. 總結

相信大家看了這篇文章,對多活的改造應該有了一定的瞭解。當然本篇文章並沒有把所有多活相關的改造都解釋清楚,因為整個改造的範圍實在是太大了。本篇主要講的是中介軟體層面和業務層面的一些改造點和過程,同時還有其他的一些點都沒有提到。比如:機房網路的建設,釋出系統支援多機房,監控系統支援多機房的整個鏈路監控,資料巡檢的監控等等。

多活是一個高可用的容災手段,但實現的成本和對技術團隊的要求非常高。在實現多活的時候,我們應該結合業務場景去進行設計,不是所有系統,所有功能都要滿足多活的條件,也沒有100%的可用性,有的只是在極端場景下對業務的一些取捨罷了,優先保證核心功能。

以上就是得物訂單域在參與多活改造中的一些經驗,分享出來希望可以對正在閱讀的你有一些幫助。

文/YINJIHUAN

關注得物技術,做最潮技術人!

相關文章