更多精彩內容,請關注微信公眾號:後端技術小屋
看了boltdb也有一陣子了,看完之後總想寫點什麼,因為感覺到這可能是個不小的坑,所以遲遲沒有動筆(沒錯我的拖延症又犯了..)。最近有一種流行的說法:如果一個東西不能把它講清楚,便不能說你學會了它。因為看起來會和真的會之間有一個巨大的鴻溝,想跨越這個鴻溝便需要不斷的提問、思考與輸出,這是個相對枯燥但絕對值得的過程,因此趁著週末兩天的完整時間正式開始挖坑。
什麼是boltdb
Boltdb是一個go語言開發的嵌入式kv資料庫。其實現相對簡單:
- 不支援網路請求和SQL查詢,因此也就沒有了網路互動、詞法分析、語法分析、查詢優化等成熟資料庫中必不可少的功能。
- 使用了比較少見的shadow page技術,只支援一個writer和多個reader,在這種約束下,事務的隔離級別為可序列話,併發控制也比較簡單
- 使用mmap將記憶體與磁碟建立對映,由OS管理磁碟page load到記憶體的過程,大大減少了boltdb手動管理的複雜度。
Boltdb所有程式碼加起來才1W行,但是麻雀雖小五臟俱全,非常適合用來學習資料庫中的一些基本原理和概念,例如page、transanction、cursor等。
值得一提的是,Boltdb還是etcd底層的kv儲存,目前Boltdb原倉庫(https://github.com/boltdb/bolt)已經是read-only狀態。而etcd維護了一個fork(https://github.com/etcd-io/bbolt), 主要是為了繼續增強可靠性、穩定性和效能。
如何使用boltdb
資料模型
在使用boltdb之前,我們需要對其資料模型有個直觀的瞭解。以下是boltdb與關係型資料庫的資料模型簡單類比:
boltdb中的概念 | 關係型資料庫中的概念 |
---|---|
DB | database |
Bucket | table |
key value pair | Tuple |
Boltdb中的Bucket雖然可簡單類比成關係型資料中table,有一點卻不相同:前者可巢狀建立Bucket, 即一個Bucket下還可建立子Bucket, 而後者不行。
安裝
go get github.com/boltdb/bolt/...
操作DB
操作DB包括建立(開啟)、關閉。
程式碼如下:在執行bolt.Open
時,如果指定檔案路徑不存在,則根據路徑建立一個資料庫檔案;否則載入該路徑下的檔案。使用db.Close
便可關閉DB.
package main
import (
"log"
"github.com/boltdb/bolt"
)
func main() {
// Open the my.db data file in your current directory.
// It will be created if it doesn't exist.
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
...
}
操作事務
Boltdb中按照是否只讀將事務分為讀事務和寫事務。
使用者使用db.View
建立讀事務時需傳入一個回撥函式,表示讀事務執行操作。如果回撥函式返回的err != nil
,db.View
則會回滾該事務,並將err
透傳給db.View
err := db.View(func(tx *bolt.Tx) error {
...
return nil
})
使用db.Update
可建立寫事務。db.Update
如何處理錯誤同db.View
err := db.Batch(func(tx *bolt.Tx) error {
...
return nil
})
操作Bucket
操作Bucket包括建立Bucket、刪除Bucket
建立Bucket屬於寫事務。這裡db.Update
會建立一個寫事務,寫事務執行的操作是CreateBucket
,即建立一個新的Bucket
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("MyBucket"))
if err != nil {
return fmt.Errorf("create bucket: %s", err)
}
return nil
})
刪除Bucket也屬於寫事務。使用上同理
db.Update(func(tx *bolt.Tx) error {
b, err := tx.DeleteBucket([]byte("MyBucket"))
if err != nil {
return fmt.Errorf("create bucket: %s", err)
}
return nil
})
操作key/value
操作key/value包括:新建/更新/刪除/查詢。所有的key/value對都必須屬於某個具體的Bucket. 因此操作key/value之前必須找到Bucket物件。
新建/更新程式碼必須用寫事務封裝,程式碼如下,這裡在名為MyBucket
的Bucket下新增了一對("answer", "42")
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
err := b.Put([]byte("answer"), []byte("42"))
return err
})
刪除程式碼如下:
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
err := b.Delete([]byte("answer")
return err
})
查詢程式碼如下:
db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
v := b.Get([]byte("answer"))
fmt.Printf("The answer is: %s\n", v)
return nil
})
如何分析Boltdb
程式碼導讀
首先是讀程式碼,從微觀到巨集觀的層面瞭解這座房屋如何建成的。程式碼閱讀順序是
page.go: 磁碟上的page layout,包括meta page, freelist page, branch page, leaf page。
node.go: 磁碟上的page反序列化到記憶體之後的資料結構,也作為B+樹節點。
freelist.go: page管理, 支援page申請、釋放、回滾等操作。
cursor.go: 用於訪問B+樹的迭代器
bucket.go: Bucket資料結構,支援建立/刪除子Bucket、新建/更新/刪除kv資料。
db.go.go: 用於訪問DB, 支援開啟/關閉DB、建立讀/寫事務、db file自動擴容。
更詳細的程式碼細節將在該系列的後續內容中給出.
分析工具
Boltdb提供了一個好用的工具,可用於檢視db file中每個page的內容
安裝:
git clone https://github.com/boltdb/bolt
cd cmd/bolt
go build
ls ./bolt
檢視所有pages狀態
$ ./bolt pages /tmp/bolt.db | head
ID TYPE ITEMS OVRFLW
======== ========== ====== ======
0 meta 0
1 meta 0
2 freelist 4
3 leaf 141
4 leaf 86
5 leaf 85
6 branch 117
7 leaf 85
其中ID表示page id, TYPE為page型別,ITEMS表示其中的資料條數,OVRFLW表示該page是否溢位。
檢視某個page的內容
$ ./bolt page /tmp/bolt-624750664 3 | head
Page ID: 3
Page Type: leaf
Total Size: 4096 bytes
Item Count: 141
"9874": "9874"
"9875": "9875"
"9876": "9876"
"9877": "9877"
"9878": "9878"
以上為某個leaf page的內容,底部為該page中儲存的key/value對。
推薦閱讀
- 一文讀懂clickhouse叢集監控
- redis實現分散式鎖
- C/C++關鍵字之restrict
- 現代C++之右值語義
- 30分鐘入門Vim
- 30分鐘入門GDB
- STL原始碼分析--vector
- zookeeper client原理總結
- 推薦幾個好用的效率神器
- Python亂碼九問
- Linux Shell指令碼攻略讀書筆記
更多精彩內容,請掃碼關注微信公眾號:後端技術小屋。如果覺得文章對你有幫助的話,請多多分享、轉發、在看。