Tair持久儲存系列技術解讀

程式碼派就是我發表於2020-10-28

Redis做為當今主流的記憶體資料庫支援許多豐富的資料結構,比如雜湊表、集合,還有lua指令碼、事務、訊息訂閱等等高階特性,同時使用記憶體做為主要的儲存介質,支援高速訪問。

但是由於其資料全部儲存在記憶體,成本較高,而且對於海量資料儲存的支援也存在一些痛點,比如在AOFREWRITE和生成RDB快照時會有較高的latency spike,大資料量下全量同步耗時較長、失敗率較高。並且資料可靠性稍弱,RDB和AOF不能保證資料不丟失。

為了解決上述問題,拓寬Redis的應用場景,我們結合新技術新硬體推出了Tair持久儲存系列產品:容量儲存型和持久記憶體型,支援大容量儲存和更高的資料可靠性。

>>釋出會傳送門

點選瞭解產品詳情

容量儲存型
9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

使用磁碟儲存就是其中的解決方案之一,利用磁碟可以降低成本並且提供海量儲存。但是在磁碟上實現redis也會有一些挑戰:

1.首先redis的資料結構都是基於記憶體實現,記憶體可以直接定址,而磁碟是個塊裝置,需要在磁碟上構建儲存引擎來支援redis資料結構訪問。

2.另外磁碟和記憶體有較大的效能差距,原生redis單執行緒的架構無法滿足吞吐需求,需要從架構設計上提升訪問效能。

應對這些挑戰,我們基於rocksdb進行了改造,提供了高效能的儲存引擎TairDB,並實現了redis資料結構向簡單kv的編碼對映,使redis資料能夠儲存在磁碟上;採用多執行緒的架構來提升訪問磁碟的效能;同時使用阿里雲ESSD高效雲盤為儲存底座,利用雲盤快照進行備份和全量同步,避免fork帶來的問題並提高全量同步效率。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

redis有五種基本資料型別,其中string可以直接對映到rocksdb的kv,但是其他一些複雜的資料結構hash、list、set、zset需要透過一定格式的編碼把redis的資料結構對映到rocksd的kv上。

我們把redis資料結構拆分為meta和data兩類,進行不同的編碼,透過meta可以去找到其對應的data,也即二級索引。

以hash為例,執行hset myhash myfield myvalue之後,hash表的名字myhash就會在meta中生成一份kv,其中key就是myhash,value會標誌它的屬性為hash表;myfield和myvalue會記錄在data中,再以key+型別+filed就可以索引到hash表的所有內容。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

為了實現多執行緒架構,首先需要解決key衝突的問題,這裡我們實現了key級別的鎖,這樣可以大大降低鎖衝突,提高併發度。命令執行過程中多個執行緒首先獲取key鎖,然後按命令的邏輯執行,透過預先設計好的編碼規則存取資料。最後再把結果以事務的方式提交給底層儲存引擎。每個命令的執行都是要在事務提交之後才會返回結果,這樣每一條命令都是持久化的,大大提升了資料可靠性。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

關於主備複製,全量複製使用雲盤快照提高效率。增量複製採用類似MySQL binlog的方式,事務提交之後同時也會寫入binlog,然後會有sender把binlog傳輸給備庫,binlog傳輸到備庫上時會首先儲存為relaylog作為中繼,然後透過relaylog再回放應用,這樣有兩點好處:

1.支援semisync,只要relaylog落盤就可以認為事務在備庫也提交完成,不用等待relaylog應用,這樣既可以提升增量同步的效率,同時提供了更強的主備一致性保證。

2.支援併發回放,在relaylog中記錄併發度的元資訊,不同的key就可以進行併發回放提高效率,同時相同的key仍然按序回放,保證主備一致性,不會造成資料錯亂。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

上圖為不同型別場景和例項規格下的效能測試結果,測試命令為時間複雜度O(1)的GET/SET,綜合效能中位數在開源版70%。

在資料小於記憶體的情況下大部分資料都會快取在作業系統的page cache中,整體效能會優於資料大於記憶體的情況。規格越高的例項執行緒越多併發度也就越高,效能也相對越好。另外不同於記憶體中的GET/SET,磁碟上寫入資料需要有read modify write的過程,也即需要先讀取後設資料才能進行修改,所以對於GET/SET寫效能要弱於讀效能。

持久記憶體型

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

傲騰持久記憶體是Intel推出的一款非易失性記憶體產品,在提供接近記憶體延時能力的同時保持持久化的能力, 理想情況下對於Redis場景來說是非常好的,因為資料寫入到持久記憶體中已經持久化,那麼就不需要額外的日誌和Checkpoint用來保證持久化的特性,同時傲騰持久記憶體在延遲上也比較接近記憶體優於傳統SSD,成本上對比記憶體也更加的便宜。

Redis基於傲騰持久記憶體能達到高效能的同時擁有較高的持久化能力,但是實際在工程實現會碰到非常大的挑戰,包括:

1.需要使用持久化記憶體的分配器來代替原有的記憶體分配器,分配器的後設資料資訊需要持久化,否則在恢復的時候會造成記憶體的洩露或者不一致。
2.原本String,Set,Hash這些資料結構和索引在異常的時候全部失效在恢復的時候重建,而現在這些資料都是持久化的,如何支援設計持久化的資料結構是目前工業界和理論界主要的研究方向之一
3.索引和資料的一致性,資料的完整性,這些都會在下一張NVM的挑戰中做更詳細的闡釋
4.持久記憶體在延時還是比記憶體更高,如何做好冷熱分離,讓系統擁有更高的效能。
5.如何擁有高效能的同時兼備強大的持久化能力。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

持久記憶體的使用分為兩大類Memory Mode和 AppDirecrt Mode, memory mode無需使用者改造但是沒有持久化內裡, 使用App Direct mode之後對比傳統SSD從block定址轉為位元組定址,同時介面也從檔案write/read轉為記憶體的load和store。

資料寫入記憶體的過程可能會停留在CPU L1,L2cache,需要呼叫類似CLWB和CLFLUSHOPT這樣的指令來刷到記憶體系統中,由於CPU只能保證8個位元組的原子寫入,那麼對於一個16位元組的寫很有可能在寫完第一個8位元組的時候crash,後半部分沒有寫入成功這個就是所謂的partial writes, 上層應用在使用持久記憶體的時候需要額外的實現來保障資料持久問題。

下面的例子是一個雙向連結串列,傳統記憶體crash之後所有的資料丟失,而持久記憶體則保留了crash的狀態,因此會出現B的Next指標指向了C而C的Prev指標缺沒有指向B,這個時候的雙向連結串列是出於異常的狀態。 從連結串列衍生開來記憶體分配器中的管理結構也存在這個問題,會出現記憶體洩露等情況。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

由於持久化的挑戰,目前主流使用持久記憶體的方式都是當做Memory或者使用AppDirect但是不支援持久化,阿里雲Tair持久記憶體版的是基於傲騰持久記憶體的自研引擎,解決了持久化程式設計中遇到的各種挑戰,撘配阿里雲官方提供的Linux作業系統映象Aliyun Linux,Aliyun彈性計算服務首次(全球首家)在神龍裸金屬伺服器上引入傲騰持久記憶體,深度最佳化完善支援,為客戶提供安全、穩定、高效能的體驗。

阿里雲持久記憶體版Tair的每一條記錄都確保寫入AEP並且持久化才返回,極大的提升資料的可靠性, 同時在讀取路徑上使用Dram快取如索引等熱點資料結構和元數資訊,來加速資料訪問的存取。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

在神龍裸金屬機器上,我們使用相同配置進行了Tair持久記憶體版和Redis6.0的效能對比, 整體上吞吐為社群記憶體版本的90%, 延時上由於沒有AofRewrite的干擾,P95的延時更加的穩定。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31550522/viewspace-2730510/,如需轉載,請註明出處,否則將追究法律責任。

相關文章