Tair ldb(leveldb存儲引擎)實現介紹

jopen 14年前發布 | 38K 次閱讀 NoSQL數據庫 NOSQL

        Tair 是淘寶開源的分布式 KV 緩存系統,內部將功能模塊化,抽離出底層存儲細節,可以接入不同的存儲引擎。leveldb 是 Google 開源的單機存儲引擎,目前,已經作為 Tair 的持久化存儲引擎 ldb 上線使用,這里對接入 leveldb 所做的處理以及修改進行介紹。

        Tair 首先是一個分布式的框架,有一系列策略滿足 CAP(數據備份,遷移復制等)。另外,還有針對應用場景的功能特性(namespace,數據過期時間,原子計數等)。接入 leveldb 時主要對 KV 操作之外的功能做相應的處理。

        一. 修改配置

        leveldb 中有一系列參數會與讀寫的效率有關,將相關的配置以及編譯常量統一修改成可運行時配置參數,測試選取最佳配置值。

        二. 實例個數控制

        首先確定在一個 tair server 上要啟幾個 leveldb 實例?

        Tair 中以桶來組織數據,如果按照一個桶一個 leveldb 實例,在做遷移復制的時候會很方便,但考慮如果在一塊磁盤上起多個實例,那么整體看來,多個順序寫變成了隨機寫,每個實例的 compact 進程會加劇整個磁盤的隨機 IO,所以并不采用每個桶一個實例,而是針對磁盤的數量由相關配置控制實例的個數。

        三. 內部 key 的格式

        為實現 Tair 中的功能邏輯,ldb 傳入 leveldb 的 user-key 格式如下:

Tair ldb(leveldb存儲引擎)實現介紹

        1)    Tair 中的數據可以設置過期時間,過期時間保存可以保存在在 value 的 meta 中,但考慮能在 leveldb 內部提前檢查,省去解析 value 的消耗,將過期時間保存在 key 中,但并不參與排序。

        2)    Tair 中的數據組織以及遷移復制都是以桶(bucket)單位,為獲得一個桶的數據,添加桶號前綴,保證一個桶的數據都存儲在一起。

        3)    Tair 中的數據區分 namespace,會將 namespace 作為客戶端傳入 key 的前綴存儲。

        四. 自定義 comparator

        為了實現 Tair 中的功能邏輯,實現自定義的 comparator 傳入 leveldb,實現自定義的排序邏輯(傳入 leveldb 的 user-key 中表示過期時間的前四個字節不參與排序),并為 comparator 添加兩個判斷數據是否有效的邏輯接口(ShouldDrop ()/ShouldDropMaybe ()),修改 leveldb 內部做遍歷以及 compact 時判斷數據是否有效(kTypeValue/kTypeDeletion)的邏輯。

        1)    ShouldDropMaybe (): 用來判斷數據是否已經過期。解析 key 中的 expired_time 即可。

        2)    ShouldDrop (): 用來判斷數據是否屬于已經清理掉的數據(bucket 已經遷移或者 namespace 已經被清理)。

        3)    區分過期數據與清理數據的判斷,是因為丟棄過期數據必須保證該 key 是數據的最后出現,否則刪除該數據會讓該 key 失去最后的更新狀態,而清理數據有 gc 信息保證,不需要關心數據的狀態。

        五. 清理數據/自定義的內部 compact 邏輯

        這里把清理數據的操作稱為 gc。

        1.     遷移的 bucket 以及清理的 namespace 中的數據

        一旦發生 bucket 遷移或者清理 namespace,會把相應的信息保存下來(GcNode)

struct GcNode

{

// 遷移走的 bucket 或者清掉的 namespace

int32_t key_;

// 清理發生時的 SequnceNumber,用來判斷數據的時間點

uint64_t sequence_;

// 清理發生時的 FileNumber,用來縮小 compact 的范圍

uint64_t file_number_;

// 清理發生時的時間

uint32_t when_;

};

        1)    Tair 中會有桶遷移或者 namespace 清理的操作,廢棄的數據并不會立刻清理,依靠后續的 compact 進程清理。但同時桶又可能被遷移回來,namespace 也可能繼續使用。leveldb 中的 SequnceNumber 恰好可以標識數據的時間點,所以在做數據清理的時候,記錄下當時的 SequnceNumber(sequnce_),在做判斷的時候,只有小于 sequnce_的數據才認為可以被丟棄。

        2)    filenumber_是為了縮小主動 compact 的范圍。

        leveldb 自身進行 compact 的過程中,加入自實現 comparator 的 ShouldDropMaybe ()/ShouldDrop ()判斷邏輯,會將對應的 gc 數據清理。但 leveldb 自身的 compact 進程并不能保證將所有的數據清理掉,所以添加 compact 的定時線程,主動觸發 compact 做數據清理。對于清理的 bucket 或者 namespace,根據 key 的格式,可以構造出需要 compact 的 key-range,但直接使用 leveldb 提供的 CompactRange 有以下問題:

        1)    如果某個 sstable 在記錄 gc 之后已經被 compact 過,所要清理的數據就已經丟棄掉了,并不需要再做 compact。

        2)    主動觸發的 compact 并不是基于均衡 db 狀態,所以造成的 level 遷移可能會有反作用。

        對于1)2)的問題,做以下策略:

        1)    FileNumber 全局遞增可以用來標識時間點,gc 時,記錄下當時的 FileNumber,主動 compact 時,只需要選取符合 key-range 并且 FileNumber 小于記錄的 filenumber_的 sstable 即可,縮小需要 compact 的 sstable 范圍。

        2)    主動觸發的 compact 只選取 level-n中的 sstable,compact 后,也只生成 level-n中的 sstable,level 之間的均衡,仍由內部 compact 進程負責。

        3)    基于上述1) 2),為 leveldb 添加 CompactRangeSelfLevel ()邏輯,實現1) 2)中的策略,主動觸發 compact 以清理數據,整個 db 的均衡仍由內部的機制保證。

        gc 信息會同時記 binlog,在 server 重啟時 replay。

        2.     過期的數據

        因為過期是一個持續的時間狀態,如果要完全回收過期的數據,只能對全 db 做 compact,這樣做的性能比不合算,所以目前對過期數據不做特別的主動清理,依靠內部以及外部觸發的 compact 進程中回收空間。

        六. 上層 cache

        leveldb 內部有 sstable 元信息和 block 數據的 cache,優化讀的效率,在 leveldb 上層再嵌入 Tair 自帶的 mdb 做 KV 層面的 cache,并添加 cache 的信息統計。當前統計看到,熱點數據的讀基本落在 mdb 中。

        七. 后續的優化

        1)    leveldb 本身的一些優化(參見 leveldb 的實現解析)。

        2)    優化網絡的使用,使用新的網絡框架。

        3)    ssd 使用優化。leveldb 內部做的一些細節優化針對于 sas 盤的 IO 性能,當前使用的 ssd IO 能也未充分利用。后續針對提升 ssd 使用性能做相應的 IO 優化(dio)。

        4)     內存的優化使用。大內存的使用有很大的優化余地,避免 swap 的使用,pagecache 的相應回寫策略配置。

        5)    對不存在數據查找的優化,采用 mock value 處理或者添加 bloomilter。

        6)    range 以及數據 dump 功能的實現。

        歡迎大家對 leveldb 的使用優化提出寶貴建議。

        廣告時間: Tair 現在已經將 Redis 的存儲部分抽離出來,作為非持久化的存儲引擎 rdb,即將上線使用,歡迎大家強勢圍觀。

來自: rdc.taobao.com

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!