存储引擎 boltdb 的设计奥秘?
作者 |?奇伢
來源 |?奇伢云存儲
etcd 的存儲
etcd v3 是使用的持久化存儲來存儲它的 kv 數(shù)據(jù),etcd ?存儲的是非常核心的元數(shù)據(jù)信息,所以最重要的是穩(wěn)定。使用的是 boltdb 。下面說道說道這個 boltdb 。
boltdb 是什么?
boltdb 是一個非常出名的存儲引擎,純 Go 語言實現(xiàn)的 KV 存儲引擎。
boltdb 項目非常值得學(xué)習(xí),封裝的 API 簡單,內(nèi)部實現(xiàn)很精巧。整個項目去掉注釋,測試代碼啥的,就幾千行代碼。Github 地址為 https://github.com/boltdb/bolt 。但 boltdb 項目已經(jīng)由原作者封版了,不再迭代更新。
etcd 自己 fork 了一個 boltdb 分支出來,上面做了一些自己的小優(yōu)化。
boltdb 啟發(fā)于 Howard Chu's LMDB[1] 項目,感興趣的也可以去看下。
特點:
整個數(shù)據(jù)庫就一個 db 文件,賊簡單;
基于 B+ 樹的索引,讀效率高效且穩(wěn)定;
讀事務(wù)可多個并發(fā),寫事務(wù)只能串行;
缺點:
事務(wù)的實現(xiàn)賊簡單,但是寫的開銷太大;
boltdb 寫事務(wù)不能并發(fā),只能靠批量操作來緩解性能問題;
下面我們從外到內(nèi)一步步探索下 boltdb 的實現(xiàn)。
boltdb 看起來是什么樣子?
整個 db 就一個單文件,只不過這個文件內(nèi)容是有格式的。先用 hexdump 看一眼:
仔細的童鞋會發(fā)現(xiàn),這個數(shù)據(jù)間隔有點意思?
0000????//?0???偏移 1000????//?4k??偏移 2000????//?8k??偏移 3000????//?12k?偏移 4000????//?16k?偏移 5000????//?16k?偏移每個偏移都是 4k 的間隔,里面還有一些看不懂的二進制數(shù)據(jù)。以前奇伢說過,越往底層都會有一個存儲單元的概念,因為要合并一些邊際開銷,比如,文件系統(tǒng)大多以 4k 為單位進行管理,page cache 也以 4k 為單位管理。再看硬件,也是如此,磁盤的最小處理單位是扇區(qū)( 512字節(jié) ),ssd 的讀寫是以 4k 為單位管理的。
boltdb 作為一個存儲引擎,自然要統(tǒng)籌管理空間的使用,自然也有這么一個概念。boltdb 以 4k 定長為存儲單元劃分空間,這一個個 4k 叫做 page,boltdb 在上面建立更抽象的概念。
來看看 boltdb 怎么管理空間的吧。
怎么管理空間?
上面提到是以 4k 為粒度來管理空間的,每個 4k 叫做 page 。
?1???page 頁
為了方便管理,page 自然也是會有格式的,每個 page 都會有 header ,header 后面是 data 數(shù)據(jù)。
//?header?結(jié)構(gòu)體 type?page?struct?{id???????pgid???????//?page?編號flags????uint16??????//?標明?page?的屬性count????uint16??????//?標明?page?上有多少個元素overflow?uint32???????//?標明后面是不是還有連續(xù)的頁跟著ptr??????uintptr??????//?這就是個用來定界的 }示意圖:
從物理層面來說
boltdb 的 db 文件來說就是由這樣一個個 page 組成的。舉個例子,如果是一個 32K 的文件,那么就由 8 個 page 組成,每個 page 都有自己的唯一編號( pgid ),從 0 到 7 。
從邏輯層面來說
boltdb 把這一個個 page 組成了一個樹形結(jié)構(gòu),它們之間通過 page id 關(guān)聯(lián)起來。我們再往下思考:
第一個點:樹自然會有個源頭,比如從那個 page 開始索引,還有一些最關(guān)鍵的元數(shù)據(jù)( meta 數(shù)據(jù) );
第二個點:既然是一顆樹,那么自然有中間節(jié)點、葉子節(jié)點;
第三個點:既然是空間管理,那么自然要知道哪些是存儲了用戶數(shù)據(jù) page ,哪些是空閑的 page ;
上面提到的三個點都指向一個結(jié)論:page 的用途是不一樣的。也就是說,雖然大家都是 page,但是身份不一樣。有的是葉子節(jié)點,有的是中間節(jié)點,有的是 meta 節(jié)點,有的是 free 節(jié)點。這個由 page.flag 來標識。
const?(branchPageFlag???=?0x01leafPageFlag?????=?0x02metaPageFlag?????=?0x04freelistPageFlag?=?0x10 )下面分開聊聊這幾種 page 頁。
?2???meta page
元數(shù)據(jù)的 page ,這可太重要了。對于 boltdb 來說,meta 的 page 位置是固定的,就在 page 0,page 1 這兩個位置( 也就是前兩個 4k 頁 )的位置。一切索引從此開始,簡單看下里面的數(shù)據(jù)含義:
Root :指明樹根的位置;
Freelist :指明空閑列表的位置;
Txn :事務(wù)編號(寫事務(wù)的時候,事務(wù)號會遞增);
有童鞋可能會疑惑了,為什么會有兩個 meta 頁?
這是一個非常重要的設(shè)計,在 boltdb 里有一個非常重要的設(shè)計:沒有覆蓋寫,也就是不會原地更新數(shù)據(jù)。這個是 boltdb 實現(xiàn) ACID 事務(wù)的秘密。
以前也提過,覆蓋寫是數(shù)據(jù)損壞的根源之一。因為寫數(shù)據(jù)的時候可能會出現(xiàn)任何異常,比如寫部分成功,部分失敗 這種就不符合事務(wù)的 ACID 原則。
但由于 meta 是 boltdb 一切的源頭,所以它必須是固定位置( 不然就找不到它 )。但為什么會有 paid 0,1 兩個位置呢?
訣竅就在于:通過輪轉(zhuǎn)寫來解決覆蓋寫的問題。 每次 meta 的更新都不會直接更新最新的位置,而是寫上上次的位置。
//?計算?page?id?的位置p.id?=?pgid(m.txid?%?2)
舉個例子:
事務(wù) 0 寫 page 0 ;
事務(wù) 1 寫 page 1 ;
事務(wù) 2 寫 page 0 ;
事務(wù) 3 寫 page 1 ;
?3???branch page
branch 的 page 就是做了樹的葉子節(jié)點,這個沒啥講的,里面就是存儲的 branch 的節(jié)點。本質(zhì)也是 key/value,key 是用戶的 key,只不過 value 是 page 的索引而已。看一下結(jié)構(gòu)體:
?4???leaf page
葉子節(jié)點里面主要存儲的是用戶的數(shù)據(jù),這個沒啥講的,一堆 key/value ,key 是用戶的 key,value 是用戶的數(shù)據(jù)。
?5???freelist page
所謂 freelist page 也就是說 page 里面存儲的是一個個 pgid ,這些個 pgid 都是空閑可用的 page 的 id 。當(dāng)寫事務(wù)需要空閑的 page 存儲數(shù)據(jù)的時候,就可以從這個里面撈一個來用。
怎么索引數(shù)據(jù)的?
現(xiàn)在我們知道了存儲的管理單元是 page,每個都由 header + data 組成,page 的類型則決定了 data 里面裝什么數(shù)據(jù)。最主要是四種 page :
meta 的數(shù)據(jù);
中間節(jié)點的數(shù)據(jù)(主要是索引數(shù)據(jù));
葉子節(jié)點,存儲的是 key/value 數(shù)據(jù)( 有意思的是這里的 key/value 也是有講究的,既可能是用戶的 key/value 數(shù)據(jù),也可能是 bucket 的結(jié)構(gòu)數(shù)據(jù) );
freelist 的數(shù)據(jù),里面存儲的是一個個 pgid ;
那現(xiàn)在我們看 boltdb 是怎么來組織 page,索引這些數(shù)據(jù)。
?1???B+ 樹 ?
都說 boltdb 用的是 B+ 樹的形式,說的也是對的,但是 boltdb 的 B+ 樹有些變異,幾點差異如下:
節(jié)點的分支個數(shù)不是固定值;
葉子節(jié)點不相互感知,它們之間不存在相互的指向引用;
并不保證所有的葉子節(jié)點在同一層;
劃重點:boltdb 它用的是一個不一樣的 B+ 樹。?除了上面的,索引查找和數(shù)據(jù)組織形式都是 B+ 樹的樣子。
在 boltdb 里面有幾個封裝的概念:
Bucket :這是一個 boltdb 封裝的一個抽象概念,但本質(zhì)上呢它就是個命名空間,就是一些 key/value 的集合,不同的 Bucket 可以有同名的 key/value ;
node :B+ 樹節(jié)點的抽象封裝,可以說 page 是磁盤物理的概念,node 則是邏輯上的抽象了;
在 boltdb 中,Bucket 是可以嵌套的,這一點也帶來了很大的靈活性,同樣也是代碼略微難懂的地方。其實 boltdb 天生就有一個 Bucket ,這個是自動生成的,由 meta 指向,不是用戶創(chuàng)建的,后續(xù)創(chuàng)建的 Bucket 都是這個 Bucket 的 subbucket 。
怎么實現(xiàn)的事務(wù)?
劃重點:boltdb 實現(xiàn)事務(wù)的方式非常簡單,就是絕對不覆蓋更新數(shù)據(jù)。 其中 meta 是通過兩個互為備份的 page 頁輪轉(zhuǎn)寫實現(xiàn)的,數(shù)據(jù)頁又是怎么實現(xiàn)的呢?
秘密就是:每次都寫新的地方,最后更改路徑引用。我們來看一下它是怎么做的?看一下演繹的過程:
?1???現(xiàn)狀一棵樹
假如當(dāng)前如下,有個 key/value 鍵值對( "hello", "world" )存儲在一個葉子 page 上。
?2???更新前先找位置
現(xiàn)在用戶要更新這個 key="hello" 的值,更新 value 為 "qiya" ,那么很自然的,開啟一個寫事務(wù),事務(wù)號遞增+1,boltdb 需要通過 B+ 樹的搜索算法定位到葉子節(jié)點。
?3???定位到后,讀改寫
定位到 node 節(jié)點之后,怎么修改呢?這個節(jié)點里面可不止這一對 key/value,里面還有很多 key/value 。
做法很簡單,讀改寫!也就是先把這個 node 對應(yīng)的 page 讀到內(nèi)存,把所有的 key/value 加載出來,然后把 key="hello" 的值更新到 value="new_world" ,并且寫到新的 page 里。
那問題來了,葉子節(jié)點更新了,那指向這個葉子節(jié)點的 branch 節(jié)點要不要更新呢 ?
自然是要的。那 branch 節(jié)點更新了,那 branch 的 branch 節(jié)點要不要更新呢?自然是要的。所以這一層層往上推,最終要更新到 meta page 的,也就是樹根。
?4???最后切換樹根,被新?page?替換的最終會被釋放
最終這些被替換的 page 就會被納入到 freelist 里面,完全沒有事務(wù)引用的話就會被釋放。
?5???小結(jié)一下
上面就是寫事務(wù)的方式,總結(jié)一下:
通過 key 查詢到位于 B+ 樹的那個 page ;
把這個 page 讀出來,構(gòu)建 node 節(jié)點,更新 node 的內(nèi)容;
把 node 的內(nèi)容寫到空閑的的 page ,并且一層層往上;
最終更新 meta 索引內(nèi)容;
這里我先提一點個人的思考,很多人提到 boltdb 這是一種 cow ( copy-on-write )的方式,但其實我更偏向于把這個理解成 row 的方式。你覺得呢?
總結(jié)
etcd 使用 boltdb 來做存儲引擎,讀性能還行,寫性能很一般,但是它穩(wěn);
boltdb 的物理存儲單元是 page ,一般為 4k 一個;
page 有不同的類型,里面的內(nèi)容根據(jù)類型不一樣而不同;
boltdb 使用 row 的方式(個人理解),保證無覆蓋寫,等到 commit 的時候,最終修改引用,從而切換整個 db 的路徑。這樣的方式幾乎是 0 成本實現(xiàn)的 ACID 事務(wù);
meta 的 page 有兩個,它們通過輪轉(zhuǎn)寫來實現(xiàn)的不覆蓋寫,從而保證了數(shù)據(jù)更新的安全;
B+ 樹相比 LSM Tree 天然就有隨機讀的性能優(yōu)勢,它的樹高度穩(wěn)定,boltdb 通過 mmap 把文件映射到內(nèi)存,這樣簡化了代碼,讀的緩存交給了操作系統(tǒng)的 page cache ;
boltdb 寫性能可能很差,因為只要改了一點點東西,都會導(dǎo)致這個節(jié)點到 root 節(jié)點整條鏈路的更新,寫放大挺嚴重的,所以它只能靠 batch 操作來安慰自己;
參考資料
[1]
LMDB Github 地址: https://github.com/LMDB
往期推薦
云計算到底是誰發(fā)明的?
從Docker的信號機制看容器的優(yōu)雅停止
Redis會遇到的坑,你踩過幾個?
低代碼平臺會帶動企業(yè)的組織變革嗎?
點分享
點收藏
點點贊
點在看
總結(jié)
以上是生活随笔為你收集整理的存储引擎 boltdb 的设计奥秘?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 13 第一个开发者版本来
- 下一篇: 2017双11技术揭秘—阿里巴巴数据库技