記憶體設計
根據之前論文之中所寫,我們在記憶體中需要儲存一份key所對應value的對映位置便於以後讀出,在記憶體中儲存的資料結構有許多,例如hash、跳錶和b tree等。 在教程中我們使用的是b tree便於以後很方便的遍歷key。
資料結構的話直接拿現成的包直接用就行了
btree庫: https://github.com/google/btree
首先定義一個資料結構用於表示key儲存在檔案的位置資訊
// LogRecordPos 資料記憶體索引資訊 主要是根據key找到指定檔案的指定位置讀取指定資料
type LogRecordPos struct {
FileId uint32 // 檔案id
Pos int64 // 資料偏移
}
然後根據物件導向的思想定義一個介面,該介面用於操作記憶體索引,如果之後需要實現其他資料結構,直接實現該介面就行了。
type Indexer interface {
Put(key []byte, pos *data.LogRecordPos) bool // 設定值
Get(key []byte) *data.LogRecordPos // 獲取資料的位置資訊
Delete(key []byte) bool // 刪除kv資料
}
// Item 表示 btree中的資料結構,需要實現了該type才能在Btree中新增資料
type Item struct {
key []byte
pos *data.LogRecordPos
}
func (item *Item) Less(than btree.Item) bool {
// (*Item).key表示斷言 如果than實現了btree.Item那麼就轉換成Item
return bytes.Compare(item.key, than.(*Item).key) == -1
}
採用Btree資料結構實現了Indexer介面
type Btree struct {
tree *btree.BTree
lock *sync.RWMutex
}
func NewBtree() *Btree {
return &Btree{
tree: btree.New(64),
lock: new(sync.RWMutex),
}
}
func (btree *Btree) Put(key []byte, pos *data.LogRecordPos) bool {
btree.lock.Lock()
item := &Item{
key: key,
pos: pos,
}
btree.tree.ReplaceOrInsert(item)
btree.lock.Unlock()
return true
}
func (btree *Btree) Get(key []byte) *data.LogRecordPos {
item := &Item{key: key}
getItem := btree.tree.Get(item)
if getItem == nil {
return nil
}
return getItem.(*Item).pos
}
func (btree *Btree) Delete(key []byte) bool {
btree.lock.Lock()
item := &Item{
key: key,
}
btree.tree.Delete(item)
btree.lock.Unlock()
return true
}
磁碟
磁碟一樣使用一個抽象的介面,如果以後需要實現自己的檔案引擎直接實現該介面就行。
type IOManagement interface {
// Read 檔案讀取 讀取的內容會儲存到buffer中並返回讀取位元組數
Read(offset int64, buffer []byte) (int, error)
// Write 寫資料到檔案中 buffer表示寫入內容 返回寫入位元組數
Write(buffer []byte) (int, error)
// Sync 將快取區內容同步到檔案中
Sync() error
// Close 關閉檔案
Close() error
}
使用最簡單的go的庫檔案即可。
type FileIO struct {
file *os.File
}
func CreateFileIo(filePath string) (*FileIO, error) {
file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return nil, err
}
return &FileIO{
file: file,
}, nil
}
func (fileIo *FileIO) Read(offset int64, buffer []byte) (int, error) {
_, err := fileIo.file.Seek(offset, 0)
if err != nil {
return 0, err
}
return fileIo.file.Read(buffer)
}
func (fileIo *FileIO) Write(buffer []byte) (int, error) {
return fileIo.file.Write(buffer)
}
func (fileIo *FileIO) Sync() error {
return fileIo.file.Sync()
}
func (fileIo *FileIO) Close() error {
return fileIo.file.Close()
}