天天看點

每天進步一點點——leveldb項目實踐

    轉載請說明出處:http://blog.csdn.net/cywosp/article/details/20746879

    leveldb是Google公司開源的高性能Key-Value資料庫,在很多開源的項目中就有使用,例如:Chromium,淘寶的Tair,SSDB等。leveldb對于小資料在寫入百萬個之後讀寫性能依舊強勁,高效,是以非常适合用于存儲小檔案,以及一些需要持久化的索引和需要持久化的異步任務。

    接觸leveldb已經有一段時間了,當初為了找到一款輕量,高性能的資料庫用于存儲分布式檔案系統中的任務,以便于在程式因為未知Bug而崩潰時能儲存未完成的異步操作任務,我們網上搜尋資料并比較了多個開源資料庫,例如:Mysql、Sqlite、leveldb等等,綜合各方面,最終選擇了leveldb,其key具有排序功能,以及高效的性能非常符合我們的要求(Mysql對于存儲本地程序中的任務太過于笨重了,而Sqlite在資料量大情況下性能會有比較明顯的變化)。在我們的分布式檔案系統中,我們對無需全局共享異步操作任務都使用了leveldb做持久化,在程式重新開機後重新加載leveldb中的資料,恢複崩潰之前的狀态,繼續自動執行未完成的任務。我們的大概做法是:為每一個任務配置設定一個唯一的Key,因為所有的任務都是具有先後執行順序的,為了能夠重新開機之後,任務的順序能先前的一樣,我們充分利用了leveldb根據字元比較來排序Key的特性,為任務從零開始配置設定Key的初始值,然後在所配置設定到的整數前以補0的方式格式化出一個21位的字元串類型的Key值(為什麼是21位呢?因為在64位的作業系統中,一個無符号長整數的最大值有20位。任務多了不會出現整型溢出嗎?這個不用擔心,即使每秒能處理一百萬個任務,20位的整型數足矣用N萬年)。在系統中不同的子產品有不同的任務,甚者同一子產品中也有不同的任務類型。那麼在從leveldb中讀取資料重建任務隊列時任務的區分就需要很重要了,是以我們引入了任務類型,将任務類型與任務的其他資料一起序列化儲存到leveldb中,在反序列化時先讀取任務類型,然後再根據任務類型來調用對應的反序列化函數。根據以上這些需求和做法,我們設計并實作了一套通用的類似于标準庫的持久化容器(list、map)來更好的完成所需的功能。

在使用leveldb的過程中我們也遇到了一些問題,同時根據自己的實驗以及網上的資料簡單總結了一下: 1. 存入leveldb中的每個key所對應的内容不宜太大,超過32KB性能就會下降很快 2. 能夠多個key内容合并寫入的據盡量使用WriteBatch,這樣會使得leveldb順序寫入檔案中,性能更快。(前提是記憶體要做限制) 3. 将db/version_set.cc中的kTargetFileSize值變大,使得在大量資料寫入後建立少量的檔案 4. 一塊磁盤最好隻使用一個leveldb執行個體進行資料的讀寫,這樣可以減少磁道尋址時間 5.由于leveldb是一個先寫log,然後再通過背景線程将log檔案壓縮到對應的檔案中,在這個過程中會産生一些臨時的檔案,進而使得磁盤的使用空間會比真實寫入的内容容量要大,隻有這些log檔案被處理過後,磁盤空間才會與真實寫入的内容大小相當。在1.14的版本中,如果磁盤被後續寫入的資料寫滿了,背景壓縮log的線程将會導緻程式崩潰,大概會出現如類似下堆棧資訊:

(_ZN7leveldb3log6Writer18EmitPhysicalRecordENS0_10RecordTypeEPKcm+0x84) [0x7fd34cdb7d94]  (_ZN7leveldb3log6Writer9AddRecordERKNS_5SliceE+0x74) [0x7fd34cdb7f44] (_ZN7leveldb6DBImpl5WriteERKNS_12WriteOptionsEPNS_10WriteBatchE+0x1b3) [0x7fd34cdb2273] (_ZN7leveldb2DB3PutERKNS_12WriteOptionsERKNS_5SliceES6_+0x54) [0x7fd34cdacd94] (_ZN7leveldb6DBImpl3PutERKNS_12WriteOptionsERKNS_5SliceES6_+0x9) [0x7fd34cdacdd9]

不過在最新的1.15版本中,這個問題已得到了解決。 6. 如果磁盤被100%寫滿了,此時最好不要停止leveldb執行個體,不然下次就無法使用已有的資料了,當磁盤容量到100%時,原有的leveldb就無法打開了。在快速寫入資料時,會産生大量的log檔案,等這些log檔案被背景壓縮線程處理後,将會釋放出很多被臨時暫用的空間。 7. 在1.15版本中,如果使用NewIterator函數建立了leveldb::Iterator對象而沒有delete該對象的話,在程式退出時将會報出如下錯誤:     db/version_set.cc:806: leveldb::VersionSet::~VersionSet(): Assertion `dummy_versions_.next_ == &dummy_versions_' failed.     根據代碼分析,這裡assert失敗的原因主要是為了防止記憶體洩露。