自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 新聞
本篇主要講的是中間件層面和業(yè)務層面的一些改造點和過程。

一、簡述

異地多活的概念以及為什么要做異地多活這里就不進行概述了。概念性的很多,像什么同城雙活、兩地三中心、三地五中心等等概念。

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

???1、機房劃分

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

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

???2、單元化

單元化簡單點我們直接就可以認為是一個機房,在這個單元內(nèi)能夠完成業(yè)務的閉環(huán)。比如說用戶進入APP,瀏覽商品,選擇商品確認訂單,下單,支付,查看訂單信息,這整個流程都在一個單元中能夠完成,并且數(shù)據(jù)也是存儲在這個單元里面。

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

二、改造點

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

圖片

如上圖所示,客戶端的請求進來會先到SLB(負載均衡),然后到我們內(nèi)部的網(wǎng)關,通過網(wǎng)關再分發(fā)到具體的業(yè)務服務。業(yè)務服務會依賴Redis、Mysql、 MQ、Nacos等中間件。

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

圖片

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

???1、流量調(diào)度

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

沒做多活之前,域名會解析到一個機房內(nèi),做了多活后,域名會隨機解析到不同的機房中。如果按照這種隨機的方式是肯定有問題的,對于服務的調(diào)用是無所謂的,因為沒有狀態(tài)。但是服務內(nèi)部依賴的存儲是有狀態(tài)的呀。

我們是電商業(yè)務,用戶在中心機房下了一個單,然后跳轉到訂單詳情,這個時候請求到了單元機房,底層數(shù)據(jù)同步有延遲,一訪問報個錯:訂單不存在。用戶當場就懵了,錢都付了,訂單沒了。

所以針對同一個用戶,盡可能在一個機房內(nèi)完成業(yè)務閉環(huán)。為了解決流量調(diào)度的問題,我們基于OpenResty二次開發(fā)出了DLB流量網(wǎng)關,DLB會對接多活控制中心,能夠知道當前訪問的用戶是屬于哪個機房,如果用戶不屬于當前機房,DLB會直接將請求路由到該用戶所屬機房內(nèi)的DLB。

圖片

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

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

???2、RPC框架

當用戶的請求達到了單元機房內(nèi),理論上后續(xù)所有的操作都是在單元機房完成。前面我們也提到了,用戶的請求盡量在一個機房內(nèi)完成閉環(huán),只是盡量,沒有說全部。

這是因為有的業(yè)務場景不適合劃分單元,比如庫存扣減。所以在我們的劃分里面,有一個機房是中心機房,那些不做多活的業(yè)務只會部署在中心機房里面,那么庫存扣減的時候就需要跨機房調(diào)用。

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

圖片

當我們的注冊信息采用雙向復制后,對于中心服務,直接跨機房調(diào)用。對于單元服務會存在多個機房的服務信息,如果不進行控制,則會出現(xiàn)調(diào)用其他機房的情況,所以RPC框架要進行改造。

1)定義路由類型

  • 默認路由

請求到中心機房,會優(yōu)先調(diào)用中心機房內(nèi)的服務,如果中心機房無此服務,則調(diào)用單元機房的服務,如果單元機房沒有此服務則直接報錯。

  • 單元路由

請求到單元機房,那么說明此用戶的流量規(guī)則是在單元機房,接下來所有的RPC調(diào)用都只會調(diào)用單元機房內(nèi)的服務,沒有服務則報錯。

  • 中心路由

請求到單元機房,那么直接調(diào)用中心機房的服務,中心機房沒有服務則報錯。請求到中心機房,那么就本機房調(diào)用。

2)業(yè)務改造

業(yè)務方需要對自己的接口(Java interface)進行標記是什么類型,通過@HARoute加在接口上面。標記完成后,在Dubbo接口進行注冊的時候,會把路由類型放入到這個接口的元數(shù)據(jù)里面,在Nacos后臺可以查看。后面通過RPC調(diào)用接口內(nèi)部所有的方法都會按照標記類型進行路由。

如果標記為單元路由,目前我們內(nèi)部的規(guī)范是方法的第一個參數(shù)為小寫的long buyerId,RPC在路由的時候會根據(jù)這個值判斷用戶所在的機房。

路由邏輯如下:

圖片

3)改造過程

  • 接口復制一份,命名為UnitApi,第一個參數(shù)加long buyerId。在新接口的實現(xiàn)里面調(diào)用老接口,新舊接口共存。
  • 將UnitApi發(fā)布上線,此時沒有流量。
  • 業(yè)務方需要升級其他域的API包,將老接口的調(diào)用切換為新的UnitApi,此處增加開關控制。
  • 上線后,通過開關控制調(diào)用走UnitApi,有問題可關閉開關。
  • 下線老的API,完成切換。

4)遇到的問題

  • 其他場景切單元接口

除了RPC直接調(diào)用的接口,還有一大部分是通過Dubbo泛化過來的,這塊在上線后也需要將流量切到UnitApi,等老接口沒有請求量之后才能下線。

  • 接口分類

接口進行分類,之前沒有多活的約束,一個Java interface中的方法可能各種各樣,如果現(xiàn)在你的interface為單元路由,那么里面的方法第一個參數(shù)都必須加buyerId,其他沒有buyerId場景的方法要挪出去。

  • 業(yè)務層面調(diào)整

業(yè)務層面調(diào)整,比如之前查詢訂單只需要一個訂單號,但是現(xiàn)在需要buyerId進行路由,所以接入這個接口的上游都需要調(diào)整。

???3、數(shù)據(jù)庫

請求順利地到達了服務層,接下來要跟數(shù)據(jù)庫打交道了。數(shù)據(jù)庫我們定義了不同的類型,定義如下:

  • 單元化

此庫為單元庫,會同時在兩個機房部署,每個機房都有完整的數(shù)據(jù),數(shù)據(jù)采用雙向同步。

  • 中心化

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

  • 中心單元化

此庫為中心單元庫,會同時在兩個機房部署,中心可以讀寫,其他機房只能讀。中心寫數(shù)據(jù)后單向復制到另一個機房。

1)代理中間件

目前各個業(yè)務方用的都是客戶端形式的Sharding中間件,每個業(yè)務方的版本還不一致。在多活切流的過程中需要對數(shù)據(jù)庫禁寫來保證業(yè)務數(shù)據(jù)的準確性,如果沒有統(tǒng)一的中間件,這將是一件很麻煩的事情。

所以我們通過對ShardingSphere進行深度定制,二次開發(fā)數(shù)據(jù)庫代理中間件彩虹橋。各業(yè)務方需要接入彩虹橋來替換之前的Sharding方式。

圖片

2)分布式ID

單元化的庫,數(shù)據(jù)層面會做雙向同步復制操作。如果直接用表的自增ID則會出現(xiàn)下面的沖突問題:

圖片

這個問題可以通過設置不同機房有不同的自增步長來解決,比如中心機房的自增步長為奇數(shù),單元機房的自增步長為偶數(shù)。但比較麻煩,后續(xù)可能會增加更多的機房。我們采用了一種一勞永逸的方式,接入全局唯一的分布式ID來避免主鍵的沖突。

  • 客戶端接入

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

  • 彩虹橋接入

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

圖片

3)業(yè)務改造

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

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

這里跟大家簡單的說明下為什么切流過程中要禁寫,這個其實跟JVM的垃圾回收有點相似。如果不對操作禁寫,那么就會不斷的產(chǎn)生數(shù)據(jù),而我們切流,一定要保證當前機房的數(shù)據(jù)全部同步過去了之后才開始生效流量規(guī)則,否則用戶切到另一個機房,數(shù)據(jù)沒同步完,就會產(chǎn)生業(yè)務問題。除了彩虹橋會禁寫,RPC框架內(nèi)部也會根據(jù)流量規(guī)則進行阻斷。

  • 數(shù)據(jù)庫連接指定連接模式

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

如果應用的數(shù)據(jù)源指定了連接模式為中心,那么在中心機房可以正常初始化數(shù)據(jù)源。在單元機房不會初始化數(shù)據(jù)源。

如果應用的數(shù)據(jù)源指定了連接模式為單元,那么在中心機房和單元機房都可以正常初始化數(shù)據(jù)源。

圖片

這里解釋下為什么要有連接模式這個設計?

在我們的項目中,會出現(xiàn)同時連接2個庫的情況,一個單元庫,一個中心庫。如果沒有連接模式,上層代碼是一份,這個項目會在中心和單元兩個機房同時部署,也就是兩個地方都會去創(chuàng)建數(shù)據(jù)源。

但實際上,我的中心庫只需要在中心機房連接就可以了,因為中心庫所有的操作都是中心接口,流量必定會走中心,我在單元機房去連接是沒有意義的。另一個問題就是我不需要在單元機房維護中心庫的數(shù)據(jù)庫信息,如果沒有連接模式,那么單元機房的彩虹橋也必須要有中心庫的信息,因為項目會進行連接。

4)遇到的問題

  • 單元接口中不能訪問中心數(shù)據(jù)庫

如果接口標記成了單元接口,那么只能操作單元庫。在以前沒有做多活改造的時候,基本上沒有什么中心和單元的概念,所有的表也都是放在一起的。多活改造后,我們會根據(jù)業(yè)務場景對數(shù)據(jù)庫進行劃分。

劃分后,中心庫只會被中心機房的程序使用,在單元機房是不允許連接中心庫。所以單元接口里面如果涉及到對中心庫的操作,必定會報錯。這塊需要調(diào)整成走中心的RPC接口。

  • 中心接口不能訪問單元數(shù)據(jù)庫

跟上面同樣的問題,如果接口是中心的,也不能在接口里面操作單元庫。中心接口的請求都會強制走到中心機房,如果里面有涉及到另一個機房的操作,也必須走RPC接口進行正確的路由,因為你中心機房不能操作另一個機房的數(shù)據(jù)庫。

  • 批量查詢調(diào)整

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

這樣批量查詢的場景,只能針對同一個買家可用,如果是不同的買家需要分批調(diào)用。

???4、Redis

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

  • 不做雙向同步

Redis不會和數(shù)據(jù)庫一樣做雙向同步,也就是中心機房一個Redis集群,單元機房一個Redis集群。每個機房的集群中只存在一部分用戶的緩存數(shù)據(jù),不是全量的。

  • Redis類型

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

1)業(yè)務改造

  • Redis多數(shù)據(jù)源支持

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

基礎架構提供的Redis包需要支持多數(shù)據(jù)源的創(chuàng)建,并且定義通用的配置格式,業(yè)務方只需要在自己 的配置里面指定集群和連接模式即可完成接入。此處的連接模式跟數(shù)據(jù)庫的一致。

具體的Redis實例信息會在配置中心統(tǒng)一維護,不需要業(yè)務方關心,這樣在做機房擴容的時候,業(yè)務方是不需要調(diào)整的,配置如下:

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

同時我們在使用Redis的時候要指定對應的數(shù)據(jù)源,如下:

@Autowired 
@Qualifier(RedisTemplateNameConstants.REDIS_TEMPLATE_UNIT)
private RedisTemplate<String, Object> redisTemplate;
  • 數(shù)據(jù)一致性

數(shù)據(jù)庫緩存場景,由于Redis不會雙向同步,就會存在數(shù)據(jù)的不一致性問題。比如用戶一開始在中心機房,然后緩存了一份數(shù)據(jù)。進行切流,切到單元機房,單元機房又緩存了一份數(shù)據(jù)。再進行切回中心機房的操作,此時中心機房里的緩存是舊的數(shù)據(jù),不是最新的數(shù)據(jù)。

所以在底層數(shù)據(jù)變更的時候,我們需要對緩存進行失效操作,這樣才能保證數(shù)據(jù)的最終一致性。單純依靠緩存的失效時間來達到一致性不是一個合適的方案。

這里我們的方案是采用訂閱數(shù)據(jù)庫的binlog來進行緩存的失效操作,可以訂閱本機房的binlog,也可以訂閱其他機房的binlog來實現(xiàn)所有機房的緩存失效。

圖片

2)遇到的問題

  • 序列化協(xié)議兼容

在接入新的Redis Client包后,測試環(huán)境出現(xiàn)了老數(shù)據(jù)的兼容問題。大部分應用都沒問題,有個別應用雖然用了統(tǒng)一的底層包,但是自己定制了序列化方式,導致Redis按新的方式裝配后沒有用到自定義的協(xié)議,這塊也是進行了改造,支持多數(shù)據(jù)源的協(xié)議自定義。

  • 分布式鎖的使用

目前項目中的分布式鎖是基于Redis實現(xiàn),當Redis有多個數(shù)據(jù)源之后,分布式鎖也需要進行適配。在使用的地方要區(qū)分場景,默認都是用的中心Redis來加鎖。

但是單元接口里面的操作都是買家場景,所以這部分需要調(diào)整為單元Redis鎖對象進行加鎖,這樣能夠提高性能。其他的一些場景有涉及到全局資源的鎖定,那就用中心Redis鎖對象進行加鎖。

???5、RocketMQ

請求到達服務層后,跟數(shù)據(jù)庫和緩存都進行了交互,接下來的邏輯是要發(fā)一條消息出去,其他業(yè)務需要監(jiān)聽這個消息做一些業(yè)務處理。

如果是在單元機房發(fā)出的消息,發(fā)到了單元機房的MQ中,單元機房的程序進行消費,是沒有問題的。但如果中心機房的程序要消費這個消息怎么辦?所以MQ跟數(shù)據(jù)庫一樣,也要做同步,將消息同步到另一個機房的MQ中,至于另一個機房的消費者要不要消費,這就要讓業(yè)務場景去決定。

圖片

1)定義消費類型

  • 中心訂閱

中心訂閱指的是消息無論是在中心機房發(fā)出的還是單元機房發(fā)出的,都只會在中心機房進行消費。如果是單元機房發(fā)出的,會將單元的消息復制一份到中心進行消費。

  • 普通訂閱

普通訂閱就是默認的行為,指的是就近消費。在中心機房發(fā)送的消息就由中心機房的消費者進行消費,在單元機房發(fā)送的消息就由單元機房的消費進行消費。

  • 單元訂閱

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

  • 全單元訂閱

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

2)業(yè)務改造

  • 消息發(fā)送方調(diào)整

消息發(fā)送方,需要結合業(yè)務場景進行區(qū)分。如果是買家場景的業(yè)務消息,在發(fā)消息的時候需要將buyerId放入消息中,具體怎么消費由消費方?jīng)Q定。如果消費方是單元消費的話那么必須依賴發(fā)送方的buyerId,否則無法知道當前消息應該在哪個機房消費。

  • 消息消費方指定消費模式

前面提到了中心訂閱,單元訂閱,普通訂閱,全單元訂閱多種模式,到底要怎么選就是要結合業(yè)務場景來定的,定好后在配置MQ信息的時候指定即可。

比如中心訂閱就適合你整個服務都是中心的,其他機房都沒部署,這個時候肯定適合中心訂閱。比如你要對緩存進行清除,就比較適合全單元訂閱,一旦數(shù)據(jù)有變更,所有機房的緩存都清除掉。

3)遇到的問題

  • 消息冪等消費

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

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

圖片

用戶在當前機房進行業(yè)務操作后,會產(chǎn)生消息。由于是單元訂閱,所以會在當前機房進行消費。消費過程中,發(fā)生了切流操作,消費邏輯里面對數(shù)據(jù)庫進行讀寫,但是單元表的操作都攜帶了ShardingKey,彩虹橋會判斷ShardingKey是否符合當前的規(guī)則,發(fā)現(xiàn)不符合直接禁寫報錯。這批切流用戶的消息就全部消費失敗。等到流量切到另一個機房后,如果不進行消息的重新投遞,那么這部分消息就丟失了,這就是為什么要復制到另一個機房進行消息的重新投遞。

  • 切流場景的消息順序問題

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

如果你之前的業(yè)務場景本身就是用的順序消息,那么是沒問題的,如果之前不是順序消息,這里就有可能有問題,我舉個例子說明下:

有個業(yè)務場景,觸發(fā)一次功能就會產(chǎn)生一條消息,這個消息是用戶級別的,也就是一個用戶會產(chǎn)生N條消息。消費方會消費這些消息進行存儲,不是來一次消息就存儲一條數(shù)據(jù),而是同一個用戶的只會存儲一條,消息里面有個狀態(tài),會根據(jù)這個狀態(tài)進行判斷。

比如下面的消息總共投遞了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

解決方案有下面幾種:

a. Topic換成順序消息,以用戶進行分區(qū),這樣就能保證每個用戶的消息嚴格按照發(fā)送順序進行消費。

b. 對消息做冪等,已消費過就不再消費。但是這里跟普通的消息不同,會有N條消息,如果對msgId進行存儲,這樣就可以判斷是否消費過,但是這樣存儲壓力太大,當然也可以只存儲最近N條來減小存儲壓力。

c. 消息冪等的優(yōu)化方式,讓消息發(fā)送方每發(fā)送一次,都帶一個version,version必須是遞增。消費方消費消息后把當前version存儲起來,消費之前判斷消息的version是否大于存儲的version,滿足條件才進行消費,這樣既避免了存儲的壓力也能滿足業(yè)務的需求。

???6、Job

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

業(yè)務改造:中心機房執(zhí)行

由于Job是老的一套體系,目前也只有個位數(shù)的任務在執(zhí)行,所以在底層框架層面并沒有支持多活的改造。后續(xù)會將Job的邏輯遷移到TOC中。

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

  • 兩個機房同時執(zhí)行Job,數(shù)據(jù)處理的時候,比如處理用戶的數(shù)據(jù),通過基礎架構提供的能力,可以判斷用戶是否屬于當前機房,如果數(shù)據(jù)就執(zhí)行,否則就跳過這條數(shù)據(jù)。
  • 從業(yè)務場景出發(fā),Job都是凌晨去執(zhí)行的,不屬于在線業(yè)務,對數(shù)據(jù)一致性要求沒那么高。即使不按單元化去處理數(shù)據(jù),也沒什么問題。所以只需要在中心機房執(zhí)行Job即可,另一個機房我們可以通過配置讓Job任務不進行生效。

但是這種方式需要去梳理Job里的數(shù)據(jù)操作,如果有對中心庫操作的,沒關系,本身就是在中心機房跑。如果有對單元庫操作的,需要調(diào)整為走RPC接口。

???7、TOC

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

舉個例子:訂單創(chuàng)建后,N分鐘內(nèi)沒有支付就自動取消。如果業(yè)務方自己實現(xiàn),要么定時掃表進行處理,要么用MQ的延遲消息。有了TOC后,我們會在訂單創(chuàng)建之后,往TOC注冊一個超時任務,指定某個時間點,你要回調(diào)我。在回調(diào)的邏輯邏輯里去判斷訂單是否已完成支付,如果沒有則取消。

圖片

業(yè)務改造:任務注冊調(diào)整

在注冊超時中心任務的時候,業(yè)務方需要識別任務是否要符合單元化的標準。如果此任務只是對中心數(shù)據(jù)庫進行操作,那么這個任務回調(diào)在中心機房即可。如果此任務是對單元數(shù)據(jù)庫操作,那么在注冊任務的時候就需要指定buyerId,超時中心在觸發(fā)回調(diào)的時候會根據(jù)buyerId進行路由到用戶所屬機房進行處理。

目前超時中心是只會在中心機房進行部署,也就是所有的任務都會在中心機房進行調(diào)度。如果任務注冊的時候沒有指定buyerId,超時中心在回調(diào)的時候就不知道要回調(diào)哪個機房,默認回調(diào)中心機房。要想讓超時中心根據(jù)多活的路由規(guī)則進行回調(diào),那么注冊的時候必須指定buyerId。

圖片

三、服務劃分

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

???1、整體方向

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

用戶瀏覽商品,進入確認訂單,下單,支付,查詢訂單信息。這個核心鏈路其實涉及到了很多的業(yè)務域,比如:商品,出價,訂單,支付,商家等等。

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

???2、服務類型

1)中心服務

中心服務只會在中心機房部署,并且數(shù)據(jù)庫也一定是中心庫??梢詫φ麄€應用進行打標成中心,這樣外部訪問這個服務的接口時都會被路由到中心機房。

2)單元服務

單元服務會在中心機房和單元機房同時部署,并且數(shù)據(jù)庫也一定是單元庫。單元服務是買家維度的業(yè)務,比如確認訂單,下單。

買家維度的業(yè)務,在接口定義上,第一個參數(shù)必須是buyerId,因為要進行路由。用戶的請求已經(jīng)根據(jù)規(guī)則進行分流到不同的機房,只會操作對應機房里面的數(shù)據(jù)庫。

圖片

3)中心單元服務

中心單元服務也就是說這個服務里面既有中心的接口也有單元的接口。并且數(shù)據(jù)庫也是有兩套。所以這種服務其實也是要在兩個機房同時部署的,只不過是單元機房只會有單元接口過來的流量,中心接口是沒有流量的。

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

而支撐類型的業(yè)務底層的數(shù)據(jù)庫是中心單元庫,也就是中心寫單元讀,寫請求是在中心進行,比如商品的創(chuàng)建,修改等。操作后會同步到另一個機房的數(shù)據(jù)庫里面。這樣的好處就是可以減少我們在核心鏈路中的耗時,如果商品不做單元化部署,那么瀏覽商品或者下單的時候查詢商品信息都必須走中心機房進行讀取。而現(xiàn)在則會就近路由進行接口的調(diào)用,請求到中心機房就調(diào)中心機房的服務,請求到單元機房就調(diào)單元機房的服務,單元機房也是有數(shù)據(jù)庫的,不需要跨機房。

圖片

從長遠考慮,還是需要進行拆分,把中心的業(yè)務和單元的業(yè)務拆開,這樣會比較清晰。對于后面新同學在定義接口,操作數(shù)據(jù)庫,緩存等都有好處,因為現(xiàn)在是混合在一起的,你必須要知道當前這個接口的業(yè)務屬于單元還是中心。

拆分也不是絕對的,還是那句話得從業(yè)務場景出發(fā)。像訂單里面的買家和賣家的業(yè)務,我覺得可以拆分,后續(xù)維護也比較方便。但是像商品這種,并不存在兩種角色,就是商品,對商品的增刪改成在一個項目中也方便維護,只不過是要進行接口的分類,將新增,修改,刪除的接口標記為中心。

四、切流方案

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

圖片

1)下發(fā)禁寫規(guī)則

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

2)彩虹橋執(zhí)行禁寫邏輯

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

3)反饋禁寫生效結果

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

4)推送禁寫生效時間給Otter

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

5)Otter進行數(shù)據(jù)同步

Otter收到消息會根據(jù)時間點進行數(shù)據(jù)同步。

6)Otter同步完成反饋同步結果

生效時間點之前的數(shù)據(jù)全部同步完成后會通過MQ消息反饋給雙活控制中心。

7)下發(fā)最新流量規(guī)則

雙活中心收到Otter的同步完成的反饋消息后,會下發(fā)流量規(guī)則,流量規(guī)則會下發(fā)到DLB,RPC,彩虹橋。后續(xù)用戶的請求就會直接被路由到正確的機房。

五、總結

相信大家看了這篇文章,對多活的改造應該有了一定的了解。當然本篇文章并沒有把所有多活相關的改造都解釋清楚,因為整個改造的范圍實在是太大了。本篇主要講的是中間件層面和業(yè)務層面的一些改造點和過程,同時還有其他的一些點都沒有提到。比如:機房網(wǎng)絡的建設、發(fā)布系統(tǒng)支持多機房、監(jiān)控系統(tǒng)支持多機房的整個鏈路監(jiān)控,數(shù)據(jù)巡檢的監(jiān)控等等。

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

以上就是我們在多活改造中的一些經(jīng)驗,分享出來希望可以對正在閱讀的你有一些幫助。

責任編輯:張燕妮 來源: 得物技術
相關推薦

2023-11-28 07:45:48

Rust自動化測試

2024-04-26 00:28:14

異地多活架構

2018-05-24 10:04:16

Zookeeper異地原理

2020-11-20 09:23:01

高可用異地淘寶

2012-06-17 12:58:04

架構師架構

2023-11-27 07:57:46

2021-02-04 10:00:09

異地多中心容災

2021-02-24 10:05:07

架構運維技術

2022-04-26 05:57:18

微服務Nacos

2021-04-23 09:55:27

技術開發(fā)實踐

2022-01-10 08:17:40

異地設計實踐

2013-07-31 17:47:16

網(wǎng)站制作Web制作Web網(wǎng)站

2019-03-18 10:32:33

容災雙活同城

2024-08-12 08:04:00

2021-11-04 06:58:31

Python開源特性

2020-06-28 08:34:07

架構師阿里軟件

2012-06-20 09:14:07

系統(tǒng)架構運維

2021-01-29 09:18:09

技術研發(fā)架構

2019-05-30 09:32:49

2019-07-24 09:22:45

Elasticsear數(shù)據(jù)Oracle
點贊
收藏

51CTO技術棧公眾號