ETCD 源码学习--MVCC 代码实现(一)
程序员文章站
2022-06-14 18:56:17
...
在 ETCD 源码学习过程,不会讲解太多的源码知识,只讲解相关的实现机制,需要关注源码细节的朋友可以自行根据文章中的提示,找到相关源码进行学习。
主要文件
/mvcc/revision.go mvcc 版本号存储定义
/mvcc/index.go 索引操作的对外接口定义和具体实现
/mvcc/key_index.go key的版本管理及相关操作定义和具体实现
/mvcc/kv.go kv接口定义
/mvcc/kv_view.go readView 与 writeView 定义
/mvcc/kvstore.go 键值对存储对象的具体实例
/mvcc/kvstore_txn.go 键值对事务实现,readTxn/writeTxn
/mvcc/kvstore_compaction kv压缩实现
backend/batch_tx.go bbolt写事务实现
backend/read_tx.go bbolt读事务实现
key 版本管理
/*
mvcc/key_index.go
键值索引的版本控制
*/
type keyIndex struct {
key []byte //客户端提供的原始 key 值
modified revision //最后一次修改的 revision 信息
generations []generation //key 修改版本
}
type generation struct {
ver int64 //修改次数
created revision // //记录创建 generation 时, revision 信息
revs []revision //当不断修改 Key 的值时,会不断增长
}
// mvcc/revision.go
type revision struct {
main int64 //主版本
sub int64 //副版本
}
对 key 索引的管理,主要通过 revision(版本) 和 generation(代) 进行管理。对键值对的增删改,都会增加当前 key 的版本号。
增/改
func (ki *keyIndex) put(lg *zap.Logger, main int64, sub int64) {
rev := revision{main: main, sub: sub}
if !rev.GreaterThan(ki.modified) {
}
if len(ki.generations) == 0 { //如果不存在任何版本
ki.generations = append(ki.generations, generation{})
}
//获取最新的一个 geneartion
g := &ki.generations[len(ki.generations)-1]
if len(g.revs) == 0 { // 当前 generation 没有任何版本
g.created = rev
}
g.revs = append(g.revs, rev) //增加版本号
g.ver++ //修改次数增
ki.modified = rev //修改为当前最新的版本
}
删
//向当前的 generation 中追加一个 revision 实例
func (ki *keyIndex) tombstone(lg *zap.Logger, main int64, sub int64) error {
....
ki.put(lg, main, sub) // 新增版本号
ki.generations = append(ki.generations, generation{}) //重新生成一个新的 generation
return nil
}
查
func (ki *keyIndex) get(lg *zap.Logger, atRev int64) (modified, created revision, ver int64, err error) {
//根据指定的 main revision,查找对应的 generation 实例, 这个实例包含了指定的 atRev
g := ki.findGeneration(atRev)
if g.isEmpty() {
return revision{}, revision{}, 0, ErrRevisionNotFound
}
//找到第一个小于等于 atRev 的值的 revsion 的索引值
n := g.walk(func(rev revision) bool { return rev.main > atRev })
if n != -1 {
return g.revs[n], g.created, g.ver - int64(len(g.revs)-n-1), nil
}
return revision{}, revision{}, 0, ErrRevisionNotFound
}
//查找指定 generation
func (ki *keyIndex) findGeneration(rev int64) *generation {
lastg := len(ki.generations) - 1
cg := lastg
/*
逆序查询
generations:
[1,2,3]
[4,5,6]
[7,8,9]
[]
查找时从最后一个开始查询。
*/
for cg >= 0 {
if len(ki.generations[cg].revs) == 0 {
cg--
continue
}
g := ki.generations[cg]
//如果不是最后一个 generation 实例
if cg != lastg {
if tomb := g.revs[len(g.revs)-1].main; tomb <= rev { //每个 generation 最后一个版本是被删除的版本号,所以满足这个条件时,表示值已被删除
return nil
}
}
//与第一个 revision 做比较
if g.revs[0].main <= rev {
return &ki.generations[cg]
}
cg--
}
return nil
}
//从 generation 找到第一个满足 rev
func (g *generation) walk(f func(rev revision) bool) int {
l := len(g.revs)
for i := range g.revs {
ok := f(g.revs[l-i-1]) //逆序查找,revs数据
if !ok {
return l - i - 1 //返回revs 下标值
}
}
return -1
}
多 key 值管理
/* mvcc/index.go
ectd 的索引键主要通过 btree 管理 (github.com/google/btree), tree 的元素值为 keyIndex。
其中通过 keyIndex.key 生成 btree。(btree 的具体细节在这里不深究,有需要的可以查看相关源码)
*/
type treeIndex struct {
sync.RWMutex
tree *btree.BTree
lg *zap.Logger
}
增/改
func (ti *treeIndex) Put(key []byte, rev revision) {
keyi := &keyIndex{key: key} //生成 btree key
ti.Lock()
defer ti.Unlock()
item := ti.tree.Get(keyi) //获取指定 key 的值
if item == nil { //如果不存在
keyi.put(ti.lg, rev.main, rev.sub) //增加版本号
ti.tree.ReplaceOrInsert(keyi)
return
}
okeyi := item.(*keyIndex)
okeyi.put(ti.lg, rev.main, rev.sub) //增加 keyIndex的 版本号
}
删
func (ti *treeIndex) Tombstone(key []byte, rev revision) error {
keyi := &keyIndex{key: key}
ti.Lock()
defer ti.Unlock()
item := ti.tree.Get(keyi)
if item == nil {
return ErrRevisionNotFound
}
ki := item.(*keyIndex)
return ki.tombstone(ti.lg, rev.main, rev.sub)
}
查
func (ti *treeIndex) Get(key []byte, atRev int64) (modified, created revision, ver int64, err error) {
keyi := &keyIndex{key: key}
ti.RLock()
defer ti.RUnlock()
if keyi = ti.keyIndex(keyi); keyi == nil {
return revision{}, revision{}, 0, ErrRevisionNotFound
}
return keyi.get(ti.lg, atRev)
}
//从 btree 中找到 key 对应 keyIndex
func (ti *treeIndex) keyIndex(keyi *keyIndex) *keyIndex {
if item := ti.tree.Get(keyi); item != nil {
return item.(*keyIndex)
}
return nil
}
总结
本文主要介绍了 ETCD MVCC 的源码实现,主要包括 key 的版本管理实现和键值的管理实现。
推荐阅读
-
51ak带你看MYSQL5.7源码3:修改代码实现你的第一个Mysql版本
-
Dijkstra算法初步学习(Java代码实现)(一)
-
ETCD 源码学习--MVCC KV存储实现(二)
-
ETCD 源码学习--MVCC 代码实现(一)
-
Vue源码学习笔记之实现双向数据绑定(一)--- 发布订阅模式
-
51ak带你看MYSQL5.7源码3:修改代码实现你的第一个Mysql版本
-
Dijkstra算法初步学习(Java代码实现)(一)
-
第2篇---Python设计模式之抽象工厂模式+含代码实现+学习python的小哥哥小姐姐一定要看看
-
第3篇---Python设计模式之建造者模式+含代码实现+学习python的小哥哥小姐姐一定要看看