ETCD 源码学习--MVCC 代码实现(一)

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值