打造先進的內存KV數據庫-1 B樹索引的建立(1)

設計目的

在搜索引擎的設計中,往往需要使用倒排索引,在當前內存價格不斷走低的情況下,內存數據庫必然會成爲主流。KV數據庫由於適合Map-Reduce用於分佈式處理。
本系統設計實現如下目標:
* 實現極高性能的查詢
* 實現分佈式集羣儲存
* 實現可靠的日誌系統

索引設計

索引採用B數索引,這樣做的目的是大大利用CPU的緩存,讓每個節點的大小與CPU二級緩存相匹配,另外,將索引值連續儲存在節點中,可以減少TLB失配的次數。由於B樹的結構,樹高不會太高,設想在16GB的內存中建立數據庫,數據庫使用8G內存,每條記錄1K,那麼就有8M個鍵,如果是每個節點儲存256個節點的B樹,那麼log256(8M)=3,樹高是3,每次查詢節點跳轉最多2次,節點內二分查找是在高速緩存中進行,高速緩存的效率大約是裝載內存讀取的4~5倍(6000次查詢),節點內二分查找的最大次數爲8次,數據可能可以優化,在後續實驗中進行。
對於爲搜索引擎定製的數據庫,對於插入的性能要求不是很高,之前做過的實驗表明,爬取的性能瓶頸在於網絡IO,網絡延遲遠遠大於數據庫插入,故可以犧牲插入性能來獲得較高的查詢性能。

代碼實現

以下是B樹索引的查詢以及部分插入實現:

//b_tree.go
package index


/*
    最大程度提高查詢效率,忽略插入效率,插入效率被爬蟲爬取效率瓶頸
*/

const (
    L2_cache_size = 256*1024            //256KB L2緩存
    node_data_num = L2_cache_size / 64
    max_support_key_length = 8          //最大支持8個字節的關鍵字查詢
)

type node struct {
    hash_key [node_data_num]uint64      //1/4
    hash_key_num int
    primary_key [node_data_num]uint64   //1/4
    child [node_data_num + 1]*node      //1/4
    is_leaf bool                        //0
}

type B_Tree struct {
    root node   //樹根
    height int  //樹高
}

func (bt * B_Tree) Init() {
    bt.height = 1
    bt.root.is_leaf = false
    bt.root.hash_key_num = 0
}

type SelectError struct {
    less bool
}

func (se SelectError) Error() (string) {
    return "not found"
}

//獲得hash值
//取前8個字節作爲hash
func hash(text []byte) (uint64) {
    hash_ := uint64(0)
    length := len(text)
    if length > max_support_key_length {
        length = max_support_key_length
    }
    for i := 0;i < length;i++ {
        hash_ *= 256
        hash_ += uint64(text[i])
    }
    return hash_
}

//查找指定索引 如果找到返回找到的node和position,失敗的話返回最近的node和position,
//並返回返回值相對於查詢值是大了還是小了 true->小 false->大 優先返回大 供插入時插在前面
func (bt *B_Tree) Select(index []byte) (*node,int,uint64,error) {
    if bt.root.hash_key_num == 0 {
        return &bt.root,0,0,SelectError{false}
    }
    hash_key := hash(index)
    node_ := &bt.root
    pos_r := 0                                  //返回位置
    var err SelectError                             //返回錯誤
    var node_r *node
    for node_ != nil{
        if node_.hash_key_num > 0 {
            //超出節點邊界
            if node_.hash_key[0] > hash_key {
                node_r = node_              //返回node,防止變成nil
                pos_r = 0
                err.less = false                    //返回大於
                node_ = node_.child[0]
                break
            }
            if node_.hash_key[node_.hash_key_num - 1] < hash_key {
                node_r = node_              //返回node,防止變成nil
                pos_r = node_.hash_key_num
                err.less = true                 //返回小於
                node_ = node_.child[node_.hash_key_num]
                break
            }
            //進行二分查找
            pos := node_.hash_key_num / 2
            c := node_.hash_key_num / 2
            for {
                if node_.hash_key[pos] == hash_key {
                    return node_,pos,node_.primary_key[pos],nil //找到
                } else if node_.hash_key[pos] < hash_key {
                    if node_.hash_key[pos + 1] > hash_key {
                        node_r = node_              //返回node,防止變成nil
                        pos_r = pos + 1
                        err.less = false                    //返回大於
                        node_ = node_.child[pos]
                        break
                    }
                    c /= 2
                    if c == 0 { c=1 }
                    pos += c
                } else if node_.hash_key[pos] > hash_key {
                    if node_.hash_key[pos - 1] < hash_key {
                        node_r = node_              //返回node,防止變成nil
                        pos_r = pos
                        err.less = false                    //返回大於
                        node_ = node_.child[pos - 1]
                        break
                    }
                    c /= 2
                    if c == 0 { c=1 }
                    pos -= c
                }
            }
        }
    }
    return node_r,pos_r,0,err
}

func (bt *B_Tree) Set(text []byte,primary_key uint64) (error) {
    node_,pos,_,err := bt.Select(text)  //查詢
    if err == nil {                         //找到了
        //TODO 和儲存引擎協調進行
    } else {
        //TODO 儲存引擎加入記錄
        if node_.hash_key_num < node_data_num { //有空間可以插入
            if err.(SelectError).less {         //插在最後一個:只有查詢到最後一個還小的時候纔會返回less
                node_.hash_key[node_.hash_key_num] = hash(text)
                node_.primary_key[node_.hash_key_num] = primary_key
                node_.hash_key_num++
            }else {                             //插在前面
                for i := node_.hash_key_num;i > pos;i-- {
                    node_.hash_key[i] = node_.hash_key[i - 1]
                    node_.primary_key[i] = node_.primary_key[i - 1]
                }
                node_.hash_key[pos] = hash(text)
                node_.primary_key[pos] = primary_key
                node_.hash_key_num++
            }
        }
        //TODO 空間不足的情況
    }
    return nil
}

單元測試(未測試多層~待續)

package index
import (
    _"fmt"
    "testing"
)

func expected(t *testing.T,expecting uint64,real uint64) {
    if expecting != real {
        t.Error("-Expected-")
        t.Error(expecting)
        t.Error("-Real-")
        t.Error(real)
    }
}

func Test_hash(t *testing.T) {
    expected(t,15108241,hash([]byte("我")))
    expected(t,14990752,hash([]byte("你")))
    expected(t,14990230,hash([]byte("他")))
    expected(t,0,hash([]byte{}))
}

func Test_Select(t *testing.T) {
    bt := new(B_Tree)
    bt.Init()
    bt.root.hash_key[2] = 15108241
    bt.root.hash_key[1] = 14990752
    bt.root.hash_key[0] = 14990230
    bt.root.hash_key_num = 3
    bt.root.primary_key[2] = 2
    bt.root.primary_key[1] = 1
    bt.root.primary_key[0] = 0
    _,_,i,_ := bt.Select([]byte("我"))
    expected(t,2,i)
    _,_,i,_ = bt.Select([]byte("你"))
    expected(t,1,i)
    _,_,i,_ = bt.Select([]byte("他"))
    expected(t,0,i)
    _,_,i,_ = bt.Select([]byte("呵呵"))
    expected(t,0,i)
    _,_,i,_ = bt.Select([]byte("呼呼"))
    expected(t,0,i)
}

func Test_Set(t *testing.T) {
    bt := new(B_Tree)
    bt.Set([]byte("我"),0)
    _,_,i,_ := bt.Select([]byte("我"))
    expected(t,0,i)
    bt.Set([]byte("蔡佳楠"),1)
    _,_,i,_ = bt.Select([]byte("蔡佳楠"))
    expected(t,1,i)
    bt.Set([]byte("楊明亮"),2)
    _,_,i,_ = bt.Select([]byte("楊明亮"))
    expected(t,2,i)
    bt.Set([]byte("楊奕輝"),3)
    _,_,i,_ = bt.Select([]byte("楊奕輝"))
    expected(t,3,i)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章