日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

存储引擎 boltdb 的设计奥秘?

發(fā)布時間:2024/8/23 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 存储引擎 boltdb 的设计奥秘? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

作者 |?奇伢

來源 |?奇伢云存儲

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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。