成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

一、簡(jiǎn)述

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

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

???1、機(jī)房劃分

得物多活改造一期目前有兩個(gè)機(jī)房,分別是機(jī)房A和機(jī)房B。文章中大部分圖中都會(huì)有標(biāo)識(shí),這就說明是兩個(gè)不同的機(jī)房。

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

???2、單元化

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

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

二、改造點(diǎn)

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

圖片

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

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

圖片

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

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

用戶的請(qǐng)求,從客戶端發(fā)出,這個(gè)用戶的請(qǐng)求該到哪個(gè)機(jī)房,這是我們要改造的第一個(gè)點(diǎn)。

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

我們是電商業(yè)務(wù),用戶在中心機(jī)房下了一個(gè)單,然后跳轉(zhuǎn)到訂單詳情,這個(gè)時(shí)候請(qǐng)求到了單元機(jī)房,底層數(shù)據(jù)同步有延遲,一訪問報(bào)個(gè)錯(cuò):訂單不存在。用戶當(dāng)場(chǎng)就懵了,錢都付了,訂單沒了。

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

圖片

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

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

???2、RPC框架

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

這是因?yàn)橛械臉I(yè)務(wù)場(chǎng)景不適合劃分單元,比如庫(kù)存扣減。所以在我們的劃分里面,有一個(gè)機(jī)房是中心機(jī)房,那些不做多活的業(yè)務(wù)只會(huì)部署在中心機(jī)房里面,那么庫(kù)存扣減的時(shí)候就需要跨機(jī)房調(diào)用。

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

圖片

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

1)定義路由類型

  • 默認(rèn)路由

請(qǐng)求到中心機(jī)房,會(huì)優(yōu)先調(diào)用中心機(jī)房?jī)?nèi)的服務(wù),如果中心機(jī)房無此服務(wù),則調(diào)用單元機(jī)房的服務(wù),如果單元機(jī)房沒有此服務(wù)則直接報(bào)錯(cuò)。

  • 單元路由

請(qǐng)求到單元機(jī)房,那么說明此用戶的流量規(guī)則是在單元機(jī)房,接下來所有的RPC調(diào)用都只會(huì)調(diào)用單元機(jī)房?jī)?nèi)的服務(wù),沒有服務(wù)則報(bào)錯(cuò)。

  • 中心路由

請(qǐng)求到單元機(jī)房,那么直接調(diào)用中心機(jī)房的服務(wù),中心機(jī)房沒有服務(wù)則報(bào)錯(cuò)。請(qǐng)求到中心機(jī)房,那么就本機(jī)房調(diào)用。

2)業(yè)務(wù)改造

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

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

路由邏輯如下:

圖片

3)改造過程

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

4)遇到的問題

  • 其他場(chǎng)景切單元接口

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

  • 接口分類

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

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

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

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

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

  • 單元化

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

  • 中心化

此庫(kù)為中心庫(kù),只會(huì)在中心機(jī)房部署。

  • 中心單元化

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

1)代理中間件

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

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

圖片

2)分布式ID

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

圖片

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

  • 客戶端接入

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

  • 彩虹橋接入

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

圖片

3)業(yè)務(wù)改造

  • 單元化庫(kù)寫請(qǐng)求必須攜帶ShardingKey

在Dao層對(duì)表進(jìn)行操作的時(shí)候,會(huì)通過ThreadLocal設(shè)置當(dāng)前方法的ShardingKey,然后通過Mybatis攔截器機(jī)制,將ShardingKey通過Hint的方式放入SQL中,帶給彩虹橋。彩虹橋會(huì)判斷當(dāng)前的ShardingKey是否屬于當(dāng)前機(jī)房,如果不是直接禁寫報(bào)錯(cuò)。

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

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

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

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

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

圖片

這里解釋下為什么要有連接模式這個(gè)設(shè)計(jì)?

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

但實(shí)際上,我的中心庫(kù)只需要在中心機(jī)房連接就可以了,因?yàn)橹行膸?kù)所有的操作都是中心接口,流量必定會(huì)走中心,我在單元機(jī)房去連接是沒有意義的。另一個(gè)問題就是我不需要在單元機(jī)房維護(hù)中心庫(kù)的數(shù)據(jù)庫(kù)信息,如果沒有連接模式,那么單元機(jī)房的彩虹橋也必須要有中心庫(kù)的信息,因?yàn)轫?xiàng)目會(huì)進(jìn)行連接。

4)遇到的問題

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

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

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

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

跟上面同樣的問題,如果接口是中心的,也不能在接口里面操作單元庫(kù)。中心接口的請(qǐng)求都會(huì)強(qiáng)制走到中心機(jī)房,如果里面有涉及到另一個(gè)機(jī)房的操作,也必須走RPC接口進(jìn)行正確的路由,因?yàn)槟阒行臋C(jī)房不能操作另一個(gè)機(jī)房的數(shù)據(jù)庫(kù)。

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

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

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

???4、Redis

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

  • 不做雙向同步

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

  • Redis類型

Redis分為中心和單元,中心只會(huì)在中心機(jī)房部署,單元會(huì)在中心和單元兩個(gè)機(jī)房部署。

1)業(yè)務(wù)改造

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

多活改造之前,每個(gè)應(yīng)用都有一個(gè)單獨(dú)的Redis集群,多活改造后,由于應(yīng)用沒有進(jìn)行單元化和中心的拆分,所以一個(gè)應(yīng)用中會(huì)存在需要連接兩個(gè)Redis的情況。一個(gè)中心Redis,一個(gè)單元Redis。

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

具體的Redis實(shí)例信息會(huì)在配置中心統(tǒng)一維護(hù),不需要業(yè)務(wù)方關(guān)心,這樣在做機(jī)房擴(kuò)容的時(shí)候,業(yè)務(wù)方是不需要調(diào)整的,配置如下:

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

同時(shí)我們?cè)谑褂肦edis的時(shí)候要指定對(duì)應(yīng)的數(shù)據(jù)源,如下:

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

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

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

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

圖片

2)遇到的問題

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

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

  • 分布式鎖的使用

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

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

???5、RocketMQ

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

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

圖片

1)定義消費(fèi)類型

  • 中心訂閱

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

  • 普通訂閱

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

  • 單元訂閱

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

  • 全單元訂閱

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

2)業(yè)務(wù)改造

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

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

  • 消息消費(fèi)方指定消費(fèi)模式

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

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

3)遇到的問題

  • 消息冪等消費(fèi)

這個(gè)點(diǎn)其實(shí)根據(jù)多活沒有多大關(guān)系,就算不做多活,消息消費(fèi)場(chǎng)景,肯定是要做冪等處理的,因?yàn)橄⒈旧砭陀兄卦嚈C(jī)制。單獨(dú)拎出來說是因?yàn)樵诙嗷顖?chǎng)景下除了消息本身的重試會(huì)導(dǎo)致消息重復(fù)消費(fèi),另外在切流的過程中,屬于切流這部分用戶的消息會(huì)被復(fù)制到另一個(gè)機(jī)房重新進(jìn)行消費(fèi),在重新消費(fèi)的時(shí)候,會(huì)基于時(shí)間點(diǎn)進(jìn)行消息的重新投放,所以有可能會(huì)消費(fèi)到之前已經(jīng)消費(fèi)了的消息,這點(diǎn)必須注意。

再解釋下為什么切流過程中會(huì)有消息消費(fèi)失敗以及需要復(fù)制到另一個(gè)機(jī)房去處理,如下圖所示:

圖片

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

  • 切流場(chǎng)景的消息順序問題

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

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

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

比如下面的消息總共投遞了3條,按正常順序消費(fèi)最終的結(jié)果是status=valid。

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

如果消息在另一個(gè)機(jī)房重新投遞的時(shí)候,消費(fèi)順序變成了下面這樣,最終結(jié)果就是status=invalid。

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

解決方案有下面幾種:

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

b. 對(duì)消息做冪等,已消費(fèi)過就不再消費(fèi)。但是這里跟普通的消息不同,會(huì)有N條消息,如果對(duì)msgId進(jìn)行存儲(chǔ),這樣就可以判斷是否消費(fèi)過,但是這樣存儲(chǔ)壓力太大,當(dāng)然也可以只存儲(chǔ)最近N條來減小存儲(chǔ)壓力。

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

???6、Job

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

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

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

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

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

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

???7、TOC

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

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

圖片

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

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

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

圖片

三、服務(wù)劃分

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

???1、整體方向

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

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

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

???2、服務(wù)類型

1)中心服務(wù)

中心服務(wù)只會(huì)在中心機(jī)房部署,并且數(shù)據(jù)庫(kù)也一定是中心庫(kù)。可以對(duì)整個(gè)應(yīng)用進(jìn)行打標(biāo)成中心,這樣外部訪問這個(gè)服務(wù)的接口時(shí)都會(huì)被路由到中心機(jī)房。

2)單元服務(wù)

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

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

圖片

3)中心單元服務(wù)

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

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

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

圖片

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

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

四、切流方案

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

圖片

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

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

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

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

3)反饋禁寫生效結(jié)果

當(dāng)配置變更后會(huì)推送到彩虹橋,配置中心會(huì)感知到配置推送的結(jié)果,然后將生效的結(jié)果反饋給雙活控制中心。

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

雙活控制中心收到所有的反饋后,會(huì)將全部生效的時(shí)間點(diǎn)通過MQ消息告訴Otter。

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

Otter收到消息會(huì)根據(jù)時(shí)間點(diǎn)進(jìn)行數(shù)據(jù)同步。

6)Otter同步完成反饋同步結(jié)果

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

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

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

五、總結(jié)

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

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

以上就是我們?cè)诙嗷罡脑熘械囊恍┙?jīng)驗(yàn),分享出來希望可以對(duì)正在閱讀的你有一些幫助。

責(zé)任編輯:張燕妮 來源: 得物技術(shù)
相關(guān)推薦

2023-11-28 07:45:48

Rust自動(dòng)化測(cè)試

2024-04-26 00:28:14

異地多活架構(gòu)

2018-05-24 10:04:16

Zookeeper異地原理

2020-11-20 09:23:01

高可用異地淘寶

2012-06-17 12:58:04

架構(gòu)師架構(gòu)

2023-11-27 07:57:46

2021-02-04 10:00:09

異地多中心容災(zāi)

2021-02-24 10:05:07

架構(gòu)運(yùn)維技術(shù)

2022-04-26 05:57:18

微服務(wù)Nacos

2021-11-04 06:58:31

Python開源特性

2021-04-23 09:55:27

技術(shù)開發(fā)實(shí)踐

2013-07-31 17:47:16

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

2022-01-10 08:17:40

異地設(shè)計(jì)實(shí)踐

2019-03-18 10:32:33

容災(zāi)雙活同城

2024-08-12 08:04:00

2020-06-28 08:34:07

架構(gòu)師阿里軟件

2019-07-24 09:22:45

Elasticsear數(shù)據(jù)Oracle

2019-05-30 09:32:49

2012-06-20 09:14:07

系統(tǒng)架構(gòu)運(yùn)維

2021-01-29 09:18:09

技術(shù)研發(fā)架構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 91亚洲欧美 | 91精品久久久久久久久久入口 | 自拍视频一区二区三区 | 国产精品久久久久永久免费观看 | 天天干天天玩天天操 | 国产精品欧美精品 | 亚洲视频免费在线看 | 国产亚洲精品综合一区 | 国产亚洲精品久久情网 | 美女久久视频 | 国产精品性做久久久久久 | 日韩欧美在线观看视频 | 精品国产伦一区二区三区观看说明 | 中文字幕1区 | 久草精品视频 | 日本综合在线观看 | 97国产精品 | 天天激情综合 | 91不卡| 中文字幕97 | 在线黄色网 | 亚洲午夜视频在线观看 | 久久久久久久久久久爱 | 国产精品国产精品国产专区不蜜 | 一区二区三区免费 | 欧美精品1区 | av网站免费在线观看 | 亚洲午夜视频在线观看 | 国产精品久久久久久久粉嫩 | 天堂网中文字幕在线观看 | 波多野结衣电影一区 | 久久免费福利 | 波多野结衣中文字幕一区二区三区 | 欧美成人a∨高清免费观看 欧美日韩中 | 国产一区二区在线视频 | 久久精品亚洲欧美日韩精品中文字幕 | 日本一区二区高清不卡 | 国产三级一区二区 | 欧美一区在线视频 | 91av精品| 国产精品a一区二区三区网址 |