请小心ETCD的Compact

5,182 阅读4分钟

本文想讨论一些ETCD的使用注意事项,ETCD作为一个分布式KV数据库,作用有很多,下面以用ETCD作为配置中心来举例,用到了ETCD关键的两个功能:Watch和Revision。Watch可以实时推送配置变更,而Revision可以回溯配置的历史变更列表。

关于Revision的概念,请参考这篇文章

在ETCD中,老实说对Revision的概念支持的不是很好,虽然标榜自己是可以回溯历史版本的,但我找了半天,除了一个--rev参数,没有找到更多的API来利用这些历史版本。

我们先来看看常用的git是怎么处理历史版本回溯的,git有两种方式可以回溯版本:

  1. CommitHash
    如果知道一个提交的hash,可以直接跳到这个提交上。
  2. HEAD指针
    类似游标,可以不断往前回溯(HEAD~),不需要知道提交的hash。

而在ETCD中,相当于只提供了第一种方式,我们以一个本地测试的ETCD服务为例,向ETCD中发送以下请求序列:
(PUT,key,bar),(PUT,key,bar2),(PUT,key2,foo),(PUT,key,bar3)

这会在ETCD中保存为如下格式:

key_namevaluecreate_revisionmod_revisionversion
keybar111
keybar2122
key2foo331
keybar3143

可以通过etcdctl get --rev 2 key 这种命令获取 value=bar2 的记录,如果传的rev比create_revision还要小,则会返回空记录。

由于ETCD没有提供游标机制,我们是没有办法拿到一个key的历史变更列表的,只能走两种曲线救国的方式:

在ETCD提供的client源码中,可以看到其支持 WithFirstCreate\WithLastRev 等,好像是可以拿到key的第一条变更,实际上这个是用来比较不同key的,比如WithFirstCreate,如果查到key、key2两个key,会比较两个key最新记录的create_revision,返回create_revision最小的记录,也就是key。

  1. Watch去拿这个Key的历史
    在Watch时传--rev参数,如果传的rev足够小,比如固定--rev=1,就会拿到这个key的全部变更历史,但是不能分页,只能拿全部。

    而且在实践中,实在是不好确定--rev传什么,因为不同的key共享全局的revision,传的太小,把key的整个历史全拉下来,如果历史很多,就比较浪费;传的过大,可能会报错。

  2. 另外找个地方存revision
    这应该是实践中用的比较多的方式,用MYSQL、redis、或者干脆就用ETCD本身(另外开个key存),每次创建、更新时,都把当前的revision记下来,就能形成历史链,也就知道--rev参数应该传啥了。 这样就可以表达:key的上一次修改key的前10次修改这样的语义。

目前我在使用ETCD做配置中心时,也是按第2条这样实践的,另外在MYSQL中用history表记录每次操作key是的revision,用来找到历史配置变更。然而,这样做,一定要小心ETCD的Compact机制。

compact是ETCD提供的清理命令,由于ETCD会记录历史,所以如果不限制的话,历史会越来越多,占用磁盘空间,ETCD提供了etcd compact {revision}命令,可以把{revision}之前(含)的历史全部清除。虽然compact翻译过来是压缩的、紧密的,但我在这里没用“压缩”这个词,就是因为我认为在中文中,压缩这个词默认是可逆的,对应的是解压。但实际上,ETCD的compact执行了就真的没了,所以更贴近清理的概念,翻译成压缩实在容易让人误解。

在实际的业务场景中,这个命令compact一般是运维执行的,甚至可能配置了自动定期compact。那么一旦revisioncompact,再用history表中记录的revision就会直接报错:

etcdserver: mvcc: required revision has been compacted

开发看到错误也只能一脸懵逼了,我在使用中就是碰到这个错,才发现的这个问题。

所以我们在使用第2种方式:另外找个地方存revision时,还得小心这个revision是否失效了,可以检查err,如果失效,则在表中清除小于等于这个revision的所有记录,留着也没什么卵用了, Watch、Get时,如果使用Rev的都要小心这个报错。

resp,err := client.Get(ctx, key, clientv3.WithRev(rev))
if err != nil{
    if err == v3rpc.ErrCompacted{
        deleteRevision(rev)
    }
    return err
}

但是这样体验也不太好,还得重试一遍。感觉ETCD还是应该提供类似游标的API,可以查询到key的历史,这样就很方便了。

参考文献

github.com/etcd-io/etc…

mp.weixin.qq.com/s?__biz=Mzg…

github.com/etcd-io/etc…