Etcd 存储,Watch 以及过期机制

etcd v3存储,Watch 以及过期机制


etcd在存储数据的时候主要分为了两个部分,第一部分叫做kvstore,这个kvstore是存储在内存当中的,这个是in memory的kvstore,任何数据库都需要做索引,所以etcd就是通过这种方式在内存当中构建了索引,这个索引的目标是来做快速的查找。

另外后端会有真正落盘的数据库,etcd默认使用blotdb去实现的,blotdb是Google开源的key value数据库,当你任何数据要去存储到etcd store的时候,它同时会去存索引和落盘,通过blotdb去落盘。(最终的数据持久化是基于blotdb)

watchablestore是干嘛的呢?因为etcd本身是支持监听的,你可以去监听某个对象的事件,这些对象就会组织在watchablestore里面。

存储机制

etcd v3 store分为两部分∶一部分是内存中的索引,kvindex,是基于Google开源的一个Golang的Btree实现的,另外一部分是后端存储。按照它的设计,backend可以对接多种存储,当前使用的是boltdb。

boltdb是一个单机的支持事务的KV存储,etcd的事务是基于boltdb的事务实现的。etcd在boltdb中存储的key是reversion, value是etcd自己的key-value组合, 也就是说 etcd 会在 boltdb 中把每个版本都保存下,从而实现了多版本机制。

reversion主要由两部分组成,第一部分main rev,每次事务进行加一,第二部分 sub rev,同一个事务中的每次操作加一。

对于任何的数据都有reversion的概念,它相当于是一个版本的信息,reversion主要由两部分组成,一部分是main rev,一部分是sub rev。

下面去get一个key,-wjson是将其细节打印出来,这里面可以看到有create mode reversion,这里就有一个版本的概念,对于任何etcd里面存的对象,它都有版本的概念,有个当前的reversion 3。

reversion是当前集群类似于自增长的这么一个值,当我们去对整个集群做任何数据修改的时候,它的reversion就会增加,reversion分为了sub和main两个部分,如果你开了一个事务,那么这个事务里面所有写操作可以公用mian reversion,然后里面子命令对应sub reversion。

对于k8s来说,我们大部分时候使用main reversion,sub reversion没有用。

回过头看k8s对象的时候,有个resource version,可以将它理解为乐观锁,其实这个resource reversion就和etcd里面的对象mod reversion是一一对应的,当对etcd做数据变更的时候,这个etcd mod revesion也会跟着变化。k8s在读取对象的时候就会以mod reversion作为它的resource version。

  • etcd提供了命令和设置选项来控制 compact,同时支持 put操作的参数来精确控制某个key 的历史版本数。
  • 内存 kvindex 保存的就是key 和 reversion 之前的映射关系,用来加速查询。

etcd数据存储流程 


客户端要发起一个写的请求,假设这个请求发给了follower,最终这个请求会转给一致性模块,一致性模块会去判断自己是不是leader,如果是leader就直接处理,如果不是就转给leader。

请求到了leader要做一些预检查,第一个配额,就是etcd有数据大小的配置,你这次写请求还能不能放进去,第二要限速,作为一个服务器要频繁的调取我的写操作会把我压垮的,第三就是去认证鉴权, 第四会去做数据包检查,如果数据包超过1.5M,它就不让写了。

如果数据包太大了,它反复确认,包括后面做索引,做查询,它的开销就非常的大,会导致etcd的性能直线下降。因为它需要多次确认,需要数据同步,它就会导致数据同步的效率非常低,所以它做了一些限制,让你不要无休止的去增加,把数据变的非常大,在k8s里面,很难将yaml写成1.5M以上的,yaml不是无限增长的,而是有一定限制的。

这些预检查做完之后,请求会被发送到主模块叫做kvserver,kvserver接收到请求发现是一次写请求,比如是y=9,它会把请求发到一致性模块,一致性模块实现两件事,它其实就是raft协议的实现者,第一,它会实现选主,第二就是日志复制,它会在etcd的内存里面构建raft log,这个raft log其实是一个数据结构,这个raft log其实是一个数据结构,它会先将自己的信息写到unstable里面,记录了我有一条数据要写入,接下来这个请求会被同时写入到本地的wal log,就是写y=9,将y=9的这条日志写到本地,这个写入最后是要落盘的,不能每次写入都落盘这样效率太低了,wal log落盘其实是由fsyn,就相当于前端写在buffer里面,最后通过fsync周期性的将这些事件正真的落在磁盘上面。

写wal log的同时它有另外的goroutine去把同样的message,就是通过一个append message发到其他follower那里,follower那边收到请求之后,它要做同样的事情,就相当于要将写操作写入wal log。

并且写完以后回复response,回到leader这边,leader发现有半数确认了这条日志的写入,那么它就认为这条数据已经commit了,所以它会更新自己的数据结构,更新match index,每条写入都有自增长的index。

同时它这个请求会以apply的方式请求状态机来记录这次数据。状态机基于mvcc模块,就是多版本并发控制的这么一个模块,经过半数确认之后要往mvcc里面去写,首先在tree index

这里,就是memory index里面

Etcd 数据的一致性


 有个matchindex,在leader这一端,它会保存一个信息,第一个term大家都写了a和b,然后后面就变为第二个term,在第二个term里面又发生了很多的变更,wal log里面存了a b c d e f g,这里有8次变更,每次变更都会在etcd里面去维护一个自身的index,leader这边都会维护一个matchindex,代表leader和哪个index保持一致了,这里有leader a,follower b c,以提交的日志是代表超过半数确认的日志,这三个人组成的集群超过半数确认的是到第7个index,也就是当前的match index是7,第8还没有被多数确认,它并不是一个正真确认过的index,如果重新选举的话,c的log本身比leader的commit  index是要小的,如果c去作为新的leader就有可能丢数据了。

之所以记录index,就是用来确保说拥有最新数据的这些人,才可以去做新的leader,c是做不了新的leader的,所以通过index从leader这边记录一下,最新的commit log的index已经到哪里了,来确保新的leader永远包含所有已经确认的数据,通过这样的方式保证数据的一致性。

 Watch机制


etcd除了上面的提供基本的读写功能,还提供了watch的机制。

watch类型

watch又分为两类,一类是watch某一类的key,这种叫做key watcher,一类是提高--prefix,通过模糊匹配来查询以斜杠开头的所有key的变更,这种叫做range watcher。

etcd V3的 Watch机制支持Watch某个固定的 key,也支持Watch一个范围(可以用于模拟目录的结构的Watch),所以 watchGroup 包含两种 watcher,一种是key watchers,数据结构是每个key 对应一组 watcher,另外一种是 range watchers,数据结构是一个IntervalTree,方便通过区间查找到对应的watcher。

如何满足watch请求

etcd如何满足watch请求,就是之前说的watchablestore,它会开辟了一片内存空间,来满足watch的需求,watchablestore里面分了两个组,一个叫sync group,一个叫unsync group。

当你要去获取最新数据的时候,没有增加reversion,那么这个时候就可以直接将内存当中的数据给你,这种watch叫做sync group。

如果是带着历史的reversion,那么在整个内存当中是没有这个信息的,它要去db里面加载,所以你去拿unsync group当中数据的时候这个请求会被发到unsync group,发到unsync group之后,etcd会有backend的也就是背后会去启动goroutine去同步数据,当将数据同步到内存之后,然后会把watch发到sync group,然后将数据同步给你。

同时,每个WatchableStore 包含两种 watcherGroup,一种是 synced,一种是 unsynced,前者表示该 group 的watcher数据都已经同步完毕,在等待新的变更,后者表示该group 的watcher 数据同步落后于当前最新变更,还在追赶。

当 etcd收到客户端的 watch请求,如果请求携带了revision参数,则比较请求的revision和

store 当前的revision,如果大于当前revision,则放入 synced组中,否则放入 unsynced 组。

同时,etcd 会启动一个后台的 goroutine 持续同步 unsynced 的watcher,然后将其迁移到Synced 组。

在这种机制下,etcd V3支持从任意版本开始 watch,没有V2的1000条历史 event表限制的问题(当然这是指没有compact的情况下)。

这里有两个窗口,一个更新一个watch,左边的窗口可以看到有put的操作,不断的变更,左边的窗口就不断收到通知,一个个的put事件,

-wjson将当前数据的详细信息展示出来 

 可以看到key values本身是加密过的了,记录了创建的reversion是多少,可以看到创建对象的时候的它的一个状态。这个信息还是存放在etcd里面,虽然这个值已经改掉了,但是还是存储在db里面。

可以看到依次记录了这几次reversion的变化,如果其他线程改变了值,那么reversion还是会跳的。 

 

 左边的watch窗口关闭重开,不加任何的reversion,也就是从当前的版本给我通知新的事件,如果有新的变更,这个时候这些对象没有发生变化,所以它就没有任何的通知。

也可以指定从4的版本开始watch,可以看到将所有的变更都发给我了

 上面就是etcd多版本的变更。

etcd常用操作


 

 

 

猜你喜欢

转载自blog.csdn.net/qq_34556414/article/details/125582162
今日推荐