07 | MVCC:如何实现多版本并发控制?
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
etcd v3引入了MVCC(多版本并发控制)机制,通过保存key-value数据的多个历史版本,并使用逻辑时间版本号来实现并发控制。相比传统的悲观锁机制,MVCC采用乐观锁机制,避免了锁粒度过大和高并发场景下的性能问题。MVCC的核心架构由treeIndex和Backend/boltdb组成,treeIndex模块基于内存版B-tree实现了key索引管理,而Backend/boltdb负责etcd的key-value持久化存储。MVCC的引入解决了etcd v2中不可靠的事件机制,实现了可靠的Watch机制,保障了etcd集群的稳定性。此外,MVCC还能以较低的并发控制开销,实现各类隔离级别的事务,保障事务的安全性,是事务特性的基础。总体而言,MVCC机制的引入为etcd v3带来了更强大的并发控制和稳定性,为读者提供了更好的数据管理和事务处理能力。 MVCC特性初体验中的更新、查询、删除key案例,为你分析了MVCC整体架构、核心模块,它由treeIndex、boltdb组成。treeIndex模块基于Google开源的btree库实现,它的核心数据结构keyIndex,保存了用户key与版本号关系。每次修改key都会生成新的版本号,生成新的boltdb key-value。boltdb的key为版本号,value包含用户key-value、各种版本号、lease的mvccpb.KeyValue结构体。当你未带版本号查询key时,etcd返回的是key最新版本数据。当你指定版本号读取数据时,etcd实际上返回的是版本号生成那个时间点的快照数据。删除一个数据时,etcd并未真正删除它,而是基于lazy delete实现的异步删除。删除原理本质上与更新操作类似,只不过boltdb的key会打上删除标记,keyIndex索引中追加空的generation。真正删除key是通过etcd的压缩组件去异步实现的,在后面的课程里我会继续和你深入介绍。基于以上原理特性的实现,etcd实现了保存key历史版本的功能,是高可靠Watch机制的基础。基于key-value中的各种版本号信息,etcd可提供各种级别的简易事务隔离能力。基于Backend/boltdb提供的MVCC机制,etcd可实现读写不冲突。
《etcd 实战课》,新⼈⾸单¥59
全部留言(28)
- 最新
- 精选
- 五味子我理解etcd采用延迟删除,1是为了保证key对应的watcher能够获取到key的所有状态信息,留给watcher时间做相应的处理。2是实时从boltdb删除key,会可能触发树的不平衡,影响其他读写请求的性能。
作者回复: 赞,理解很到位
2021-02-0432 - tianfeiyu一般情况下(默认堆积的写事务数大于 1 万才在写事务结束时同步持久化),数据持久化由 Backend 的异步 goroutine 完成,它通过事务批量提交,定时将 boltdb 页缓存中的脏数据提交到持久化存储磁盘中 --- 如果etcd集群突然挂了,如何保证这部分未持久化的数据不会丢呢?
作者回复: 重启时会重放wal日志中已提交的日志条目再次执行
2021-02-07415 - 花晨少年并发读特性的核心原理是创建读事务对象时,它会全量拷贝当前写事务未提交的 buffer 数据,并发的读写事务不再阻塞在一个 buffer 资源锁上,实现了全并发读。 --------------- 写事务未提交,为什么读事务要去读这个脏数据呢?另外写事务的写buffer是这个事务所有操作一起写bufeer吗,我们保证原子的写呢,在不锁的情况下? 我理解是如果对读事务来看,想让写事务具有原子性,应该必须得加锁吧。
作者回复: 第一个问题,未提交的buffer,并不是脏数据,参考下03 etcd写原理,为了优化性能,etcd并不会来一个put请求就发起一次boltdb事务提交,将数据持久化到db文件,而是将多个put和txn等请求合并异步提交(未大量写操作堆积时),它们一方面会更新buffer,一方面会更新boltdb内存数据结构。这里对两个事务可能让你困惑了,一个是你应用层发起的txn等操作,一个是boltdb事务,后者一个事务可提交多个txn和put等操作。 第二个问题,有加锁的哈,每个写事务都需要获取一个mvcc全局写锁才能更新哈
2021-04-1844 - shuff1e当你再次查询 hello 的时候,treeIndex 模块根据 key hello 查找到 keyindex 对象后,若发现其存在空的 generation 对象,并且查询的版本号大于被删除时的版本号,则会返回空。 --- 如果删除了之后,又重新写入了。 查询的最新的版本号,还是会返回最新的数据的吧。
作者回复: 嗯,是的,新写入后会生成新的generation, 匹配generation过程中会优先匹配最新的一代,然后从中返回最后一次修改的版本号,就可从boltdb查询到最新的数据
2021-02-034 - Ching“全局版本号随读写事务自增,因此是 main 为 2,sub 随事务内的 put/delete 操作递增,因此 key hello 的 revison 为{2,0},key world 的 revision 为{2,1}。” 老师请问一下,读事物也会递增全局版本号吗?然后这个子版本号,在这个例子里有两个put,为什么不是递增2呢?是一个事物内的全部写操作只看作1次子版本递增吗
作者回复: ching你好, 读事务不会的,一个txn写事务只会递增一次全局版本号,若有若干个写操作,sub子版本号会递增多次
2021-08-183 - John如果一个key 删除了,并且compactor也已经真正删除了该key,那查询历史某个版本会报错?
作者回复: 嗯,会返回ErrCompacted错误的,你可以测试下
2021-07-30 - 石小backend 的异步 goroutine 完成,它通过事务批量提交,定时将 boltdb 页缓存中的脏数据提交到持久化存储磁盘中。 有配置能控制提交条目和刷新频率吗?
作者回复: 有的,etcd提供了如下两个参数可以控制事务提交的行为。 --backend-batch-interval '' BackendBatchInterval is the maximum time before commit the backend transaction. --backend-batch-limit '0' BackendBatchLimit is the maximum operations before commit the backend transaction. 在etcd v3.4.9中,backend-batch-interval如果你没指定,默认是100ms,对应的异步goroutine将批量、每隔100ms,将boltdb事务进行提交。 backend-batch-limit默认是10000,当堆积的put/del等操作若超过10000个,则会同步触发boltdb事务提交。
2021-03-193 - WGJ在删除原理中: 当你再次查询 hello 的时候,....,若发现其存在空的 generation 对象,并且查询的版本号大于被删除时的版本号,则会返回空。 中的查询的版本号,中的查询版本号如果没有指定,默认是最新的话,我理解的应该是和删除的版本号相等吧?
作者回复: 嗯,是大于等于被删除时的版本号,感谢
2021-02-08 - 奕为什么 etcd v2 版本基于内存的 Watch 机制会不可靠呢? 存在历史版本不就可以比较key 的 value 是否有变化的?
作者回复: etcd v2历史版本只保存最近1000条,重启就没了,详细watch机制分析可看08讲,已更新
2021-02-04 - mckee思考题: etcd 为什么删除使用 lazy delete 方式呢?相比同步 delete, 各有什么优缺点? etcd要保存key的历史版本,直接删除就不能支持revision查询了; lazy方式性能更高,空闲空间可以再利用; 当你突然删除大量 key 后,db 大小是立刻增加还是减少呢? 应该会增大,etcd不会立即把空间返回系统而是维护起来后续使用,维护空闲页面应该需要一些内存;2021-02-056