隨機訪問KV存儲引擎

Key-Value系統只需要支持簡單的隨機讀(Get),寫(Put)和刪除(Del)操作。由於磁盤是順序存儲介質,因此可以往數據文件追加Key-Value記錄並在內存中存放記錄所在的磁盤位置,即索引信息。由於對同一個Key的更新(Put)操作以最後一個爲準,內存中的索引只需要記錄最新的Key-Value對位置即可,而對於刪除操作,也是往數據文件追加一個刪除記錄。由於內存的大小有限,需要儘可能減小索引記錄的大小,比如只支持64位的整數Key(其它類型的Key可以通過md5運算得到64位的整數值)。

內存中的索引信息包括64位的Key(8字節),Key-Value記錄所在的磁盤位置(8字節),Key-Value記錄在磁盤中的長度(4字節),另外,假設採用Hash的方式組織索引信息並採用採用鏈地址法解決衝突,每個索引項在Hash表中佔用額外的8字節,最後,考慮到64位機的內存對齊,可以大致認爲每個索引項佔用32字節的內存空間,8GB內存存放的索引項條數爲8GB / 32 = 2.5億。如果每個Key-Value記錄的平均大小爲1KB,系統設計的磁盤內存比爲1KB / 32 = 32 : 1,16GB的內存對應512GB的磁盤數據,滿足大多數線上服務的需求。存儲引擎需要實現如下幾種操作:

讀取操作(Get):先通過內存中的Hash索引找到Key-Value對的磁盤存儲位置,接着執行一次隨機讀取操作。

寫操作(Put):無論是插入還是更新已有的Key-Value記錄,只需要將新數據追加到數據文件末尾,並更新內存中的Hash索引信息。

刪除操作(Del):將刪除操作追加到數據文件末尾,並刪除內存中的Hash索引項。

如果不對索引信息進行持久化,系統宕機重啓時需要重新讀取所有的數據以重建索引,宕機恢復時間很長。將索引信息以同樣的方式追加到索引文件中,寫操作及刪除操作的流程修改爲:a, 追加數據到數據文件;b, 追加索引信息到索引文件;c, 更新內存中的Hash索引結構。當機器宕機時,通過讀取索引文件來重建內存中的索引表,由於a和b兩個操作不是原子的,可能出現數據文件更新但是索引文件沒有更新的情況,因此,宕機恢復時除了讀取索引文件的索引信息外,可能還需要讀取數據文件中的最後幾條記錄。

更新和刪除操作會產生很多的無用數據,這些垃圾數據的回收是通過定時合併操作實現的,一般可選擇每天服務的低峯期,比如凌晨兩點啓動每日合併任務。

定時合併(Merge):採用0/1目錄的方式,假設當前的服務目錄編號爲0,合併過程如下:

1, 關閉目錄0的數據文件和索引文件,後續的更新操作(包括合併過程中的更新操作)都寫入目錄1中新開的文件;

2, 順序讀取目錄0的索引文件,對每一個索引項,對比是否與內存中的內容一致,如果一致,說明是最新的有效索引,將對應的數據追加到目錄1中的數據文件,同時生成相應的索引信息追加到目錄1的索引文件中並修改內存中的索引項;

3, 合併過程結束時可以回收目錄0中的數據文件和索引文件;

由於合併過程中可能有更新操作,且都需要追加目錄1中的索引文件,因此,需要將索引文件編號分成兩段,比如合併過程中寫入的索引文件從1開始編號,最大不超過1000;更新操作寫入的索引文件從1001開始編號。

上述介紹的存儲引擎的特點就是簡單,滿足特定類型的應用,且隨機讀取基本做到了最優。當然,工程實踐中還有很多工作需要細化,比如將數據分組從而利用多塊磁盤,定時合併過程中記錄進度從而宕機後可以從上次合併的位置開始繼續執行,追加數據和索引文件是否fsync,如何通過異步group commit寫磁盤保證不丟數據的前提下不損失併發能力,等等。總之,表面看似簡單的事情做到極致往往很不簡單。

說明:第一次接觸隨機存儲引擎是在百度,一位目前在創業的大牛的作品,目前douban的Beansdb也使用了類似思想。另外,可以參考一篇文章:Bitcask: A Log-Structured Hash Table for Fast Key/Value Data

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章