Bolt源码解析(一):系统架构及数据结构

LevelDB 和 BoltDB 都是k/v存储,LevelDB的实现是基于LSM树,没有事务,LevelDB实现了一个日志结构化的merge tree,将随机的写变成顺序写,每次都把数据写入新文件。

LSM树而且通过批量存储技术规避磁盘随机写入问题。 LSM树的设计原理是把一颗大树拆分成N棵小树, 数据首先写入到内存中,在内存中构建一颗有序小树,随着小树越来越大,内存的小树会flush到磁盘上。磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。

Bolt 是受 LMDB 项目启发的一个纯粹的 Go key/value数据库,BoltDB使用一个单独的内存映射的文件(.db),实现一个写入时拷贝的B+树,这能让读取更快。BoltDB的载入时间很快,它不需要通过读log恢复事务,它仅仅从两个B+树的根节点读取ID就可以加载出完整数据。具有以下特点:

1、直接使用API接口存取数据,没有查询语句。

2、支持完全可序列化的ACID事务

3、数据保存在内存映射的文件里。没有wal、线程压缩和垃圾回收。

4、通过COW技术保证即使在机器异常时也可以达到数据一致性,读写可并发,但是写写不能并发,适合与读多写少的场景。

ACID:事务具有4个特征,分别是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability),简称事务的ACID特性。

COW:CopyOnWrite,写时复制

整体结构图:

                          

Bolt就是将db文件通过mmp映射到内存,在db中创建bucket,每个bucket相当于一张表,在bucket中记录kv。插入删除kv通过rwtx,查询kv通过rtx。数据在磁盘存放在page中,page分为几种类型:

MetaPage:记录根元数据,通过此元数据可以遍历出所有数据

branchPage:记录非叶子节点

leafPage:记录叶子节点,即最终的kv

freelistPage:记录空闲可用的page。

为了加快操作速度,将page在内存对应的数据组织称node结构,node就是b+tree。使用cursor在page和node之间来回查询直到结束。

1、主要内存数据结构:

type DB struct {
	//提交数据时是否进行校验
	StrictMode bool

	// 提交数据后是否进行fsync操作
	NoSync bool

	NoGrowSync bool

	// 是否开启文件到内存的映射
	MmapFlags int

	// 一次提交最大数据量. 默认值 DefaultMaxBatchSize 
	MaxBatchSize int

	// 批处理最大时延
	MaxBatchDelay time.Duration

	// db文件一次增长量
	AllocSize int

	path     string//db文件路径
	file     *os.File//db文件打开句柄
	lockfile *os.File // windows only
	dataref  []byte   // mmap'ed readonly, write throws SEGV
	data     *[maxMapSize]byte//文件到内存映射对应的buf
	datasz   int
	filesz   int // current on disk file size
	meta0    *meta//两个根元数据之一
	meta1    *meta//两个根元数据之一
	pageSize int//page 大小
	opened   bool
	rwtx     *Tx//读写事务
	txs      []*Tx//读事务,可以多个
	freelist *freelist//freepage 记录链表
	stats    Stats

	pagePool sync.Pool//page 内存池

	batchMu sync.Mutex
	batch   *batch

	rwlock   sync.Mutex   // Allows only one writer at a time.
	metalock sync.Mutex   // Protects meta page access.
	mmaplock sync.RWMutex // Protects mmap access during remapping.
	statlock sync.RWMutex // Protects stats access.

	ops struct {
		writeAt func(b []byte, off int64) (n int, err error)//db写接口
	}

	// 以只读模式打开的db
	readOnly bool
}



type Bucket struct {
	*bucket
	tx       *Tx                // 对应的事务
	buckets  map[string]*Bucket // 具体执行读写的bucket
	page     *page              // inline page reference
	rootNode *node              // page对应内存node的入口
	nodes    map[pgid]*node     // node cache

	// 节点分裂时使用
	FillPercent float64
}

// bucket represents the on-file representation of a bucket.
// This is stored as the "value" of a bucket key. If the bucket is small enough,
// then its root page can be stored inline in the "value", after the bucket
// header. In the case of inline buckets, the "root" will be 0.
type bucket struct {
	root     pgid   // 根page id,如果roo是0就表明该bucket是inline的
	sequence uint64 // monotonically incrementing, used by NextSequence()
}


type Cursor struct {
	bucket *Bucket//查找目标
	stack  []elemRef//记录查找路径
}

// elemRef represents a reference to an element on a given page/node.
type elemRef struct {
	page  *page//查找最终结果对应的page 
	node  *node//查找最终结果对应的node
	index int//所在page/node中的位置
}

// node represents an in-memory, deserialized page.
type node struct {
	bucket     *Bucket
	isLeaf     bool//是否叶子节点
	unbalanced bool
	spilled    bool//是否spil过
	key        []byte
	pgid       pgid//page id
	parent     *node//上一层的反向指针
	children   nodes//该节点下一层子节点
	inodes     inodes//如果是叶子节点记录kv
}

// inode represents an internal node inside of a node.
// It can be used to point to elements in a page or point
// to an element which hasn't been added to a page yet.
type inode struct {
	flags uint32
	pgid  pgid
	key   []byte
	value []byte
}

// freelist represents a list of all pages that are available for allocation.
// It also tracks pages that have been freed but are still in use by open transactions.
type freelist struct {
	ids     []pgid          // all free and available free page ids.
	pending map[txid][]pgid // mapping of soon-to-be free page ids by tx.
	cache   map[pgid]bool   // fast lookup of all free and pending page ids.
}

type Tx struct {
	writable       bool//是否写事务
	managed        bool
	db             *DB//对应的db
	meta           *meta
	root           Bucket
	pages          map[pgid]*page//本事务涉及的变化需要下盘的page
	stats          TxStats
	commitHandlers []func()

	// WriteFlag specifies the flag for write-related methods like WriteTo().
	// Tx opens the database file with the specified flag to copy the data.
	//
	// By default, the flag is unset, which works well for mostly in-memory
	// workloads. For databases that are much larger than available RAM,
	// set the flag to syscall.O_DIRECT to avoid trashing the page cache.
	WriteFlag int
}

2、物理结构

2.1 MetaPage

const (
    branchPageFlag   = 0x01
    leafPageFlag     = 0x02
    metaPageFlag     = 0x04
    freelistPageFlag = 0x10
)
type pgid uint64

type page struct {
    id       pgid
    flags    uint16
    count    uint16
    overflow uint32
    ptr      uintptr
}
type meta struct {
    magic    uint32
    version  uint32
    pageSize uint32
    flags    uint32
    root     bucket//磁盘上根page id
    freelist pgid  //磁盘上空闲page id
    pgid     pgid
    txid     txid
    checksum uint64
}
// meta returns a pointer to the metadata section of the page.
func (p *page) meta() *meta {
    return (*meta)(unsafe.Pointer(&p.ptr))
}

Metapage一共两个,每次数据变更这两个page轮流使用,使用哪个page是根据meta中的txid,数据变化时txid会递增。

2.2 branchPage

type branchPageElement struct {
    pos   uint32   //key本page内偏移
    ksize uint32   //key大小
    pgid  pgid    //key对应的page id
}
// branchPageElement retrieves the branch node by index
func (p *page) branchPageElement(index uint16) *branchPageElement {
    return &((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[index]
}

// branchPageElements retrieves a list of branch nodes.
func (p *page) branchPageElements() []branchPageElement {
    if p.count == 0 {
        return nil
    }
    return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:]
}

 branchPage存储b+tree的非叶子节点, count 表示后面多少个 branchPageElement。branchPageElement 中的 pos 和 ksize 用于定位当前页中 key 的位置,由于本page是非叶子节点,找到该key会进入下一级,pgid 代表 key 对应下一级的page id,这个 key 该节点管理范围内,最小的叶子节点key。

2.3 leafPage

type leafPageElement struct {
    flags uint32
    pos   uint32  //kv在本page内偏移
    ksize uint32  //key大小
    vsize uint32  //value大小
}
// leafPageElement retrieves the leaf node by index
func (p *page) leafPageElement(index uint16) *leafPageElement {
    n := &((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[index]
    return n
}
// leafPageElements retrieves a list of leaf nodes.
func (p *page) leafPageElements() []leafPageElement {
    if p.count == 0 {
        return nil
    }
    return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:]
}

 

2.4 freelistPage

count 表示后面的元素个数,如果flags是 freelist,表示 page id 个数。由于 count 是 2 bytes, 所以如果 count 值为 0xffff,那么紧临的第一个 int64 就不是 page id 而是真正的个数

 

 

 

 

 

发布了48 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/cyq6239075/article/details/105596196