硬核干貨 | 突破底層基礎(chǔ)架構(gòu)瓶頸,揭秘TDSQL存儲(chǔ)核心技術(shù)

來(lái)源: 騰訊云數(shù)據(jù)庫(kù)
作者:騰訊云數(shù)據(jù)庫(kù)
時(shí)間:2021-12-30
14631
TDSQL是騰訊面向企業(yè)級(jí)應(yīng)用場(chǎng)景的分布式數(shù)據(jù)庫(kù)產(chǎn)品,目前已在眾多金融、政務(wù)、電商、社交等客戶應(yīng)用案例中奠定金融級(jí)高可用、強(qiáng)一致、高性能的產(chǎn)品特性和口碑,幫助20余家金融機(jī)構(gòu)完成核心替換,有力推動(dòng)了國(guó)產(chǎn)數(shù)據(jù)庫(kù)的技術(shù)創(chuàng)新與發(fā)展。

TDSQL是騰訊面向企業(yè)級(jí)應(yīng)用場(chǎng)景的分布式數(shù)據(jù)庫(kù)產(chǎn)品,目前已在眾多金融、政務(wù)、電商、社交等客戶應(yīng)用案例中奠定金融級(jí)高可用、強(qiáng)一致、高性能的產(chǎn)品特性和口碑,幫助20余家金融機(jī)構(gòu)完成核心替換,有力推動(dòng)了國(guó)產(chǎn)數(shù)據(jù)庫(kù)的技術(shù)創(chuàng)新與發(fā)展。

日前,TDSQL新敏態(tài)引擎正式發(fā)布,高度適配金融敏態(tài)業(yè)務(wù)。該引擎可完美解決對(duì)于敏態(tài)業(yè)務(wù)發(fā)展過(guò)程中業(yè)務(wù)形態(tài)、業(yè)務(wù)量的不可預(yù)知性,實(shí)現(xiàn)PB級(jí)存儲(chǔ)的Online DDL,可以大幅提升表結(jié)構(gòu)變更過(guò)程中的數(shù)據(jù)庫(kù)吞吐量,有效應(yīng)對(duì)業(yè)務(wù)的變化;最關(guān)鍵的是,騰訊獨(dú)有的數(shù)據(jù)形態(tài)自動(dòng)感知特性,可以使數(shù)據(jù)能夠根據(jù)業(yè)務(wù)負(fù)載情況自動(dòng)遷移,打散熱點(diǎn),降低分布式事務(wù)比例,獲得極致的擴(kuò)展性和性能。

本期將由騰訊云數(shù)據(jù)庫(kù)專家工程師朱翀深度解讀TDSQL新敏態(tài)引擎存儲(chǔ)核心技術(shù)。以下是分享實(shí)錄:

TDSQL新敏態(tài)存儲(chǔ)引擎

TDSQL在銀行核心系統(tǒng)及常見(jiàn)業(yè)務(wù)上表現(xiàn)出優(yōu)秀性能和良好穩(wěn)定性,但在某些敏態(tài)業(yè)務(wù)中,其底層基礎(chǔ)架構(gòu)遭遇新的問(wèn)題。

640.webp (2).jpg

首先是兼容性的問(wèn)題。TDSQL的架構(gòu)包括計(jì)算層及分布式的存儲(chǔ)層。分布式存儲(chǔ)層中存在眾多DB,利用中間層即計(jì)算層,再通過(guò)hash的方式將數(shù)據(jù)分片,分別存放在不同的DB。這種方式在建表時(shí)會(huì)遇到兼容性問(wèn)題,需要指定shardkey才能將用戶產(chǎn)生的數(shù)據(jù)存放到指定DB上。面對(duì)經(jīng)常變化的敏態(tài)業(yè)務(wù),如果每次建表都要指定shardkey,當(dāng)業(yè)務(wù)變化時(shí),指定的shardkey在未來(lái)業(yè)務(wù)中就不可用,需要重新去分布數(shù)據(jù),整個(gè)流程將變得更繁瑣。

其次是運(yùn)維的問(wèn)題。在TDSQL中,后端的存儲(chǔ)節(jié)點(diǎn)是眾多DB,如果容量不夠則需要擴(kuò)容。DBA需要在前端發(fā)起操作,過(guò)程較為簡(jiǎn)單,但途中會(huì)有部分事務(wù)中斷。隨著敏態(tài)業(yè)務(wù)的發(fā)展,需要不停擴(kuò)容,擴(kuò)容過(guò)程中的事務(wù)中斷也會(huì)對(duì)敏態(tài)業(yè)務(wù)造成影響。

最后是模式變更的問(wèn)題。隨著業(yè)務(wù)的發(fā)展,敏態(tài)業(yè)務(wù)的表結(jié)構(gòu)也在變化,需要經(jīng)常加字段或加索引。在TDSQL中加索引等表結(jié)構(gòu)變更必須鎖表。如果想避免鎖表,就需要借助周邊生態(tài)工具。

640.png

基于上述問(wèn)題,我們研發(fā)了TDSQL新敏態(tài)存儲(chǔ)引擎架構(gòu)。考慮到敏態(tài)業(yè)務(wù)變化較大,我們希望在TDSQL新敏態(tài)存儲(chǔ)引擎架構(gòu)中,用戶可以像單機(jī)數(shù)據(jù)庫(kù)一樣去使用分布式數(shù)據(jù),不需要關(guān)注存儲(chǔ)變化,可以隨時(shí)加字段、建索引,業(yè)務(wù)完全無(wú)感知。

目前該引擎完全兼容MySQL,具備全局一致性,擴(kuò)縮容業(yè)務(wù)完全無(wú)感知,完全支持原生在線表結(jié)構(gòu)變更。與此前架構(gòu)最大的區(qū)別在于,該存儲(chǔ)引擎為分布式KV系統(tǒng),同時(shí)提供事務(wù)和自動(dòng)擴(kuò)縮容能力。在該引擎中,數(shù)據(jù)按范圍分片,分成一個(gè)個(gè)Region,Region內(nèi)部的數(shù)據(jù)有序排列。每個(gè)KV節(jié)點(diǎn)上有許多Region,每次擴(kuò)容時(shí)只需要將指定Region搬遷走即可。

TDSQL新敏態(tài)存儲(chǔ)引擎

技術(shù)挑戰(zhàn)

TDSQL新敏態(tài)存儲(chǔ)引擎中數(shù)據(jù)是如何存儲(chǔ)的以及SQL是如何執(zhí)行的呢?以下圖為例,t1表中有三個(gè)字段,分別是id、f1、f2,其中id是主鍵,f1是二級(jí)索引。在建t1表時(shí),計(jì)算層會(huì)為其獲取兩個(gè)索引id,假設(shè)主鍵的索引id為0x01,二級(jí)索引的索引id為0x02。當(dāng)我們?yōu)閠1表插入一行數(shù)據(jù)時(shí),insert into t1 value(1,3,3),計(jì)算層會(huì)把Key編碼成0x0101(16進(jìn)制表示法,下同,第一個(gè)字節(jié)0x01表示主鍵索引ID,第二個(gè)字節(jié)0x01表示主鍵值),value會(huì)被編碼成0x010303。因?yàn)樵摫泶嬖诙?jí)索引,所以插入一條主鍵Key還不夠,二級(jí)索引也要進(jìn)行編碼保存;二級(jí)索引的編碼中需要包含主鍵值的信息,故將其Key編碼為0x020301(第一個(gè)字節(jié)0x02表示二級(jí)索引ID,第二個(gè)字節(jié)0x03表示二級(jí)索引值,第三個(gè)字節(jié)0x01表示主鍵值),因?yàn)镵ey中已經(jīng)包含了所有需要的信息,所以二級(jí)索引的value是空值。

當(dāng)我們?yōu)閠1表再插入一行數(shù)據(jù)insert into t1 value(2,3,2)時(shí),是同樣的過(guò)程,這里不再贅述,這條數(shù)據(jù)會(huì)被編碼成主鍵Key-value對(duì)即0x0102-0x020302,和二級(jí)索引Key-value對(duì)即0x020302-null。

640.webp (3).jpg

假設(shè)后端有兩個(gè)敏態(tài)引擎存儲(chǔ)節(jié)點(diǎn)即TDStore,第一個(gè)TDStore上Region的范圍為0x01-0x02,這樣兩個(gè)記錄的主鍵就存儲(chǔ)在TDStore1上。第二個(gè)TDStore上的Region的范圍是0x02-0x03,這兩個(gè)值的二級(jí)索引存儲(chǔ)在TDStore2上。計(jì)算層收到客戶端發(fā)過(guò)來(lái)的查詢語(yǔ)句select*from t1 where id=2時(shí),經(jīng)過(guò)sql parse、bind等一系列工作之后,知道這條語(yǔ)句查詢的是表t主鍵值為2的數(shù)據(jù)。表t的主鍵索引ID為0x01,于是計(jì)算層編碼查詢Key為0x0102,計(jì)算層再根據(jù)路由表可知該值在TDStore1上,于是通過(guò)RPC將值從TDStore1上讀取出來(lái),該值value為0x020302,再將其反編碼成(2,3,2)返回給客戶端。

接著計(jì)算層收到客戶端發(fā)過(guò)來(lái)的第二條查詢語(yǔ)句select*from t1 where f1=3,計(jì)算層同樣經(jīng)過(guò)sql parse、bind等一系列工作之后,知道這條語(yǔ)句查詢的是表t二級(jí)索引字段為3的數(shù)據(jù),表t的二級(jí)索引ID為0x02,這樣計(jì)算層可以組合出Key:0x0203,利用前綴掃描,計(jì)算層從TDStore2中得到兩條數(shù)據(jù)0x020301,0x020302。這意味著f1=3有兩條記錄主鍵值分別為1和2,但是此時(shí)還沒(méi)有獲取到f3這個(gè)列的值,需要根據(jù)主鍵值再次編碼去獲取相應(yīng)記錄的全部信息(這個(gè)過(guò)程我們也稱之為回表)。

經(jīng)過(guò)上面的過(guò)程,我們可以看到當(dāng)往t表中插入一行記錄時(shí),TDSQL新敏態(tài)引擎會(huì)產(chǎn)生兩個(gè)Key,這兩個(gè)Key還可能會(huì)存放在不同的TDStore上。這時(shí)我們就會(huì)遇到事務(wù)原子性的問(wèn)題。例如我們可能會(huì)遇到這樣一種場(chǎng)景:插入第一個(gè)Key成功了,但在插入第二個(gè)Key過(guò)程中,第二個(gè)Key所在的節(jié)點(diǎn)故障了。如果沒(méi)有處理好可能就會(huì)出現(xiàn)第一個(gè)Key保存成功,而第二個(gè)Key丟失的情況,這種情況是不允許出現(xiàn)的。所以TDSQL新敏態(tài)引擎要保證一次事務(wù)涉及的數(shù)據(jù)要么全部插入成功、要么全部插入失敗。

640.webp (4).jpg

TDSQL新敏態(tài)引擎面臨的另一個(gè)問(wèn)題是事務(wù)的并發(fā)處理。如上圖所示:TDSQL新敏態(tài)引擎支持多計(jì)算層節(jié)點(diǎn)寫入,因此可能會(huì)出現(xiàn)兩個(gè)客戶端連上兩個(gè)不同的計(jì)算層節(jié)點(diǎn)同時(shí)寫入同一個(gè)主鍵值。我們知道記錄插入時(shí)首先要判定主鍵的唯一性,因此在收到insert語(yǔ)句時(shí)計(jì)算層節(jié)點(diǎn)SQLEngine會(huì)在存儲(chǔ)節(jié)點(diǎn)TDStore上根據(jù)主鍵Key讀取數(shù)據(jù),看其是否存在,在上圖中主鍵Key編碼為0x0103,兩個(gè)SQLEngine都同時(shí)發(fā)現(xiàn)在TDStore上Key:0x0103并不存在,于是都將Key:0x0103發(fā)到TDStore上要求將其寫入,但它們對(duì)應(yīng)的value又不相同,最終要保留哪條記錄呢?這就成為了問(wèn)題。

TDSQL新敏態(tài)引擎還面臨另一個(gè)問(wèn)題,就是如何保證數(shù)據(jù)調(diào)度過(guò)程中事務(wù)不受影響。如下圖所示,假設(shè)此時(shí)DBA正在導(dǎo)入大量數(shù)據(jù),TDSQL新敏態(tài)引擎發(fā)現(xiàn)存儲(chǔ)節(jié)點(diǎn)存儲(chǔ)空間不夠,于是決定擴(kuò)容,將部分?jǐn)?shù)據(jù)搬遷到空閑機(jī)器上。搬遷過(guò)程中,要屏蔽影響,保證導(dǎo)入數(shù)據(jù)的事務(wù)不中斷。

640.webp (5).jpg

綜上所述,TDSQL新敏態(tài)存儲(chǔ)引擎要解決三方面的挑戰(zhàn):

·事務(wù)原子性。一個(gè)事務(wù)涉及到的數(shù)據(jù)可能分布在多個(gè)存儲(chǔ)節(jié)點(diǎn)上,必須保證該事務(wù)涉及到的所有修改全部成功或全部失敗。

·事務(wù)并發(fā)控制。并發(fā)事務(wù)之間不能出現(xiàn)臟讀(事務(wù)A讀到了事務(wù)B未提交的數(shù)據(jù))、臟寫(事務(wù)A和事務(wù)B同時(shí)基于某個(gè)相同的數(shù)據(jù)版本寫入不同的值,一個(gè)覆蓋另一個(gè))。

·數(shù)據(jù)調(diào)度時(shí)不殺事務(wù)。新敏態(tài)存儲(chǔ)引擎的重要設(shè)計(jì)目標(biāo)之一,是讓業(yè)務(wù)在敏態(tài)變化中無(wú)感知,因此要確保在數(shù)據(jù)搬遷時(shí),不影響事務(wù)的正常進(jìn)行。

640.webp.jpg

事務(wù)原子性

解決事務(wù)原子性問(wèn)題的經(jīng)典方法是兩階段提交。如果我們讓計(jì)算層節(jié)點(diǎn)SQLEngine作為兩階段提交的協(xié)調(diào)者,那么當(dāng)一個(gè)事務(wù)提交時(shí),SQLEngine需要先寫prepare日志,再發(fā)送prepare請(qǐng)求給存儲(chǔ)節(jié)點(diǎn)TDStore,如果prepare都成功了,再寫commit日志,發(fā)送commit請(qǐng)求。一旦SQLEngine節(jié)點(diǎn)發(fā)生了故障,只要能夠恢復(fù),就可以從日志中讀取出當(dāng)前有哪些懸掛事務(wù),然后根據(jù)其對(duì)應(yīng)的階段繼續(xù)推動(dòng)兩階段事務(wù)。但是如果SQLEngine發(fā)生了永久性故障,無(wú)法恢復(fù),那么日志就會(huì)丟失,就無(wú)從得知有哪些懸掛事務(wù),也就永遠(yuǎn)無(wú)法繼續(xù)推進(jìn)懸掛事務(wù)。在TDSQL新敏態(tài)存儲(chǔ)引擎設(shè)計(jì)目標(biāo)里,要求計(jì)算層SQLEngine節(jié)點(diǎn)可以隨時(shí)增減和替換,也要求SQLEngine節(jié)點(diǎn)能夠隨時(shí)承受永久性故障。所以經(jīng)典的兩階段提交方法不可取。

640.webp (1).jpg

經(jīng)典的兩階段提交方法不可取的主要原因是本地日志可能會(huì)丟失,我們可以對(duì)經(jīng)典的方案進(jìn)行改進(jìn),將日志放在存儲(chǔ)層節(jié)點(diǎn)TDStore中。因?yàn)榇鎯?chǔ)層是基于raft多副本的,這樣就能夠在不出現(xiàn)多數(shù)派節(jié)點(diǎn)永久故障的情況下,保證日志的安全。但這種做法帶來(lái)的壞處是網(wǎng)絡(luò)層次太多,首先兩階段的日志先發(fā)送到存儲(chǔ)層TDStore的Leader,再同步到TDStore的Follow,然后才能進(jìn)行真正的兩階段請(qǐng)求。除了延遲高,這個(gè)方案還存在故障后懸掛事務(wù)恢復(fù)慢的缺點(diǎn)。比如當(dāng)一個(gè)計(jì)算層SQLEngine節(jié)點(diǎn)發(fā)生了永久性故障,就需要另一個(gè)SQLEngine節(jié)點(diǎn)感知到這件事情,然后才能繼續(xù)推進(jìn)涉及的懸掛事務(wù)。感知SQLEngine節(jié)點(diǎn)存活問(wèn)題,往往會(huì)歸納成心跳超時(shí)的問(wèn)題。因?yàn)橐乐惯M(jìn)程夯住假死等問(wèn)題,超時(shí)一般不能設(shè)置的太短,這里的設(shè)計(jì)就導(dǎo)致了一個(gè)計(jì)算層SQLEngine節(jié)點(diǎn)故障后,需要較長(zhǎng)時(shí)間其涉及的懸掛事務(wù)才能被其它節(jié)點(diǎn)接管,恢復(fù)起來(lái)很慢。

640.webp (2).jpg

最終我們采用了協(xié)調(diào)者下沉到存儲(chǔ)節(jié)點(diǎn)的方法來(lái)解決分布式原子性事務(wù)。因?yàn)榇鎯?chǔ)節(jié)點(diǎn)本身使用了raft協(xié)議保證多數(shù)派一致性,不存在單點(diǎn)問(wèn)題。只要選一個(gè)存儲(chǔ)節(jié)點(diǎn)的參與者作為協(xié)調(diào)者,將參與者的列表信息包含在參與者日志一起提交。這樣當(dāng)故障發(fā)生時(shí),就可以利用日志恢復(fù)raft狀態(tài)機(jī)的方式,將協(xié)調(diào)者也恢復(fù)出來(lái)。這樣的好處是網(wǎng)絡(luò)層次相對(duì)較少,提交延遲較低,同時(shí)故障恢復(fù)也比較確定。

640.webp (3).jpg

分布式事務(wù)并發(fā)控制

接下來(lái)我們一起看下,TDSQL新敏態(tài)存儲(chǔ)引擎是如何解決分布式事務(wù)并發(fā)控制的。

我們首先構(gòu)造了以下規(guī)則:

1.數(shù)據(jù)存儲(chǔ)是基于時(shí)間戳的數(shù)據(jù)多版本,以下圖中左下方的表為例,數(shù)據(jù)有多個(gè)版本,每個(gè)版本都會(huì)有一個(gè)時(shí)間戳。比如數(shù)據(jù)Key:A有三個(gè)版本,它的時(shí)間戳分別為1、3、5,對(duì)應(yīng)的值也不同。

2.TDMetaCluster模塊提供全局邏輯時(shí)間戳服務(wù),保證邏輯時(shí)間戳在全局單調(diào)遞增。

640.webp (4).jpg

3.事務(wù)開(kāi)始時(shí)會(huì)從時(shí)間戳服務(wù)模塊獲取一個(gè)時(shí)間戳,我們稱之為start_ts。事務(wù)讀取指定Key的value時(shí),讀取的是從數(shù)據(jù)存儲(chǔ)中第一個(gè)小于等于start_ts的key value(上圖例子中是從下往上讀,因?yàn)閳D例中的新數(shù)據(jù)在下面)。

4.事務(wù)未提交前的寫入都在內(nèi)存中(我們稱之為事務(wù)私有空間),只有事務(wù)提交時(shí)才寫入數(shù)據(jù)存儲(chǔ)里對(duì)其他事務(wù)可見(jiàn)。

5.事務(wù)提交前需要再獲取一個(gè)時(shí)間戳,我們稱之為commit_ts。事務(wù)提交時(shí)寫入數(shù)據(jù)存儲(chǔ)中的數(shù)據(jù)項(xiàng)需要包含這個(gè)時(shí)間戳。

舉個(gè)例子,見(jiàn)上圖右側(cè)的事務(wù)執(zhí)行空間,假設(shè)正在執(zhí)行一條update A=A+5的SQL,它需要先從存儲(chǔ)中g(shù)et A的值,再對(duì)值進(jìn)行+5操作,最后把+5的結(jié)果寫回存儲(chǔ)中。從圖中可以看到事務(wù)拿到的start_ts為4,當(dāng)事務(wù)去數(shù)據(jù)存儲(chǔ)中讀取A的值的時(shí)候,讀取到的值是10,原因是A的多個(gè)版本中時(shí)間戳3是第一個(gè)小于等于該事務(wù)start_ts的版本,因此要讀到時(shí)間戳3這個(gè)版本,讀到的值為10。拿到A=10后,事務(wù)對(duì)10進(jìn)行+5操作,把結(jié)果15暫時(shí)保存在自己的私有空間中,再獲取commit_ts為5,最后再把A=15寫回到數(shù)據(jù)存儲(chǔ)中,此時(shí)數(shù)據(jù)存儲(chǔ)中多了一條A的版本,該版本為5,值為15。

從上述過(guò)程中我們可以看出,我們當(dāng)前定義的幾條規(guī)則很自然地解決了臟讀問(wèn)題,原因是未提交的事務(wù)寫入的數(shù)據(jù)都暫存在其私有內(nèi)存中,對(duì)其他事務(wù)都不可見(jiàn),如果該事務(wù)回滾了我們只需要將其在私有內(nèi)存中的數(shù)據(jù)釋放掉,期間不會(huì)對(duì)數(shù)據(jù)存儲(chǔ)產(chǎn)生任何影響。

盡管上述規(guī)則定義了事務(wù)讀寫的方式,也解決了臟讀問(wèn)題,但是僅有這幾條規(guī)則還是不夠,我們可以看看下圖這個(gè)問(wèn)題。

640.webp (5).jpg

這是一個(gè)常見(jiàn)的數(shù)據(jù)并發(fā)更新的場(chǎng)景。假設(shè)有兩個(gè)客戶端在同時(shí)執(zhí)行update A=A+5的操作,對(duì)于數(shù)據(jù)庫(kù)來(lái)說(shuō)就產(chǎn)生了兩個(gè)并發(fā)的更新事務(wù)T1、T2。假設(shè)這兩個(gè)事務(wù)的執(zhí)行順序如上圖所示,T2先拿到start_ts:4,把A時(shí)間戳為3的版本value=10讀取出來(lái)了。事務(wù)T1同時(shí)進(jìn)行,它拿到的start ts:5,也把A事務(wù)戳為3的版本value=10讀取出來(lái)。隨后它們都對(duì)10加5,得到A=15的新結(jié)果,暫存于各自的私有內(nèi)存中。事務(wù)T2再去拿commit_ts:6,再將A=15寫回?cái)?shù)據(jù)存儲(chǔ)中。事務(wù)T1也拿到了commit_ts:7,再把A=15寫回?cái)?shù)據(jù)存儲(chǔ)。最終會(huì)產(chǎn)生兩個(gè)A的新版本,但是其value都等于15。這樣相當(dāng)于數(shù)據(jù)庫(kù)執(zhí)行了兩次update A=A+5,并且都返回客戶端成功,但是最終A的值只增加了一個(gè)5,相當(dāng)于其中一個(gè)更新操作丟失了。

為什么會(huì)這樣呢?我們回顧上述過(guò)程會(huì)發(fā)現(xiàn)T2的值被T1錯(cuò)誤地覆蓋了:T1讀取到了T2更新前的值,然后覆蓋了T2更新后的值。因此要想得到正確的結(jié)果有兩個(gè)方法,要么T1應(yīng)該讀取到T2更新后的值再去覆蓋T2更新后的值,要么T1在獲取到T2更新前的值的基礎(chǔ)上去覆蓋T2更新后的值時(shí)應(yīng)該失敗。(方法1是悲觀事務(wù)模型,方法2是樂(lè)觀事務(wù)模型)

在TDSQL新敏態(tài)引擎中,我們采用了方法2,引入了沖突檢測(cè)的規(guī)則,當(dāng)然以后我們也會(huì)支持方法1。

640.webp.jpg

怎么保證T1在獲取到T2更新前的值再去覆蓋T2更新后的值時(shí)應(yīng)該失敗呢,我們引入了一個(gè)新的規(guī)則:事務(wù)在提交前需要做一次沖突檢測(cè)。沖突檢測(cè)的具體過(guò)程為:按照前述執(zhí)行順序,在獲取commit_ts前,讀取本事務(wù)所有更新數(shù)據(jù)項(xiàng)在數(shù)據(jù)存儲(chǔ)中的最新的版本對(duì)應(yīng)的時(shí)間戳,將其與本事務(wù)的start_ts比較,如果數(shù)據(jù)版本對(duì)應(yīng)的timestamp小于start _ts才允許提交,否則應(yīng)失敗回滾。

在上圖例子中,當(dāng)事務(wù)T2提交前做沖突檢測(cè)時(shí),會(huì)再次讀取數(shù)據(jù)項(xiàng)A最新的版本timestamp=3,小于事務(wù)T2的start_ts:4,于是事務(wù)T2進(jìn)行后續(xù)流程,將更新數(shù)據(jù)成功提交。但是當(dāng)事務(wù)T1執(zhí)行沖突檢測(cè)時(shí),再次讀取數(shù)據(jù)項(xiàng)A最新版本時(shí)其已經(jīng)變成timestamp=6,大于它的start_ts:5,這說(shuō)明數(shù)據(jù)項(xiàng)A在事務(wù)T1執(zhí)行期間被其它事務(wù)并發(fā)修改過(guò),這里已經(jīng)產(chǎn)生了事務(wù)沖突,于是事務(wù)T1需要回滾掉。

通過(guò)引入新的規(guī)則:事務(wù)在提交前需要做一次沖突檢測(cè),我們似乎看起來(lái)解決了臟寫的問(wèn)題,但是真正的解決了嗎?上圖的示例中我們給出了一種并發(fā)調(diào)度的可能,這個(gè)調(diào)度就是下圖的左上角的情況,通過(guò)沖突檢測(cè)確實(shí)可以解決問(wèn)題。但是還存在另一種可能的并行調(diào)度。兩個(gè)事務(wù)在client端同時(shí)commit,這個(gè)調(diào)度在數(shù)據(jù)庫(kù)層可能會(huì)同時(shí)做沖突檢測(cè)(兩個(gè)不同的執(zhí)行線程),然后沖突檢測(cè)都判定成功,最終都成功提交,這樣相當(dāng)于又產(chǎn)生了臟寫。

640.webp (1).jpg

這個(gè)問(wèn)題其實(shí)可以用另一種可能的調(diào)度去解決。雖然client同時(shí)commit,但是在數(shù)據(jù)庫(kù)層事務(wù)T2提交完之后事務(wù)T1才開(kāi)始進(jìn)行,這樣事務(wù)T1就能檢測(cè)到A的最新版本發(fā)生的變化,于是進(jìn)入回滾。這種調(diào)度意味著事務(wù)提交在數(shù)據(jù)項(xiàng)上要原子串行化,在單節(jié)點(diǎn)情況下(或者簡(jiǎn)單的主備同步)這種操作是可行的。但在分布式事務(wù)的前提下,獲取時(shí)間戳需要網(wǎng)絡(luò)交互,如果仍然采用這種串行化操作,事務(wù)并發(fā)無(wú)法提高,延遲會(huì)非常大。

除了這個(gè)問(wèn)題,分布式場(chǎng)景也給事務(wù)并發(fā)控制帶來(lái)一些新的挑戰(zhàn)——當(dāng)事務(wù)涉及到多個(gè)節(jié)點(diǎn)時(shí)要如何統(tǒng)一所有節(jié)點(diǎn)的時(shí)序,從而保證一致性讀?(這里的一致性讀指的是:一個(gè)事務(wù)的修改要么被另一個(gè)事務(wù)全部看到,要么全不被看到)

以下圖為例,我們?cè)敿?xì)闡述一下一致性讀問(wèn)題。在下圖中A、B兩個(gè)賬戶分別存儲(chǔ)在兩個(gè)不同的存儲(chǔ)節(jié)點(diǎn)上;事務(wù)T1是轉(zhuǎn)賬事務(wù),從A賬戶中轉(zhuǎn)5元到B賬戶,在T1執(zhí)行完所有流程正在提交時(shí),查總賬事務(wù)T2開(kāi)啟,其要查詢A、B兩個(gè)賬戶的總余額。這時(shí)可能會(huì)出現(xiàn)下面這個(gè)執(zhí)行流程:事務(wù)T1將A=5元提交到存儲(chǔ)節(jié)點(diǎn)1上時(shí),事務(wù)T2在存儲(chǔ)節(jié)點(diǎn)2上讀取到了B=10元,然后事務(wù)T1再把B=15元提交到存儲(chǔ)節(jié)點(diǎn)2上,最后事務(wù)T2再去存儲(chǔ)節(jié)點(diǎn)1上讀取A=5元。最終的結(jié)果是雖然事務(wù)T1執(zhí)行前后總余額都是20,但是事務(wù)T2查詢到的總余額卻等于15,少了5元。

640.webp (2).jpg

我們的分布式事務(wù)并發(fā)控制模型除了要解決上述問(wèn)題,還需要考慮一個(gè)非常重要的點(diǎn):如何與分布式事務(wù)原子性解決方案2pc結(jié)合。

最終我們給出了下圖所示處理模型:

首先,我們將兩階段提交與樂(lè)觀事務(wù)模型相結(jié)合,在事務(wù)提交時(shí)先進(jìn)入prepare階段,進(jìn)行寫寫沖突檢測(cè)。這樣做的原因是保證兩階段提交中,如果prepare成功,commit就必定要成功的承諾。

640.webp (3).jpg

其次,我們引入prepare lock map來(lái)進(jìn)行活躍并發(fā)事務(wù)的沖突檢測(cè),而原本的沖突檢測(cè)流程繼續(xù)保留,負(fù)責(zé)已提交事務(wù)的沖突檢測(cè)。這樣我們就把沖突檢測(cè)與數(shù)據(jù)寫入解綁,不再需要這里進(jìn)行原子串行化,提高了事務(wù)并發(fā)的能力。具體到事務(wù)執(zhí)行流程里面就是在prepare階段需要將對(duì)應(yīng)的更新數(shù)據(jù)項(xiàng)的key插入到prepare lock中,如果發(fā)現(xiàn)對(duì)應(yīng)Key已經(jīng)存在,說(shuō)明存在并發(fā)活躍的事務(wù)沖突,如果對(duì)應(yīng)更新數(shù)據(jù)項(xiàng)插入全部成功,說(shuō)明prepare執(zhí)行成功。

最后,在事務(wù)執(zhí)行讀取操作時(shí)還需要根據(jù)讀取的Key查詢prepare lock map。如果事務(wù)的start_ts大于在prepare map中查詢到的lock項(xiàng)的prepare ts,就必須等到lock釋放后才能去數(shù)據(jù)存儲(chǔ)中讀取Key對(duì)應(yīng)的數(shù)據(jù)。這里包含的原理是:已提交事務(wù)的commit_ts和讀取事務(wù)的start_ts決定了數(shù)據(jù)項(xiàng)的可見(jiàn)性,當(dāng)讀取事務(wù)的start_ts大于prepare map中查詢到的lock項(xiàng)的prepare ts時(shí),意味著有一個(gè)事務(wù)其commit_ts可能小于讀取事務(wù)start_ts正在提交,讀取事務(wù)需要等待其提交成功之后才能執(zhí)行讀取操作,否則有可能會(huì)漏掉要讀取數(shù)據(jù)項(xiàng)的最新版本。

有了這些新規(guī)則,我們?cè)倩氐缴厦嬉恢滦宰x的例子中,如下圖所示,事務(wù)T2在存儲(chǔ)節(jié)點(diǎn)2上面的讀取需要延遲到事務(wù)T1將B=15提交到數(shù)據(jù)存儲(chǔ)后才可以執(zhí)行,這樣就保證讀到的是B最新的版本15元,然后再去存儲(chǔ)節(jié)點(diǎn)1上將A=5元讀取出來(lái),這樣最后的總余額才是準(zhǔn)確的。

640.webp (4).jpg

數(shù)據(jù)調(diào)度不殺事務(wù)

在TDSQL敏態(tài)存儲(chǔ)引擎中,數(shù)據(jù)分段管理在Region中,數(shù)據(jù)調(diào)度通過(guò)Region調(diào)度實(shí)現(xiàn)。Region調(diào)度又可分為分裂、遷移和切主。

640.webp (5).jpg

首先我們看一下Region的分裂,以下圖為例,假設(shè)數(shù)據(jù)在不停寫入,寫入的數(shù)據(jù)并不是完全均勻的,出現(xiàn)了某個(gè)Region比較大的情況,我們不能放任這個(gè)Region一直增大下去,于是我們?cè)谠揜egion中找到一個(gè)合適的分裂點(diǎn),將其一分為二。在下圖中,Region1分裂完后,原本每個(gè)存儲(chǔ)節(jié)點(diǎn)三個(gè)Region變成每個(gè)存儲(chǔ)節(jié)點(diǎn)四個(gè)Region。

640.webp (6).jpg

我們繼續(xù)前面的示例,寫入數(shù)據(jù)一直源源不斷,存儲(chǔ)節(jié)點(diǎn)的磁盤空間即將不足,于是我們?cè)黾恿艘粋€(gè)存儲(chǔ)節(jié)點(diǎn),并且開(kāi)始遷移數(shù)據(jù)到新節(jié)點(diǎn)上。數(shù)據(jù)遷移則是通過(guò)增減副本的方式進(jìn)行,假設(shè)我們選定了Region2做遷移,那么我們先在存儲(chǔ)節(jié)點(diǎn)4上增加Region2的副本,然后再到存儲(chǔ)節(jié)點(diǎn)1上將Region2的副本移除,這樣就相當(dāng)于Region2對(duì)應(yīng)的數(shù)據(jù)從存儲(chǔ)節(jié)點(diǎn)1遷移到存儲(chǔ)節(jié)點(diǎn)4。依次選擇不同Region重復(fù)這個(gè)過(guò)程,最終實(shí)現(xiàn)效果如下圖所示——從每一個(gè)存儲(chǔ)節(jié)點(diǎn)上都遷移了部分?jǐn)?shù)據(jù)到新存儲(chǔ)節(jié)點(diǎn)上。

640.webp.jpg

僅僅只是執(zhí)行副本遷移的操作會(huì)遇到leader不均衡的問(wèn)題,此時(shí)還需要輔助主動(dòng)切主的操作,來(lái)實(shí)現(xiàn)leader數(shù)目動(dòng)態(tài)平衡。

在實(shí)際應(yīng)用場(chǎng)景中,業(yè)務(wù)的需求是:不論數(shù)據(jù)如何調(diào)度和動(dòng)態(tài)均衡,服務(wù)不能中斷。在上面介紹的Region調(diào)度過(guò)程中,Region遷移是通過(guò)raft增減副本的方式進(jìn)行,與提供服務(wù)的leader無(wú)直接關(guān)系,不會(huì)影響到業(yè)務(wù)。但分裂和切主都在leader節(jié)點(diǎn)上執(zhí)行,不可避免地會(huì)存在與事務(wù)并發(fā)執(zhí)行的問(wèn)題,要想保證業(yè)務(wù)服務(wù)不受Region調(diào)度的影響,其實(shí)就是要保證事務(wù)不受Region的影響,這其中最關(guān)鍵的是要讓事務(wù)的生命周期跨越分裂和切主。

640.webp (1).jpg

我們看看上圖的示例:在磁盤上存儲(chǔ)著A和H的值分別為A=10、H=2,有一個(gè)事務(wù)T,其執(zhí)行過(guò)程應(yīng)該是先put A=1、put H=5,然后再Get H的值,最后再提交。假設(shè)該事務(wù)在執(zhí)行過(guò)程中Region發(fā)生了分裂,分裂的時(shí)機(jī)在Put H=5之后,Get H之前;同時(shí)Region的分裂點(diǎn)為G。在把磁盤上的數(shù)據(jù)遷移過(guò)去后,我們會(huì)發(fā)現(xiàn)在磁盤上Region1有A=10,而新的Region2上有H=2。當(dāng)事務(wù)繼續(xù)執(zhí)行Get H時(shí),根據(jù)最新的路由關(guān)系,它應(yīng)該需要在Region2上去讀取最新的值,此時(shí)如果我們沒(méi)有其它規(guī)則的保證,就會(huì)讀到H=2,這就產(chǎn)生了問(wèn)題:該事務(wù)剛剛寫了的數(shù)據(jù)似乎丟了。為了解決這個(gè)問(wèn)題,需要將Region上的活躍事務(wù)的私有數(shù)據(jù)在分裂時(shí)遷移到new Region上,這樣在上面例子中事務(wù)在執(zhí)行g(shù)et H時(shí)讀到的最新值為5。

640.webp (2).jpg

上述例子中事務(wù)還有一種可能的執(zhí)行流(如下圖所示):不進(jìn)行g(shù)et H操作,而是做完兩次put操作后直接提交;并且分裂時(shí)機(jī)在Put H之后,commit之前。由于沒(méi)有執(zhí)行過(guò)Get H,計(jì)算層只感知到該事務(wù)只有Region1參與,于是在執(zhí)行commit時(shí),計(jì)算層就會(huì)只提交Region1上的數(shù)據(jù),導(dǎo)致Region2上的數(shù)據(jù)沒(méi)有提交,破壞了事務(wù)的原子性。所以我們還需要額外的規(guī)則來(lái)保證在提交事務(wù)時(shí)感知到Region的分裂,保證事務(wù)的原子性。

640.webp (3).jpg

具體過(guò)程如下圖中的時(shí)序圖所示。假設(shè)最初只涉及到兩個(gè)Region,計(jì)算層在提交時(shí)會(huì)將參與者列表告訴協(xié)調(diào)者,協(xié)調(diào)者會(huì)在Region1和Region2上做prepare。假設(shè)Region2經(jīng)歷一次分裂,分裂出的新的Region3,當(dāng)收到prepare請(qǐng)求時(shí),Region2發(fā)現(xiàn)協(xié)調(diào)者包含的region列表中沒(méi)有新Region3,于是跟協(xié)調(diào)者說(shuō)明分裂情況。協(xié)調(diào)者感知到Region2的分裂后,會(huì)重新補(bǔ)齊參與者列表,再次發(fā)起一輪prepare,從而保證了事務(wù)的原子性。

640.webp (4).jpg

還有一種情況,當(dāng)事務(wù)提交時(shí),Region正在分裂,處于數(shù)據(jù)遷移過(guò)程中。這時(shí)Region2會(huì)告訴協(xié)調(diào)者,說(shuō)明自身狀態(tài)正處在分裂過(guò)程中。協(xié)調(diào)者會(huì)等待一段時(shí)間后再去重試。通過(guò)重試協(xié)調(diào)者最終可以知道這次分裂是否成功,如果成功新的參與者是誰(shuí),然后協(xié)調(diào)者就可以將參與者列表補(bǔ)齊,最終提交事務(wù)。

640.webp (5).jpg

結(jié)語(yǔ)

作為騰訊企業(yè)級(jí)分布式數(shù)據(jù)庫(kù)產(chǎn)品TDSQL的又一突破,TDSQL新敏態(tài)引擎高度適配金融敏態(tài)業(yè)務(wù),完美解決對(duì)于敏態(tài)業(yè)務(wù)發(fā)展過(guò)程中業(yè)務(wù)形態(tài)、業(yè)務(wù)量的不可預(yù)知性。

在突破原有底層基礎(chǔ)架構(gòu)瓶頸的基礎(chǔ)上,TDSQL新敏態(tài)引擎采用協(xié)調(diào)者下沉方法解決分布式事務(wù)原子性問(wèn)題,保證事務(wù)涉及到的所有修改全部成功或全部失敗;采用樂(lè)觀事務(wù)模型,引入沖突檢測(cè)環(huán)節(jié),解決分布式事務(wù)并發(fā)控制問(wèn)題;通過(guò)raft增減副本方式實(shí)現(xiàn)數(shù)據(jù)遷移,同時(shí)保證事務(wù)周期跨越分裂和切主,實(shí)現(xiàn)數(shù)據(jù)調(diào)度不殺事務(wù)。

未來(lái)TDSQL將持續(xù)推動(dòng)技術(shù)創(chuàng)新,釋放領(lǐng)先的技術(shù)紅利,繼續(xù)推動(dòng)國(guó)產(chǎn)數(shù)據(jù)庫(kù)的技術(shù)創(chuàng)新與發(fā)展,幫助更多行業(yè)客戶實(shí)現(xiàn)數(shù)據(jù)庫(kù)國(guó)產(chǎn)化替換。

立即登錄,閱讀全文
版權(quán)說(shuō)明:
本文內(nèi)容來(lái)自于騰訊云數(shù)據(jù)庫(kù),本站不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。文章內(nèi)容系作者個(gè)人觀點(diǎn),不代表快出海對(duì)觀點(diǎn)贊同或支持。如有侵權(quán),請(qǐng)聯(lián)系管理員(zzx@kchuhai.com)刪除!
相關(guān)文章
騰訊云數(shù)據(jù)庫(kù)PostgreSQL全面支持PG 17
騰訊云數(shù)據(jù)庫(kù)PostgreSQL全面支持PG 17
即日起,騰訊云PostgreSQL全面支持PostgreSQL 17.0。所有用戶可使用大版本升級(jí)能力升級(jí)至最新的PostgreSQL 17.0進(jìn)行體驗(yàn),也可以在產(chǎn)品購(gòu)買頁(yè)直接購(gòu)買。
騰訊云
云服務(wù)
2024-12-152024-12-15
高可用這個(gè)問(wèn)題,加機(jī)器就能解決?
高可用這個(gè)問(wèn)題,加機(jī)器就能解決?
互聯(lián)網(wǎng)服務(wù)的可用性問(wèn)題是困擾企業(yè)IT人員的達(dá)摩克利斯之劍:防于未然,體現(xiàn)不出價(jià)值。已然發(fā)生,又面臨P0危機(jī)。就更別提穩(wěn)定性建設(shè)背后顯性的IT預(yù)算問(wèn)題與隱性的人員成本問(wèn)題。
騰訊云
云服務(wù)
2024-11-252024-11-25
TDSQL TDStore引擎版替換HBase:在歷史庫(kù)場(chǎng)景中的成本與性能優(yōu)勢(shì)
TDSQL TDStore引擎版替換HBase:在歷史庫(kù)場(chǎng)景中的成本與性能優(yōu)勢(shì)
HBase憑借其高可用性、高擴(kuò)展性和強(qiáng)一致性,以及在廉價(jià)PC服務(wù)器上的低部署成本,廣泛應(yīng)用于大規(guī)模數(shù)據(jù)分析。
騰訊云
云服務(wù)
2024-11-042024-11-04
復(fù)雜查詢性能弱,只讀分析引擎來(lái)幫忙
復(fù)雜查詢性能弱,只讀分析引擎來(lái)幫忙
隨著當(dāng)今業(yè)務(wù)的高速發(fā)展,復(fù)雜多表關(guān)聯(lián)的場(chǎng)景越來(lái)越普遍。但基于行式存儲(chǔ)的數(shù)據(jù)庫(kù)在進(jìn)行復(fù)雜查詢時(shí)性能相對(duì)較弱。
騰訊云
云服務(wù)
2024-11-022024-11-02
掃碼登錄
打開(kāi)掃一掃, 關(guān)注公眾號(hào)后即可登錄/注冊(cè)
加載中
二維碼已失效 請(qǐng)重試
刷新
賬號(hào)登錄/注冊(cè)
個(gè)人VIP
小程序
快出海小程序
公眾號(hào)
快出海公眾號(hào)
商務(wù)合作
商務(wù)合作
投稿采訪
投稿采訪
出海管家
出海管家