關(guān)于LevelDB,眾所周知就是google基于lsm不斷演化出來的一種kv存儲庫。而中間有各種語言的不同版本,今天則直接介紹一下golang版本中的一下實(shí)例。
基本功能
作為kv存儲庫,本身leveldb并不是和redis以及mysql一樣擁有自己的獨(dú)立服務(wù),它本身是作為一個三方庫,支持各個服務(wù)直接使用,這更像sqllite的能力,這中間就需要指定一個路徑作為數(shù)據(jù)庫的基本空間。
db, err := leveldb.OpenFile("path/to/db", nil)
...
defer db.Close()
通過OpenFile方法,便可以指定對應(yīng)的數(shù)據(jù)庫路徑,第二個參數(shù)則是當(dāng)前數(shù)據(jù)庫的相關(guān)屬性,例如過濾器類型、緩存大小、壓縮屬性相關(guān),這塊就放在后面細(xì)說
當(dāng)打開數(shù)據(jù)庫后,kv存儲基本上就是3類操作:插入和查詢、刪除,只不過刪除在最底層實(shí)現(xiàn)也還是插入邏輯。具體使用也是如下:
err = db.Put([]byte("key"), []byte("value"), nil)
if err != nil {
panic(err)
}
data, err := db.Get([]byte("key"), nil)
if err != nil {
panic(err)
}
fmt.Println(data)
err = db.Delete([]byte("key"), nil)
if err != nil {
panic(err)
}
方法基本上沒有什么特殊之處,而第二個參數(shù)都是操作中基本上需要的一下配置化信息:插入和更新操作關(guān)心是否強(qiáng)制落庫以及是否支持合并寫入、而查詢所關(guān)心的則是是否不走緩存。
當(dāng)然,為了高性能,leveldb本身也支持批量插入
batch := new(leveldb.Batch)
batch.Put([]byte("foo"), []byte("value"))
batch.Put([]byte("bar"), []byte("another value"))
batch.Delete([]byte("baz"))
err = db.Write(batch, nil)
批量插入的核心優(yōu)點(diǎn)就是會打開一個事務(wù)保證此次的插入原子性。關(guān)于本身事務(wù)的實(shí)現(xiàn),這也是后續(xù)的一個課題。
遍歷
當(dāng)數(shù)據(jù)的存儲已經(jīng)給出實(shí)例,那這塊還需要有檢索能力,才能支持更豐富的應(yīng)用場景。
而關(guān)于遍歷。由于本身是一個高性能的并發(fā)數(shù)據(jù)庫,當(dāng)并行時出現(xiàn)變更,則會導(dǎo)致遍歷異常,而若直接加鎖,則會導(dǎo)致性能的大規(guī)模損壞。這也映射了mysql中的mvcc實(shí)現(xiàn)。
在leveldb中,直接使用的是迭代器+快照的方法來實(shí)現(xiàn)遍歷能力。而遍歷本身也就分為全局遍歷、部分遍歷、范圍遍歷、匹配遍歷。這塊也暫時舉幾個??,讓人有直觀的印象。
for i := 0; i < 5; i++ {
db.Put([]byte(gofakeit.Name()), []byte(gofakeit.Address().Address), nil)
}
iter := db.NewIterator(nil, nil)
for iter.Next() {
// Remember that the contents of the returned slice should not be modified, and
// only valid until the next call to Next.
key := iter.Key()
value := iter.Value()
fmt.Println("all date: ", string(key), " -> ", string(value))
}
iter.Release()
生成數(shù)據(jù)本身使用的是
github.com/brianvoe/gofakeit
而針對范圍遍歷。我們只需要更改一下迭代器的生成即可:
iter = db.NewIterator(&util.Range{Start: []byte("Trinity Runte"), Limit: []byte("Vito Gulgowski")}, nil)
部分遍歷。則是通過迭代器本身的seek方法來找到偏移量:
iter.Seek([]byte("Trinity Runte"))
還有一個有趣的點(diǎn)是,遍歷能支持前綴匹配:
iter := db.NewIterator(util.BytesPrefix([]byte("foo-")), nil)
關(guān)于遍歷本身,其實(shí)么有特別多好講的,更多的是遍歷對性能上是一個較大的損失,因?yàn)楸旧韑eveldb是分層文件,遍歷則表示需要將所有數(shù)據(jù)全部查詢,其中也就包括熱點(diǎn)和非熱點(diǎn)數(shù)據(jù),這樣會變現(xiàn)導(dǎo)致io的壓力增加。