从一次故障探究etcd的多版本机制

245 阅读3分钟

一、背景

笔者突然想起来,在陪产假期间收到了运维同事反馈的故障,于是乎要来了故障日志,如下:

图片

笔者当时忙于家庭,匆忙按错误日志查找资料相关的命令让现场同事进行了恢复,如下:

./etcdctl endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*'./etcdctl compact {version}   ## 压缩到上一步得到的version ./etcdctl defrag              ## 清理碎片./etcdctl alarm disarm        ## 消除报警 

当时在陪产假期间,无空深入,今天寻思着深入研究一下 现场实际的etcd存储的业务数据量不多的情况下,为啥空间这么快会满?

二、原因刨析

经过一番探究

v3版本的etcd采用了多版本控制。笔者实际尝试了一下,etcd会保留同一个key的历史版本,如下图~

图片

在这个例子中,create_revision 是key创建时的全局版本号,mod_revision是key当前版本的全局版本;version是这个key的局部版本号;笔者对/name/2 这个key修改了一次,version从 1变成了 2 ,mod_revision 从50182变成了50183。

因此可以看出来这样的mvcc机制导致数据量非常大,这也是v3版本的etcd采用磁盘作为数据库而不是内存。所以我们要定期压缩清理历史版本,也可以自动压缩;压缩后,数据库空间将存在碎片,要用defrag命令进行清理,释放空间。这就是上文故障中让运维同事恢复etcd四条命令背后的含义。

三、深入探究

笔者又不禁思考两个问题

1、为啥v3版本的etcd要采用mvcc机制?

2、摒弃内存而采用磁盘作为数据库怎么处理读写磁盘的瓶颈问题?

一、问题一:为啥v3版本的etcd要采用mvcc机制?

v2版本的etcd作为纯内存的数据库,有一个stop the world的大锁来保证并发下的数据一致性,这其实是悲观锁的一种实现方式,这种方式中,读写会互相阻塞,长时间的读操作或写操作将降低整体的并发性能;

于是,应该用乐观锁的机制来处理高并发的数据竞争问题,乐观锁的机制,可以让读写不堵塞,对临界资源做操作不独占,mvcc其实就是一种乐观锁的实现方式。

此外,v2版本的etcd采用滑动窗口来实现watch机制,这种存在两个弊端:

1、滑动窗口的实现本质还是消耗网络和内存资源来实现对服务端的监听,如果存在大量的watch就会占用了大量资源

2、不稳定,如果客户端断开连接,中途对key的修改将丢失,并且滑动窗口的大小有1k条的限制,1k之前的版本数据将丢失。

因此,v3版本采用mvcc机制势在必行。一方面解决了v2版本watch机制的弊端,一方面,解决了v2版本数据竞争引发的性能问题。 

二、问题二:采用磁盘作为数据库怎么处理磁盘的瓶颈问题

众所周知,磁盘的读写需要旋转磁盘和读写磁头,相对于内存来说是很慢的,但是etcd v3采用了boltDB作为底层数据库的实现,boltDB这个项目笔者曾简单看过,麻雀虽小,五脏俱全,感兴趣可以读读。

boltDB有mmap机制将数据库文件映射到内存中,使得进程可以直接访问内存中的数据,避免了频繁的文件IO操作,解决了磁盘的瓶颈问题。当然,v3版本的etcd相较v2,在存储结构上也进行了一些优化,例如使用线段树而非目录式层级化,又例如mvcc机制降低数据竞争及降低wath机制资源消耗等,也是为了提升读写性能