騰訊云超火開源數(shù)據(jù)庫產(chǎn)品架構(gòu)揭秘

來源: 騰訊云數(shù)據(jù)庫
作者:騰訊云數(shù)據(jù)庫
時間:2021-04-15
17969
本文帶你一起回顧騰訊云數(shù)據(jù)庫Tendis混合存儲版的整體架構(gòu),并且詳細(xì)揭秘其內(nèi)部的原理。

Redis作為高性能緩存經(jīng)常被廣泛應(yīng)用到各個業(yè)務(wù)——如游戲的排行榜、分布式鎖等場景。

但Redis也并非萬能的,在長期的使用過程中,我們也遇到Redis一些痛點(diǎn)問題,比如內(nèi)存占用高,數(shù)據(jù)可靠性差,業(yè)務(wù)維護(hù)緩存和存儲的一致性繁瑣等。

因此,騰訊云數(shù)據(jù)庫Tendis誕生了,今天,我們就一起回顧騰訊云數(shù)據(jù)庫Tendis混合存儲版的整體架構(gòu),并且詳細(xì)揭秘其內(nèi)部的原理。

Redis&Tendis

使用Redis有哪些痛點(diǎn)?我大致分類為以下三種:

一、內(nèi)存成本高

首先,在產(chǎn)品的不同階段,業(yè)務(wù)對QPS的要求不同,以游戲業(yè)務(wù)為例,剛上線的新游戲通常來說都特別火爆,為了支持上千萬同時在線,需要不斷的進(jìn)行擴(kuò)容增加機(jī)器。而在運(yùn)營一段時間后,游戲玩家逐漸變少到一個正常的用戶量級,訪問頻率(QPS)沒那么高,依然占用大量機(jī)器,維護(hù)成本很高。

其次,Redis保存全量數(shù)據(jù)時,需要Fork一個進(jìn)程。Linux的fork系統(tǒng)調(diào)用基于Copy On Write機(jī)制,如果在此期間Redis有大量的寫操作,父子進(jìn)程就需要各自維護(hù)一份內(nèi)存。因此部署Redis的機(jī)器往往需要預(yù)留一半的內(nèi)存。

二、緩存一致性的問題

對于Redis+MySQL的架構(gòu)需要業(yè)務(wù)方花費(fèi)大量的精力來維護(hù)緩存和數(shù)據(jù)庫的一致性。

三、數(shù)據(jù)可靠性

Redis本質(zhì)上是一個內(nèi)存數(shù)據(jù)庫,用戶雖然可以使用AOF的Always來落盤保證數(shù)據(jù)可靠性,但是會帶來性能的大幅下降,因此生產(chǎn)環(huán)境很少有使用。另外不支持回檔,Master故障后,異步復(fù)制會造成數(shù)據(jù)的丟失。

四、異步復(fù)制

Redis主備使用異步復(fù)制,這個是異步復(fù)制固有的問題。主備使用異步復(fù)制,響應(yīng)延遲低,性能高,但是Master故障后,會造成數(shù)據(jù)丟失。

關(guān)于Tendis,我們已經(jīng)做了很多介紹(開源一周star上千,什么產(chǎn)品這么香?)在此不再贅述。接下來我們對Tendis混合存儲版的整體架構(gòu)進(jìn)行詳細(xì)的解讀。

Tendis混合存儲版整體架構(gòu)

Tendis冷熱混合存儲版主要由Proxy、緩存層Redis、存儲層Tendis存儲版和同步層Redis-sync組成,其中每個組件的功能如下:

一、Proxy組件

負(fù)責(zé)對客戶端請求進(jìn)行路由分發(fā),將不同的Key的命令分發(fā)到正確的分片,同時Proxy還負(fù)責(zé)了部分監(jiān)控數(shù)據(jù)的采集,以及高危命令在線禁用等功能。

二、緩存層Redis Cluster

緩存層Redis基于社區(qū)Redis 4.0進(jìn)行開發(fā)。Redis具有以下功能:

版本控制;自動將冷數(shù)據(jù)從緩存層中淘汰,將熱數(shù)據(jù)從存儲層加載到緩存層;使用Cuckoo Filter表示全量Keys,防止緩存穿透;基于RDB+AOF擴(kuò)縮容方式,擴(kuò)縮容更加高效便捷。

三、存儲層Tendis Cluster

Tendis存儲版是騰訊基于RocksDB自研的兼容Redis協(xié)議的KV存儲引擎,該引擎已經(jīng)在騰訊內(nèi)部運(yùn)營多年,性能和穩(wěn)定性得到了充分的驗證。在混合存儲系統(tǒng)中主要負(fù)責(zé)全量數(shù)據(jù)的存儲和讀取,以及數(shù)據(jù)備份,增量日志備份等功能。

四、同步層Redis-sync

并行數(shù)據(jù)導(dǎo)入存儲層Tendis;服務(wù)無狀態(tài),故障重新拉起;數(shù)據(jù)自動路由。

640.webp.jpg

Tendis冷熱混合存儲的一些重要特性總結(jié):

緩存層Redis Cluster和存儲層Tendis Cluster分別進(jìn)行擴(kuò)縮容,集群自治管理等;

冷數(shù)據(jù)自動降冷,降低內(nèi)存成本;熱數(shù)據(jù)自動緩存,降低訪問延遲。

緩存層Redis Cluster

一、版本控制

首先基于社區(qū)版Redis改動是版本控制。我們?yōu)槊總€Key和每條Aof增加一個Version,并且Version是單調(diào)遞增的。在每次更新/新增一個Key后,將當(dāng)前節(jié)點(diǎn)的Version賦值給Key和Value,然后對全局的Version++;

如下所示,在redisObject中添加64bits,其中48bits用于版本控制。

typedef struct redisObject {

unsigned type:4;

unsigned encoding:4;

unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or

                            * LFU data (least significant 8 bits frequency

                            * and most significant 16 bits access time). */

int refcount;


/* for hybrid storage */

unsigned flag:4;                           /* OBJ_FLAG_... */

unsigned reserved:4;

unsigned counter:8;                        /* for cold-data-cache-policy */

unsigned long long revision:REVISION_BITS; /* for value version */


void *ptr;

} robj;

引入版本控制主要帶來以下優(yōu)勢:

1.增量RDB

社區(qū)版Redis主備在斷線重連后,如果slave發(fā)送的psync_offset對應(yīng)的數(shù)據(jù)不在當(dāng)前的Master的repl_backlog中,則主備需要重新進(jìn)行全量同步。

再引入Version之后,slave斷線重連,給Master發(fā)送帶Version的PSYNC replid psync_offset version命令。如果出現(xiàn)上述情況,Master將大于等于Version的數(shù)據(jù)生成增量RDB,發(fā)給Slave,進(jìn)而解決需要增量,同步比較慢的問題。

2.Aof的冪等

如果同步層Redis-sync出現(xiàn)網(wǎng)絡(luò)瞬斷(短暫的和緩存層或者存儲層斷開),作為一個無狀態(tài)的同步組件,Redis-sync會重新拉取未同步到Tendis的增量數(shù)據(jù),重新發(fā)送給Tendis。每條Aof都具有一個Version,Tendis在執(zhí)行的時候僅會執(zhí)行比當(dāng)前Version大的Aof,避免aof執(zhí)行多次導(dǎo)致的數(shù)據(jù)不一致。

二、冷熱數(shù)據(jù)交互

冷數(shù)據(jù)的恢復(fù)指當(dāng)用戶訪問的Key不在緩存層,需要將數(shù)據(jù)從存儲層重新加載到緩存層。數(shù)據(jù)恢復(fù)這里是緩存層直接和存儲層直接交互,當(dāng)冷Keys訪問的請求比較大,數(shù)據(jù)恢復(fù)很容易成為瓶頸,因此為每個Tendis節(jié)點(diǎn)建立一個連接池,專門負(fù)責(zé)與這個Tendis節(jié)點(diǎn)進(jìn)行冷熱數(shù)據(jù)恢復(fù)。

1618456780(1).png

用戶訪問一個Key的具體流程如下:

首先判斷Key是否在緩存層,如果緩存層存在,則執(zhí)行命令;如果緩存層不存在,查詢Cuckoo Filter,判斷Key是否有可能在存儲層;

如果Key可能在存儲層,則向存儲層發(fā)送dumpx dbid key withttl命令嘗試從存儲層獲取數(shù)據(jù),并且阻塞當(dāng)前請求的客戶端;

存儲層收到dumpx,如果Key在存儲層,則向緩存層返回RESTOREEX dbid key ttl value;如果Key不在存儲層(Cuckoo Filter的誤判),則向緩存層返回DUMPXERROR key;

存儲層收到RESTOREEX或者DUMPXERROR后,將冷數(shù)據(jù)恢復(fù)。然后就可以喚醒阻塞的客戶端,執(zhí)行客戶端的請求。

三、Key降冷與Cuckoo Filter

這里主要講解混合存儲從1:1版的緩存層緩存全量Keys,到N:M版的緩存層將Key和Value同時驅(qū)逐的演進(jìn),以及我們引入Cuckoo Filter避免緩存穿透,同時節(jié)省大量內(nèi)存。

1.Key降冷的背景介紹

2020年6月份上線的1:1版的冷熱混合存儲,緩存層Redis存儲全量的Keys和熱Values(All Keys+Hot values),存儲層Tendis存儲全量的Keys和Values(All Keys+All values)。在上線運(yùn)行了一段時間后,發(fā)現(xiàn)全量Keys的內(nèi)存開銷特別大,冷熱混合的收益并不明顯。為了進(jìn)一步釋放內(nèi)存空間,提高緩存的效率,我們放棄了Redis緩存全量Keys的方案,驅(qū)逐的時候?qū)ey和Value都從緩存層淘汰。

2.Cuckoo Filter解決緩存擊穿和緩存穿透

如果緩存層不存儲全量的Keys,就會出現(xiàn)緩存擊穿和緩存穿透的問題。為了解決這一問題,緩存層引入Cuckoo Filter表示全量的keys。我們需要一個支持刪除、可動態(tài)伸縮并且空間利用率高的Membership Query結(jié)構(gòu),經(jīng)過我們的調(diào)研和對比,最終選擇Dynamic Cuckoo Filter。

3.Dynamic Cuckoo Filter實現(xiàn)

項目初期參考了RedisBloom中Cuckoo Filter的實現(xiàn),在開發(fā)的過程中也遇到了一些坑,RedisBloom實現(xiàn)的Cuckoo Filter在刪除的時候會出現(xiàn)誤刪,最終給RedisBloom提PR修復(fù)了問題。

4.Key降冷的收益

最終采用將Key和Value同時從緩存層淘汰,降低內(nèi)存的收益很大。比如現(xiàn)網(wǎng)的一個業(yè)務(wù),總共有6620 W個Keys,在緩存全量Keys的時候占用18408 MB的內(nèi)存,在Key降冷后僅僅占用593MB。

四、智能淘汰/加載策略

作為冷熱混合存儲系統(tǒng),熱數(shù)據(jù)在緩存層,全量數(shù)據(jù)在存儲層。關(guān)鍵的問題是淘汰和加載策略,這里直接影響緩存的效率,細(xì)分主要有兩點(diǎn):當(dāng)緩存層內(nèi)存滿時,選擇哪些數(shù)據(jù)淘汰?當(dāng)用戶訪問存儲層的數(shù)據(jù)時,是否需要將其放入緩存層?

首先介紹混合存儲的淘汰策略,主要有以下兩個淘汰策略:

1.maxmemory-policy

當(dāng)緩存層Redis內(nèi)存使用到達(dá)maxmemory,系統(tǒng)將按照maxmemory-policy的內(nèi)存策略將Key/Value從緩存層驅(qū)逐,釋放內(nèi)存空間。(驅(qū)逐是指將Key/Value從緩存層中淘汰掉,存儲層和緩存層的Cuckoo Filter依然存在該Key;

2.value-eviction-policy

如果配置value-eviction-policy,后臺會定期將用戶N天未訪問的Key/Value被驅(qū)逐出內(nèi)存;

其次講一下緩存加載策略,為了避免緩存污染的問題(比如類似Scan的訪問,遍歷存儲層的數(shù)據(jù),將緩存層真正的熱數(shù)據(jù)淘汰,從而造成了緩存效率低下)。我們實現(xiàn)緩存加載策略:僅僅將規(guī)定時間內(nèi)訪問頻率超過某個閾值的數(shù)據(jù)加載到緩存中,這里的時間和閾值都是可配置的。

五、基于RDB+AOF擴(kuò)縮容

社區(qū)版Redis的擴(kuò)容流程如下所示:

640.webp (2).jpg

而社區(qū)版Redis擴(kuò)容也存在以下三個問題:

1.importing和migrating的設(shè)置不是原子的

先設(shè)置目標(biāo)節(jié)點(diǎn)slot為importing狀態(tài),再設(shè)置源節(jié)點(diǎn)的slot為migrating狀態(tài)。如果反過來,由于兩次操作非原子:源節(jié)點(diǎn)設(shè)置為migrating,目標(biāo)節(jié)點(diǎn)還未設(shè)置migrating狀態(tài),請求在這兩個節(jié)點(diǎn)間反復(fù)Move。

2.搬遷以Key為粒度,效率較低

Migrate命令每次搬遷一個或者多個Keys,將整個Slot搬遷到目標(biāo)節(jié)點(diǎn)需要多次網(wǎng)絡(luò)交互。

3.大Key問題

由于Migrate命令是同步命令,在搬遷過程中是不能處理其他用戶請求的,因此可能會影響業(yè)務(wù)(延遲時間波動較大)。

由于社區(qū)版Redis存在的上述問題,我們實現(xiàn)了基于RDB+Aof的擴(kuò)縮容方式,大致流程如下:

1)管控添加新節(jié)點(diǎn),規(guī)劃待搬遷slots;

2)管控端向目標(biāo)節(jié)點(diǎn)下發(fā)slot同步命令:cluster slotsync beginSlot endSlot[[beginSlot endSlot]...]

3)目標(biāo)節(jié)點(diǎn)向源節(jié)點(diǎn)發(fā)送sync[slot...],命令請求同步slot數(shù)據(jù)

4)源節(jié)點(diǎn)生成指定slot數(shù)據(jù)的一致性快照全量數(shù)據(jù)(RDB),并將其發(fā)送給目標(biāo)節(jié)點(diǎn)

5)源節(jié)點(diǎn)開始持續(xù)發(fā)送增量數(shù)據(jù)(Aof)

6)管控端定位獲取源節(jié)點(diǎn)和目標(biāo)節(jié)點(diǎn)的落后值(diff_bytes),如果落后值在指定的閾值內(nèi),管控端向目標(biāo)節(jié)點(diǎn)發(fā)送cluster slotfailover(流程類似Redis的cluster failover,首先阻塞源節(jié)點(diǎn)寫入,然后等待目標(biāo)節(jié)點(diǎn)和源節(jié)點(diǎn)的落后值為0,最后將搬遷的slots歸屬目標(biāo)節(jié)點(diǎn))

1618457044(1).png

同步層Redis-sync

同步層Redis-sync模擬Redis Slave的行為,接收RDB和Aof,然后并行地導(dǎo)入到存儲層Tendis。同步層主要需要解決以下問題:

1)并發(fā)地導(dǎo)入到存儲層Tendis,如何保證時序正確?

2)特殊命令的處理,比如FLUSHALL/FLUSHDB/SWAPDB/SELECT/MULTI等?

3)作為一個無狀態(tài)的同步組件,如何保證故障后,數(shù)據(jù)斷點(diǎn)續(xù)傳?

4)緩存層和存儲層分別進(jìn)行擴(kuò)縮容,如何將請求路由到正確的Tendis節(jié)點(diǎn)?

1618457102(1).png

為了解決上述問題,我們實現(xiàn)了下面的功能:

1.Slot內(nèi)串行,Slot間并行

針對問題1,Redis-sync中采用與Redis相同的計算Slot的算法,解析到具體的命令后,根據(jù)Key所屬的slot,將其放到對應(yīng)的隊列中(slot%QueueSize)。因此同一個Slot的數(shù)據(jù)是串行寫入,不同slot的數(shù)據(jù)可以并行寫入,不會出現(xiàn)時序錯亂的行為。

2.串并轉(zhuǎn)換

針對問題2,Redis-sync會在并行和串行模式之間進(jìn)行轉(zhuǎn)換。比如收到FLUSHDB命令,這是需要將FLUSHDB命令前的命令都執(zhí)行完,再執(zhí)行FLUSHDB命令。

3.定期上報

針對問題3,Redis-sync會定期將已發(fā)送給存儲層的aof的Version持久化到存儲層。如何Redis-sync故障,首先從存儲層獲取上次已發(fā)送的位置,然后向?qū)?yīng)的Redis節(jié)點(diǎn)發(fā)送psync,請求同步。

4.數(shù)據(jù)自動路由

針對問題4,Redis-sync會定期從存儲層獲取Slot到Tendis節(jié)點(diǎn)的映射關(guān)系,并且維護(hù)這些Tendis節(jié)點(diǎn)的連接池。請求從緩存層到達(dá),然后計算請求所屬的slot,然后發(fā)送到正確的Tendis節(jié)點(diǎn)。

存儲層Tendis Cluster

Tendis是兼容Redis核心數(shù)據(jù)結(jié)構(gòu)與協(xié)議的分布式高性能KV數(shù)據(jù)庫,主要具有以下特性:

1.兼容Redis協(xié)議

完全兼容redis協(xié)議,支持redis主要數(shù)據(jù)結(jié)構(gòu)和接口,兼容大部分原生Redis命令。

2.持久化存儲

使用rocksdb作為存儲引擎,所有數(shù)據(jù)以特定格式存儲在rocksdb中,最大支持PB級存儲。

3.去中心化架構(gòu)

類似于redis cluster的分布式實現(xiàn),所有節(jié)點(diǎn)通過gossip協(xié)議通訊,可指定hashtag來控制數(shù)據(jù)分布和訪問,使用和運(yùn)維成本極低。

4.水平擴(kuò)展

集群支持增刪節(jié)點(diǎn),并且數(shù)據(jù)可以按照slot在任意兩節(jié)點(diǎn)之間遷移,擴(kuò)容和縮容過程中對應(yīng)用運(yùn)維人員透明,支持?jǐn)U展至1000個節(jié)點(diǎn)。

5.故障自動切換

自動檢測故障節(jié)點(diǎn),當(dāng)故障發(fā)生后,slave會自動提升為master繼續(xù)對外提供服務(wù)。

640.webp (5).jpg

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