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 而是真正的個數

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章