浅谈 etcd 的发展历史

992 阅读6分钟

2013 年,一个叫做作 CoreOS 的创业团队认为,所有的安全问题,都可以归结为软件更新能力的问题,如果可以构建一个自动更新的服务器(自动化),那么系统的安全问题将迎刃而解。为了达到这个目的,必须将应用程序打包成一个独立单元,达到基本操作系统和应用程序隔离的目的,CoreOS 通过容器技术来达到目的。同时,还共了集群化的管理方案,让用户管理集群就像管理单机一样方便。

对于管理集群,CoreOS 团队面临的第一个问题就是,如何保证集群的高可用?换句话说,CoreOS 团队希望在重启任意一个集群中的某个节点时,其他的应用程序不会因此而宕机。因此,需要运行多个副本。那多个副本,就需要进行协调服务。CoreOS 首先在开源社区去寻找优秀的、相关的开源协调服务项目,它们希望这个开源项目满足 5 个目标:

  1. 高可用

    协调服务作为集群的控制面板,它的背后保存了各个服务的部署信息。如果出现故障,可能会导致集群无法变更、服务副本数量无法协调,从而影响用户体验,例如数据加载过慢(节点少,流量大)。

  2. 低容量

    只保存存储关键元数据,集群、节点的配置信息。

  3. 可维护性

    在分布式系统出现 bug 时,都会出现新增、替换节点的场景,这些都需要通过协调服务进行节点的变更,如果人工去操作这些节点,那可能会出现认为操作失误导致的宕机,能否通过 API 来约定协调服务需要进行的操作,这会是更平滑的一种方式。

  4. 数据一致性

    高可用的前提下,不能出现单点故障,那多节点(多个副本)又会出现数据一致性的问题。

  5. 能够监听节点并且可以进行增删改查的基本操作

    节点出现异常或变更时,相比控制端定时去轮询检查,一个“pull”的拉取操作,变成一个“push”操作,将事件变为推送操作,可以避免协调服务中不必要的性能开销。

有了理想目标后,CoreOS 开始寻找这样的协调服务了,不过很遗憾,在 2013 年并没有,因此 CoreOS 决定自己造轮子,那接下来就是为了实现这个目标而技术选型了。

在解决数据一致性的问题上,CoreOS 选择引入一个共识算法 Raft。该算法是易于理解的,它将一致性问题拆解成了 Leader 选举、日志同步、安全性三个相对独立的子问题。

再来看看低容量的问题,CoreOS 参考了 ZooKeeper 的数据模型与 API,使用基于目录的层级模式,而 API 相比 ZooKeeper 来说,使用了更加简单的 REST API,来对节点进行增删改查以及监听操作。

对于在存储引擎上的节点,etcd 选择的是简单内存树,将节点的数据结构进行了精简,节点中包含路径、值、孩子节点信息。这是一个典型的低容量设计。

最后是可维护性,Raft 算法提供了成员变更算法,基于此来监听成员的在线、变更状态。

基于以上的技术选型,CoreOS 团队在 2013 年发布了基于 Go 编写的 etcd 的第一个版本 beta v0.1 。为什么叫 etcd 呢?CoreOS 团队的灵感来自 unix 系统的 /etc 文件夹和分布式系统 distributed system 中的 D,组合在一起表示:etcd 是用于存储分布式配置的信息存储服务

2014 年 6 月,Google 的 Kubernetes 项目横空出世,它选择了 etcd 作为服务协调的基石,因为 etcd 的目标与 Kubernetes 所需要的特性不谋而合,例如:高可用、Watch 机制。Kubernetes 项目使用 etcd,除了技术因素,也与当时的商业竞争有关,CoreOS 是 Kubernetes 容器生态圈的核心成员之一,感兴趣的小伙伴可以看一下这篇文章。在 Kubernetes 的带领下,etcd 也进入了快速发展的时代。因此在 2015 年 1 月,etcd 的 v2.0 版本正式发布,此时它被广泛应用于配置存储、服务发现、主备选举等场景。不过此时也出现了若干性能、功能不足的问题,如下:

  1. 功能局限问题

    首先, etcd v2.0 不支持范围查询和分页。在 Kubernetes 集群规模增大后,Pod、Event 等资源可能出现上千个,不支持分页会出现 expensive request,引发性能问题。其次是不支持多 key 事务,在实际场景中,我们往往需要同时更新多个 key。

  2. 性能瓶颈问题

    HTTP/1.x 协议的瓶颈引发了 etcd 的性能瓶颈,因为 HTTP/1.x 协议没有压缩机制,批量拉取较多的 Pod 时容易导致 Server 和 etcd 出现丢包、CPU 高负载、内存溢出问题。

  3. 内存开销问题

    前面我们知道了,etcd 使用了一颗内存树来保存节点,在数据场景较大时,配置项较多,将会存储大量的数据,导致内存靠小较大,这影响了系统的稳定性。

  4. Watch 机制可靠性问题

    Kubernetes 非常依赖 etcd Watch 机制,然而 etcd v2.0 是内存型,不支持保存节点的历史版本,只在内存中使用滑动窗口保存了最近的变更时间,当 etcd server 写请求较多、网络波动时,很容易出现事件丢失,因此触发 client 数据的全量拉取,产生大量的 expensive request。

面对这些问题,CoreOS 团队积极听取了开源社区的声音并积极改进,面对功能局限中提到的分页、范围查询查询不支持,那么它就去支持;面对内存开销大的问题,那它就通过引入 B-tree、boltdb 实现一个 MVCC 数据库,将数据模型从层次性改为扁平型,基于 boltdb 实现持久化存储,降低 etcd 的内存占用;对于性能瓶颈,它引入了 gRPC,通过 protobuf 定义消息,解码性能得到了大幅的提高,并通过 HTTP/2.0 多路复用机制,减少了大量 watcher 等场景下的连接数。基于这些解决方案,在 2016 年 6 月,etcd v3.0 正式发布了。

如今,etcd 在 Github 上的 star 数已经达到了 38K!成为了云原生时代首选的元数据存储产品。