欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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 的版本管理实现和键值的管理实现。

 

相关标签: etcd etcd mvcc