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

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

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

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

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

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

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

640.webp (2).jpg

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

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

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

640.png

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

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

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

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

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

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

640.webp (3).jpg

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

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

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

640.webp (4).jpg

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

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

640.webp (5).jpg

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

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

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

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

640.webp.jpg

事務(wù)原子性

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

640.webp (1).jpg

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

640.webp (2).jpg

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

640.webp (3).jpg

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

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

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

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

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

640.webp (4).jpg

3.事務(wù)開始時會從時間戳服務(wù)模塊獲取一個時間戳,我們稱之為start_ts。事務(wù)讀取指定Key的value時,讀取的是從數(shù)據(jù)存儲中第一個小于等于start_ts的key value(上圖例子中是從下往上讀,因為圖例中的新數(shù)據(jù)在下面)。

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

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

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

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

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

640.webp (5).jpg

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

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

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

640.webp.jpg

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

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

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

640.webp (1).jpg

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

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

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

640.webp (2).jpg

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

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

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

640.webp (3).jpg

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

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

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

640.webp (4).jpg

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

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

640.webp (5).jpg

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

640.webp (6).jpg

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

640.webp.jpg

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

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

640.webp (1).jpg

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

640.webp (2).jpg

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

640.webp (3).jpg

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

640.webp (4).jpg

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

640.webp (5).jpg

結(jié)語

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

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

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

立即登錄,閱讀全文
版權(quán)說明:
本文內(nèi)容來自于騰訊云數(shù)據(jù)庫,本站不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。文章內(nèi)容系作者個人觀點,不代表快出海對觀點贊同或支持。如有侵權(quán),請聯(lián)系管理員(zzx@kchuhai.com)刪除!
優(yōu)質(zhì)服務(wù)商推薦
更多