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