超級輕量級: KV存儲引擎實(shí)現(xiàn)
Hi (????)?”,各位
Gopher本人最近又用Go造了一個輪子,一個超級輕量級KV存儲引擎,歡迎????各位Gopher進(jìn)行評測。
主頁介紹:https://bottle.ibyte.me (PC效果更佳??)
項(xiàng)目地址:https://github.com/auula/bottle
特 性
嵌入的存儲引擎 數(shù)據(jù)可以加密存儲 可以自定義實(shí)現(xiàn)存儲加密器 即使數(shù)據(jù)文件被拷貝,也保證存儲數(shù)據(jù)的安全 未來索引數(shù)據(jù)結(jié)構(gòu)也可以支持自定義實(shí)現(xiàn)
簡 介
首先要說明的是Bottle是一款KV嵌入式存儲引擎,并非是一款KV數(shù)據(jù)庫,我知道很多人看到了KV認(rèn)為是數(shù)據(jù)庫,當(dāng)然不是了,很多人會把這些搞混淆掉,KV存儲可以用來存儲很多東西,而并非是數(shù)據(jù)庫這一領(lǐng)域。可以這么理解數(shù)據(jù)庫是一臺汽車,那么Bottle是一臺車的發(fā)動機(jī)??梢院唵卫斫?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Bottle是一個對操作系統(tǒng)文件系統(tǒng)的KV抽象化封裝,可以基于Bottle做為存儲層,在Bottle層之上封裝一些數(shù)據(jù)結(jié)構(gòu)和對外服務(wù)的協(xié)議就可以實(shí)現(xiàn)一個數(shù)據(jù)庫。

本項(xiàng)目功能實(shí)現(xiàn)完全基于?bitcask?論文所實(shí)現(xiàn),另外本項(xiàng)目所用到一些知識和卡內(nèi)基梅隆大學(xué)的CMU 15-445: Database Systems課程內(nèi)容很接近,這門課由數(shù)據(jù)庫領(lǐng)域的大牛Andy Pavlo講授,有感興趣的朋友可以去看看這套課,如果覺得不錯你可以給我按一顆小星?謝謝。
安裝Bottle
你只需要在你的項(xiàng)目中安裝Bottle模塊即可使用:
go?get?-u?github.com/auula/bottle
基本API
如何操作一個Bottle實(shí)例代碼:
package?main
import?(
????"fmt"
????"github.com/auula/bottle"
)
func?init()?{
????//?通過默認(rèn)配置打開一個存儲實(shí)例
????err?:=?bottle.Open(bottle.DefaultOption)
????//?并且處理一下可能發(fā)生的錯誤
????if?err?!=?nil?{
????????panic(err)
????}
}
//?Userinfo?測試數(shù)據(jù)結(jié)構(gòu)
type?Userinfo?struct?{
????Name??string
????Age???uint8
????Skill?[]string
}
func?main()?{
????//?PUT?Data
????bottle.Put([]byte("foo"),?[]byte("66.6"))
????//?如果轉(zhuǎn)成string那么就是字符串
????fmt.Println(bottle.Get([]byte("foo")).String())
????//?如果不存在默認(rèn)值就是0
????fmt.Println(bottle.Get([]byte("foo")).Int())
????//?如果不成功就是false
????fmt.Println(bottle.Get([]byte("foo")).Bool())
????//?如果不成功就是0.0
????fmt.Println(bottle.Get([]byte("foo")).Float())
????user?:=?Userinfo{
????????Name:??"Leon?Ding",
????????Age:???22,
????????Skill:?[]string{"Java",?"Go",?"Rust"},
????}
????var?u?Userinfo
????//?通過Bson保存數(shù)據(jù)對象,并且設(shè)置超時時間為5秒,TTL超時可以不設(shè)置看需求
????bottle.Put([]byte("user"),?bottle.Bson(&user),?bottle.TTL(5))
????//?通過Unwrap解析出結(jié)構(gòu)體
????bottle.Get([]byte("user")).Unwrap(&u)
????//?打印取值
????fmt.Println(u)
????//?刪除一個key
????bottle.Remove([]byte("foo"))
????//?關(guān)閉處理一下可能發(fā)生的錯誤
????if?err?:=?bottle.Close();?err?!=?nil?{
????????fmt.Println(err)
????}
}
加密器
數(shù)據(jù)加密器是針對數(shù)據(jù)的value記錄的,也就是針對字段級別的區(qū)塊加密,并非是把整個文件加密一遍,那樣設(shè)計會帶來性能消耗,所以采用區(qū)塊數(shù)據(jù)段方式加密的方式。
下面例子是通過bottle.SetEncryptor(Encryptor,[]byte)函數(shù)去設(shè)置數(shù)據(jù)加密器并且配置16位的數(shù)據(jù)加密秘鑰。
func?init()?{
????bottle.SetEncryptor(bottle.AES(),?[]byte("1234567890123456"))
}
你也可以自定義去實(shí)現(xiàn)數(shù)據(jù)加密器的接口:
//?SourceData?for?encryption?and?decryption
type?SourceData?struct?{
????Data???[]byte
????Secret?[]byte
}
//?Encryptor?used?for?data?encryption?and?decryption?operation
type?Encryptor?interface?{
????Encode(sd?*SourceData)?error
????Decode(sd?*SourceData)?error
}
下面代碼就是內(nèi)置AES加密器的實(shí)現(xiàn)代碼,實(shí)現(xiàn)bottle.Encryptor接口即可,數(shù)據(jù)源為bottle.SourceData?結(jié)構(gòu)體字段:
//?AESEncryptor?Implement?the?Encryptor?interface
type?AESEncryptor?struct{}
//?Encode?source?data?encode
func?(AESEncryptor)?Encode(sd?*SourceData)?error?{
????sd.Data?=?aesEncrypt(sd.Data,?sd.Secret)
????return?nil
}
//?Decode?source?data?decode
func?(AESEncryptor)?Decode(sd?*SourceData)?error?{
????sd.Data?=?aesDecrypt(sd.Data,?sd.Secret)
????return?nil
}
具體的加密器實(shí)現(xiàn)代碼可以查看encrypted.go
散列函數(shù)
如果你需要自定義實(shí)現(xiàn)散列函數(shù),實(shí)現(xiàn)bottle.Hashed?接口即可:
type?Hashed?interface?{
????Sum64([]byte)?uint64
}
然后通過內(nèi)置的bottle.SetHashFunc(hash Hashed)?設(shè)置即可完成你的散列函數(shù)配置。
索引大小
索引預(yù)設(shè)置的大小很大程度上會影響你的程序存取和讀取數(shù)據(jù)的速度,如果在初始化的時候能夠預(yù)計出程序運(yùn)行時需要的索引大小,并且在初始化的時候配置好,可以減小程序在運(yùn)行過程中帶來的運(yùn)行數(shù)據(jù)遷移和擴(kuò)容帶來的性能問題。
func?init()?{
????//?設(shè)置索引大小?
????bottle.SetIndexSize(1000)
}
配置信息
你也可以不使用默認(rèn)配置,你可以使用內(nèi)置的bottle.Option?的結(jié)構(gòu)體初始化你存儲引擎,配置實(shí)例如下:
func?init()?{
????????//?自定義配置信息
????????option?:=?bottle.Option{
????????//?工作目錄
????????Directory:???????"./data",
????????//?算法開啟加密
????????Enable:??????????true,
????????//?自定義秘鑰,可以使用內(nèi)置的秘鑰
????????Secret:??????????bottle.Secret,
????????//?自定義數(shù)據(jù)大小,存儲單位是kb
????????DataFileMaxSize:?1048576,
????}
????//?通過自定義配置信息
????bottle.Open(option)
}
當(dāng)然也可以使用內(nèi)置的bottle.Load(path string)?函數(shù)加載配置文件啟動Bottle,配置文件格式為yaml,可配置項(xiàng)如下:
#?Bottle?config?options
Enable:?TRUE
Secret:?"1234567890123456"
Directory:?"./testdata"
DataFileMaxSize:?536870912
需要注意的是內(nèi)置的加密器實(shí)現(xiàn)的秘鑰必須是16位,如果你是自定義實(shí)現(xiàn)的加密器可通過bottle.SetEncryptor(Encryptor,[]byte)設(shè)置你自定義的加密器,那這個秘鑰位數(shù)將不受限制。
數(shù)據(jù)目錄
由于bottle設(shè)計就是基于但進(jìn)程的程序,所以每個存儲實(shí)例對應(yīng)是一個數(shù)據(jù)目錄,data為日志合并結(jié)構(gòu)數(shù)據(jù)目錄,index為索引數(shù)據(jù)版本。
日志合并結(jié)構(gòu)數(shù)據(jù)目前版本是每次數(shù)據(jù)啟動時候進(jìn)行合并,默認(rèn)是data數(shù)據(jù)文件夾下的所有數(shù)據(jù)文件占用總和超過1GB就會觸發(fā)一次合并,合并之后沒有用的數(shù)據(jù)被丟棄。
當(dāng)然如果未達(dá)到臟數(shù)據(jù)合并要求,數(shù)據(jù)文件會以啟動時候配置的大小進(jìn)行歸檔,每個數(shù)據(jù)有版本號,并且被設(shè)置為只讀掛載,進(jìn)程工作目錄結(jié)構(gòu)如下:
./testdata
├──?data
│???└──?1.data
└──?index
????├──?1646378326.index
????└──?1646378328.index
2?directories,?3?files
當(dāng)存儲引擎開始工作的時候,這個目錄下的所以文件夾和文件只能被這個進(jìn)程操作,保證數(shù)據(jù)安全。
后續(xù)維護(hù)
Bottle目前不支持多數(shù)據(jù)存儲分區(qū),后續(xù)版本會引入一個Bucket概念,未來可以把指定的數(shù)據(jù)存儲到指定的分區(qū)中,來降低并發(fā)的時候索引鎖的顆粒度。后續(xù)將引入 零拷貝技術(shù),當(dāng)前文件操作很大程度上依賴于操作系統(tǒng),當(dāng)前文件必須sync才能保證數(shù)據(jù)一致性。臟數(shù)據(jù)合并可以在運(yùn)行中進(jìn)行合并整理,基于 信號量的方式通知垃圾回收工作線程。
其他信息
如果你發(fā)現(xiàn)了bug歡迎提issue或者發(fā)起pull request,我收到了消息會盡快回復(fù)你,另外歡迎各位Gopher提出自己意見,或者貢獻(xiàn)自己的代碼也是可以的,另外我們也非常歡迎大家進(jìn)行存儲相關(guān)技術(shù)交流,不錯的話別忘去Github點(diǎn)一個?Thanks?(?ω?)?。
更詳細(xì)的問題可以查看:https://github.com/auula/bottle
想要了解更多與Go語言相關(guān)的內(nèi)容,歡迎入群和我們進(jìn)行交流~
