暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

key-value数据库bbolt深入分析:简介、文件头格式

零君聊软件 2020-08-12
3687

BoltDB是一个用golang实现的开源的key-value数据库,

    https://github.com/boltdb/bolt
    复制

    由于BoltDB的原始作者Ben Johnson没有精力继续维护这个项目,所以他封存了这个项目,目前这个项目是read-only状态。最后一个commit也已经是差不多两年半之前的事了。但是从这个项目的star/fork数量10.9k/1.3k,可以看出这个项目还是很受欢迎的。

    之所以文章开头特意提这一段历史,是因为我觉得Ben Johnson是值得尊敬的大师级软件工程师。


    后来,CoreOS公司fork了BoltDB,继续维护这个项目。不过名字改成了bbolt,开源地址如下:

      https://github.com/etcd-io/bbolt
      复制


      此系列主要是对CoreOS公司fork的这个项目bbolt进行深入分析,本文是该系列文章的第一篇。

      CoreOS于2018年1月被Red hat收购,而Red hat又在2019年7月被IBM收购了。


      bbolt简介

      文章开头已经对bbolt的历史来源做了一个简要的介绍。可能很多人压根就没有听说过bbolt,但是bbolt已经被很多其它项目所依赖,而且用到了生产环境中,其中就包含知名的consul、InfluxDB以及etcd。零君也是在分析etcd的源码过程中才知道etcd的存储实际用到了bbolt,因而也才会进一步深入分析bbolt。

      注:目前consul依赖的依然是BoltDB,而并非bbolt。


      我们通常所熟知的各种数据库,如MySQL、PostgreSQL、etcd等,都是以独立的服务/进程的形式运行,客户应用程序通过网络连接来与数据库服务器交互。而BoltDB/bbolt的定位只是提供一个key-value的存储,以library的形式提供给客户应用程序使用,最终是运行在客户程序的进程空间中。如果想在分布式环境中使用bbolt,就要对其做进一步的改进并封装;etcd就是在bbolt的基础上,针对分布式环境做了大量的改进和封装。


      bbolt使用示例

      首先站在用户(developer)的角度来看一个如何使用bbolt的示例(golang代码)。

        package main


        import (
        "bytes"
        "log"


        bolt "go.etcd.io/bbolt"
        )


        func main() {
        db, err := bolt.Open("my.db", 0600, nil)
        if err != nil {
        log.Fatal(err)
        }
        defer db.Close()


        if err := db.Update(func(tx *bolt.Tx) error {
        b, err := tx.CreateBucket([]byte("b1"))
        if err != nil {
        log.Fatal(err)
        }
        if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
        log.Fatal(err)
        }
        if v := b.Get([]byte("foo")); !bytes.Equal(v, []byte("bar")) {
        log.Fatalf("unexpected value: %v", v)
        }
        return nil
        }); err != nil {
        log.Fatal(err)
        }
        }


        复制


        上面的例子中,DB.Update()创建了一个read-write事务,在回调函数体内就可以保证始终可以看到一致的数据库视图。首先创建了一个名称为"b1"的bucket (在bbolt中,bucket是一些key-value对的集合)。然后在这个bucket内添加了一个key-value对:"foo"/"bar",最后重新读取key为"foo"的值,检查是否等于之前保存的值。


        文件头格式

        与其它数据库一样,bbolt数据文件的存储结构可以分成header和body两部分。这里要注意一点,数据库文件存储空间的分配是以页面(page)为单位进行分配的。如果没有指定页面大小,默认就通过系统调用获取os的页面大小:

          // default page size for db is set to the OS page size.
          var defaultPageSize = os.Getpagesize()
          复制

          通常一个页面的大小是4K,所以数据库文件的大小一般是4K的整数倍。


          header的大小虽然不到4K,但是由于最小分配单位是页面,所以开始也分配了4K的空间。header主要包含page header与metadata,定义分别如下:

            // page header
            type page struct {
            id pgid
            flags uint16
            count uint16
            overflow uint32
            }


            // metadata
            type meta struct {
            magic uint32
            version uint32
            pageSize uint32
            flags uint32
            root bucket
            freelist pgid
            pgid pgid
            txid txid
            checksum uint64
            }
            复制


            图示如下:


            第一个page的编号是0,所以pgid的值为0。flag的值0x04表示这是一个metadata的page。源码中对flag的定义如下:

              const (
              branchPageFlag = 0x01
              leafPageFlag = 0x02
              metaPageFlag = 0x04
              freelistPageFlag = 0x10
              )
              复制


              count和overflow这两个field对metadata page没有作用,暂时忽略。meta中的各个field含义分别如下:

              Field name
              Description
              magic目前固定是 0xED0CDAED
              version
              数据文件格式版本号,目前是2
              pageSize
              页面大小,不同的CPU和系统可能会有不同的值,通常是4096 (4K)
              flags
              代码中未看到对该字段的使用,应该是保留字段
              root
              root bucket(根bucket)的信息,主要包含root buket对应的page ID;数据文件刚创建时,root bucket是第4个page,所以编号是3    
              freelist
              空闲page ID列表所保存的pgid,刚开始是用第3个页面来保存所有空闲pgid,所以编号是2
              pgid
              当前数据文件总共有多少个page,刚开始时有4个page。    
              txid
              到目前为止,最大的trancation ID
              checksum
              metadata数据的校验和


              为了防止metadata page由于各种原因被破坏,所以定义了两个metadata page,分别是编号为0、1的page。第二个metadata page格式如下:


              bbolt数据文件除了header之外,最主要的就是body了。body包含两种类型的数据:空闲页面列表与key-value数据。bbolt数据文件刚初始化的时候,会将第三个page(编号为2)分配给空闲页面链表,将第四个page(编号为3)分配给一个叶子节点(存储key-value数据)。所以一个刚创建并初始化的bbolt数据文件的大小通常是16K。关于文件body,后面会详细描述。


              --END--

              文章转载自零君聊软件,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论