云计算与云原生 — ETCD 数据库完全解析
Posted 范桂飓
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了云计算与云原生 — ETCD 数据库完全解析相关的知识,希望对你有一定的参考价值。
目录
文章目录
- 目录
- etcd
- etcd vs ZooKeeper vs Consul
- etcd 的应用场景
- etcd 的安装与部署
- etcd 的架构原理
- etcd 的基本操作
etcd
etcd 是 CoreOS 团队于 2013 年 6 月发起的使用 Golang 开发的开源项目,基于 Raft 强一致性算法,它的目标是构建一个高可用,且数据强一致性的分布式键值(key/value)数据库,专注于配置共享(shared configuration)和服务发现(service discovery)。etcd 目前更新至 v3 版本,已被用在 CoreOS、Kubernetes、Cloud Foundry 等项目中。
A distributed, reliable key-value store for the most critical data of a distributed system
- 官方网页:https://etcd.io/
- Github:https://github.com/etcd-io/etcd
etcd 作为一个受到 ZooKeeper 启发而催生的项目,它除了拥有与之类似的功能外,更专注于以下四点:
- 简单:易部署,易使用。提供 REST 和 gRPC API。
- 安全:支持可选的 SSL 安全认证。
- 快速:每个实例每秒支持 10,000 次写操作。
- 可靠:使用 Raft 算法保证了分布式的数据强一致性。
etcd 的经典应用场景在于存储控制系统中的元数据,所以 etcd 并不是其他 NoSQL 的替代品,更不作为应用数据的主要存储,etcd 中应尽量只存储系统中服务的配置信息。
etcd 提供了以下能力:
- 提供存储以及获取数据的接口:它通过协议保证 etcd 集群中的多个节点数据的强一致性。用于存储元信息以及共享配置。
- 提供监听机制:客户端可以监听某个 key 或者某些 key 的变更,用于监听和推送变更。
- 提供 key 的过期以及续约机制:客户端通过定时刷新来实现续约,用于集群监控以及服务注册发现
- 提供原子的 CAS(Compare And Swap)和 CAD(Compare And Delete)支持:用于分布式锁以及 Leader 竞选。
etcd vs ZooKeeper vs Consul
Etcd,Zookeeper,Consul 这三个产品是经常被我们拿来进行选型,etcd 和 ZooKeeper 提供的能力非常相似,都是通用的一致性元信息存储,都提供 Watch 机制用于变更通知和分发,也都被分布式系统用来作为共享信息存储,在软件生态中所处的位置也几乎是一样的,可以互相替代。
ZooKeeper 寄托在 Apache 基金会,使用 Java 开发,提供 RPC 接口,最早从 Hadoop 项目中孵化出来,在分布式系统中得到广泛使用,例如:Hadoop、Solr、Kafka 和 Mesos 等。而 etcd 则是后起之秀,主要关注一致性协议、易用性、运维、安全等维度。相较之下,ZooKeeper 有着 “复杂”、“语言绑定”、“发展缓慢” 的缺点。下面列举两者的对比:
- 一致性协议: etcd 使用 Raft 协议,Zookeeper 使用 ZAB 协议(类 PAXOS 协议),前者更易于理解,方便工程实现;
- 数据存储:etcd 支持多版本并发控制(MVCC)数据模型,支持查询先前版本的键值对。
- 运维:etcd 易于运维,Zookeeper 则难以运维;
- 安全:etcd 支持 HTTPS 协议,Zookeeper 则在这方面有缺失;
- API:etcd 提供 HTTP+JSON 和 gRPC API,跨平台跨语言,Zookeeper 则需要使用其客户端;
而 Consul 的目标则更为具体一些,etcd 和 ZooKeeper 提供的是分布式一致性存储能力,具体的业务场景需要用户自己实现,比如:服务发现、配置变更。而 Consul 则以服务发现和配置变更为主要目标,同时附带了 key/value 存储。在软件生态中,越抽象的组件适用范围越广,但同时对具体业务场景需求的满足上肯定有不足之处。
etcd 的应用场景
服务注册与发现
服务发现是分布式系统最常见的问题之一,即:“在同一个分布式集群中的进程或服务,要如何才能找到对方并建立连接?” 的问题。简而言之,就是集群中的进程或服务需要让别人发现自己,相对的自己也要发现别人,并且可以通过彼此的名字来进行查找和连接。
要解决服务发现的问题,需要下述三大支柱,缺一不可:
- 一个强一致性、高可用的服务存储目录(Service Registry):基于 Raft 算法的 etcd 天生就是这样一个强一致性高可用的服务存储目录。
- 一种注册服务和监控服务健康状态的机制:用户可以在 etcd 中注册服务,并且对注册的服务设置 key TTL,定时保持服务的心跳以达到监控健康状态的效果。
- 一种查找和连接服务的机制:通过在 etcd 指定的主题(Topic)下注册的服务也能在对应的主题下查找到。为了确保连接,我们可以在每个服务机器上都部署一个 Proxy 模式的 etcd,这样就可以确保能访问 etcd 集群的服务都能互相连接。
- 微服务架构中的服务动态添加:微服务架构,即:多个微服务共同协作构成一个功能强大的架构。在微服务架构中,如何透明化的动态添加服务是首要解决的问题。通过服务发现机制,在 etcd 中注册某个服务名字的目录(Catalog),在该目录下存储可用的服务节点的 IP 地址。在使用服务的过程中,只要从服务目录下查找可用的服务节点去使用即可。
- PaaS 平台中的实例故障重启透明化:PaaS 平台中的应用一般是多实例的,客户端通过域名(Domain)就可以透明的对这多个实例进行访问,还可以做到负载均衡。但是应用的某个实例随时可能发生故障重启,这时就需要动态的更新域名解析配置。通过 etcd 的服务发现功能可以轻松解决这个动态配置的问题。
消息发布与订阅
在分布式系统中,消息发布与订阅是一种适用强的组件(服务)间通信方式。即:构建一个配置共享中心,消息生产者(Producer)在这个配置中心发布消息,而消息消费者(Consumer)则订阅他们关心的主题(Topic),一旦主题有消息发布,就会实时通知订阅者(Subscriber)。通过这种方式可以做到分布式系统配置的集中式管理与动态更新。
-
应用使用到的配置信息存储到 etcd 上进行集中管理(配置共享中心):应用在启动的时候主动从 etcd 获取一次配置信息,同时,在 etcd 上注册一个 Watcher 并等待(完成订阅),以后每次配置有更新的时候,etcd 都会实时通知订阅者,以此达到获取最新配置信息的目的。
-
分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存放在 etcd 中,供各个客户端订阅使用:使用 etcd 的 key TTL 功能可以确保机器状态是实时更新的。
-
分布式日志收集系统:这个系统的核心工作是收集分布在不同机器的日志。收集器通常是按照应用或主题来分配收集任务单元,因此可以在 etcd 上创建一个以应用或主题命名的目录,并将这个应用或主题相关的所有机器 IP 地址以子目录的形式存储到目录上,然后设置一个 etcd 递归的 Watcher,递归式的监控应用或主题的目录下所有信息的变动。这样就实现了机器 IP(消息)变动的时候,能够实时通知到收集器调整任务分配。
-
系统中信息需要动态自动获取与人工干预修改信息请求内容的情况:通常是暴露出接口,例如 JMX 接口,来获取一些运行时的信息。引入 etcd 之后,就不用自己实现一套方案了,只要将这些信息存放到指定的 etcd 目录中即可,etcd 的这些目录就可以通过 HTTP 的接口在外部进行访问。
分布式通知与协调
分布式系统的通知与协调,与消息发布和订阅有些相似。都用到了 etcd 中的 Watcher 机制,通过注册与异步通知机制,实现分布式环境下不同系统之间的通知与协调,从而对数据变更做到实时处理。区别在于,前者服务于分布式系统本身,后者服务于上层应用级别。
实现方式通常是这样:不同系统都在 etcd 上对同一个目录进行注册,同时设置 Watcher 观测该目录的变化(如果对子目录的变化也有需要,可以设置递归模式),当某个系统更新了 etcd 的目录,那么设置了 Watcher 的系统就会收到通知,并作出相应处理。
-
通过 etcd 进行低耦合的心跳检测:检测系统和被检测系统通过 etcd 上某个目录关联而非直接关联起来,这样可以大大减少系统的耦合性。
-
通过 etcd 完成系统调度:某系统由控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改了 etcd 上某些目录节点的状态,而 etcd 就把这些变化通知给注册了 Watcher 的推送系统客户端,推送系统再作出相应的推送任务。
-
通过 etcd 完成工作汇报:大部分类似的任务分发系统,子任务启动后,到 etcd 来注册一个临时工作目录,并且定时将自己的进度进行汇报(将进度写入到这个临时目录),这样任务管理者就能够实时知道任务进度。
分布式锁
因为 etcd 使用 Raft 算法保证了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易用于实现分布式锁。锁服务有两种使用方式,一是保持独占,二是控制时序。
-
保持独占:即独占锁,所有获取锁的用户最终只有一个可以得到。etcd 为此提供了一套实现分布式锁原子操作 CAS(Compare And Swap)的 API。通过设置 prevExist 值,可以保证在多个节点同时去创建某个目录时,只有一个成功。而创建成功的用户就可以认为是获得了锁。
-
控制时序:即时序锁,所有想要获得锁的用户都会被安排执行,但是获得锁的顺序也是全局唯一的,同时决定了执行顺序。etcd 为此也提供了一套 API(自动创建有序键),对一个目录建值时指定为 POST 动作,这样 etcd 会自动在目录下生成一个当前最大的值为键,存储这个新的值(客户端编号)。同时还可以使用 API 按顺序列出所有当前目录下的键值。此时这些键的值就是客户端的时序,而这些键中存储的值可以是代表客户端的编号。
分布式队列
分布式队列与上述提到的分布式时序锁的用法类似,即:创建一个先进先出的队列,并保证顺序。另一种比较有意思的实现是在保证队列达到某个条件时再统一按顺序执行。这种方法的实现可以在 /queue 这个目录中另外建立一个 /queue/condition 节点。
- condition 可以表示队列大小:比如一个大的任务需要很多小任务就绪的情况下才能执行,每次有一个小任务就绪,就给这个 condition 数字加 1,直到达到大任务规定的数字,再开始执行队列里的一系列小任务,最终执行大任务。
- condition 可以表示某个任务在不在队列:这个任务可以是所有排序任务的首个执行程序,也可以是拓扑结构中没有依赖的点。通常,必须执行这些任务后才能执行队列中的其他任务。
- condition 可以表示其它的一类开始执行任务的通知:可以由控制程序指定,当 condition 出现变化时,开始执行队列任务。
集群监控
通过 etcd 来实现集群监控非常简单且实时性强。应用了 etcd 的两大功能:
- Watcher 机制:当某个节点消失或有变动时,Watcher 会第一时间发现并告知用户。
- key TTL 机制:比如每隔 30s 发送一次心跳使代表该机器存活的节点继续存在,否则节点消失。
这样就可以第一时间检测到各节点的健康状态,以完成集群监控的要求。
Leader 竞选
另外,利用分布式锁,可以简单的实现集群多节点的 Leader 竞选。这种场景通常是一些长时间的 CPU 计算或者使用 I/O 操作的机器,只需要竞选出的 Leader 计算或处理一次,就可以把结果复制给其他的 Follower。从而避免重复劳动,节省计算资源。
经典场景就是在搜索系统中建立全量索引:如果每个机器都进行一遍索引的建立,不但耗时而且建立索引的一致性不能保证。通过在 etcd 的 CAS 机制同时创建一个节点,创建成功的机器作为 Leader,进行索引计算,然后把计算结果分发到其它节点。
负载均衡
通过 etcd 可以实现一个软负载均衡器,体现在两个方面:
- etcd 本身分布式架构存储的信息访问支持负载均衡:etcd 集群化以后,每个 etcd 的核心节点都可以处理用户的请求。所以,虽然 etcd 更多的存储控制系统的元数据,但把数据量小且访问频繁的消息数据直接存储到 etcd 中也是个不错的选择,如:业务系统中常用的二级代码表(在表中存储代码,在 etcd 中存储代码所代表的具体含义,业务系统调用查表的过程,就需要查找表中代码的含义)。
- 利用 etcd 维护一个负载均衡节点表:etcd 可以监控一个集群中多个节点的状态,当有一个请求发过来后,可以轮询式的把请求转发给存活着的多个状态。类似 KafkaMQ 通过 ZooKeeper 来维护生产者和消费者的负载均衡。同样也可以用 etcd 来做 ZooKeeper 的工作。
etcd 的安装与部署
服务进程
etcd 目前默认使用 2379 端口提供 HTTP API 服务,2380 端口提供 Peer 通信(这两个端口已经被 IANA 官方预留给 etcd),在之前的版本中,可能会分别使用 4001 和 7001,在使用的过程中需要注意这个区别。
虽然 etcd 也支持单点部署,但是在生产环境中推荐集群方式部署,一般 etcd 节点数会选择 3、5、7。etcd 会保证所有的节点都会保存数据,并保证数据的一致性和正确性。
单点部署
编译部署
因为 etcd 是 Golang 编写的,安装只需要下载对应的二进制文件,并放到合适的路径就行。如果在测试环境,启动一个单点的 etcd 服务,只需要运行 etcd 执行即可。
git clone https://github.com/etcd-io/etcd.git
cd etcd
./build
使用 build 脚本构建会在当前项目的 bin 目录生产 etcd 和 etcdctl 可执行程序。etcd 就是 etcd Server,而 etcdctl 主要为 etcd Server 提供指令行操作。
查看版本:
$ ./bin/etcd --version
etcd Version: 3.5.0-pre
Git SHA: ab4cc3cae
Go Version: go1.14.4
Go OS/Arch: darwin/amd64
$ ./bin/etcdctl version
etcdctl version: 3.5.0-pre
API version: 3.5
启动 etcd Server:
$ ./bin/etcd
"level":"info","ts":"2020-10-04T07:39:14.751+0800","caller":"etcdmain/etcd.go:69","msg":"Running: ","args":["./bin/etcd"]
"level":"info","ts":"2020-10-04T07:39:14.751+0800","caller":"etcdmain/etcd.go:94","msg":"failed to detect default host","error":"default host not supported on darwin_amd64"
"level":"warn","ts":"2020-10-04T07:39:14.751+0800","caller":"etcdmain/etcd.go:99","msg":"'data-dir' was empty; using default","data-dir":"default.etcd"
"level":"info","ts":"2020-10-04T07:39:14.751+0800","caller":"embed/etcd.go:113","msg":"configuring peer listeners","listen-peer-urls":["http://localhost:2380"]
"level":"info","ts":"2020-10-04T07:39:14.752+0800","caller":"embed/etcd.go:121","msg":"configuring client listeners","listen-client-urls":["http://localhost:2379"]
"level":"info","ts":"2020-10-04T07:39:14.753+0800","caller":"embed/etcd.go:266","msg":"starting an etcd server","etcd-version":"3.5.0-pre","git-sha":"ab4cc3cae","go-version":"go1.14.4","go-os":"darwin","go-arch":"amd64","max-cpu-set":4,"max-cpu-available":4,"member-initialized":false,"name":"default","data-dir":"default.etcd","wal-dir":"","wal-dir-dedicated":"","member-dir":"default.etcd/member","force-new-cluster":false,"heartbeat-interval":"100ms","election-timeout":"1s","initial-election-tick-advance":true,"snapshot-count":100000,"snapshot-catchup-entries":5000,"initial-advertise-peer-urls":["http://localhost:2380"],"listen-peer-urls":["http://localhost:2380"],"advertise-client-urls":["http://localhost:2379"],"listen-client-urls":["http://localhost:2379"],"listen-metrics-urls":[],"cors":["*"],"host-whitelist":["*"],"initial-cluster":"default=http://localhost:2380","initial-cluster-state":"new","initial-cluster-token":"etcd-cluster","quota-size-bytes":2147483648,"pre-vote":false,"initial-corrupt-check":false,"corrupt-check-time-interval":"0s","auto-compaction-mode":"periodic","auto-compaction-retention":"0s","auto-compaction-interval":"0s","discovery-url":"","discovery-proxy":""
"level":"info","ts":"2020-10-04T07:39:14.764+0800","caller":"etcdserver/backend.go:78","msg":"opened backend db","path":"default.etcd/member/snap/db","took":"9.908726ms"
"level":"info","ts":"2020-10-04T07:39:14.862+0800","caller":"etcdserver/raft.go:444","msg":"starting local member","local-member-id":"8e9e05c52164694d","cluster-id":"cdf818194e3a8c32"
"level":"info","ts":"2020-10-04T07:39:14.862+0800","caller":"raft/raft.go:1528","msg":"8e9e05c52164694d switched to configuration voters=()"
"level":"info","ts":"2020-10-04T07:39:14.862+0800","caller":"raft/raft.go:701","msg":"8e9e05c52164694d became follower at term 0"
"level":"info","ts":"2020-10-04T07:39:14.862+0800","caller":"raft/raft.go:383","msg":"newRaft 8e9e05c52164694d [peers: [], term: 0, commit: 0, applied: 0, lastindex: 0, lastterm: 0]"
"level":"info","ts":"2020-10-04T07:39:14.863+0800","caller":"raft/raft.go:701","msg":"8e9e05c52164694d became follower at term 1"
"level":"info","ts":"2020-10-04T07:39:14.863+0800","caller":"raft/raft.go:1528","msg":"8e9e05c52164694d switched to configuration voters=(10276657743932975437)"
"level":"warn","ts":"2020-10-04T07:39:14.888+0800","caller":"auth/store.go:1231","msg":"simple token is not cryptographically signed"
"level":"info","ts":"2020-10-04T07:39:14.912+0800","caller":"etcdserver/quota.go:94","msg":"enabled backend quota with default value","quota-name":"v3-applier","quota-size-bytes":2147483648,"quota-size":"2.1 GB"
"level":"info","ts":"2020-10-04T07:39:14.924+0800","caller":"etcdserver/server.go:752","msg":"starting etcd server","local-member-id":"8e9e05c52164694d","local-server-version":"3.5.0-pre","cluster-version":"to_be_decided"
"level":"info","ts":"2020-10-04T07:39:14.925+0800","caller":"etcdserver/server.go:640","msg":"started as single-node; fast-forwarding election ticks","local-member-id":"8e9e05c52164694d","forward-ticks":9,"forward-duration":"900ms","election-ticks":10,"election-timeout":"1s"
"level":"warn","ts":"2020-10-04T07:39:14.925+0800","caller":"etcdserver/metrics.go:212","msg":"failed to get file descriptor usage","error":"cannot get FDUsage on darwin"
"level":"info","ts":"2020-10-04T07:39:14.925+0800","caller":"raft/raft.go:1528","msg":"8e9e05c52164694d switched to configuration voters=(10276657743932975437)"
"level":"info","ts":"2020-10-04T07:39:14.925+0800","caller":"membership/cluster.go:385","msg":"added member","cluster-id":"cdf818194e3a8c32","local-member-id":"8e9e05c52164694d","added-peer-id":"8e9e05c52164694d","added-peer-peer-urls":["http://localhost:2380"]
"level":"info","ts":"2020-10-04T07:39:14.927+0800","caller":"embed/etcd.go:513","msg":"serving peer traffic","address":"127.0.0.1:2380"
"level":"info","ts":"2020-10-04T07:39:14.927+0800","caller":"embed/etcd.go:235","msg":"now serving peer/client/metrics","local-member-id":"8e9e05c52164694d","initial-advertise-peer-urls":["http://localhost:2380"],"listen-peer-urls":["http://localhost:2380"],"advertise-client-urls":["http://localhost:2379"],"listen-client-urls":["http://localhost:2379"],"listen-metrics-urls":[]
"level":"info","ts":"2020-10-04T07:39:15.866+0800","caller":"raft/raft.go:788","msg":"8e9e05c52164694d is starting a new election at term 1"
"level":"info","ts":"2020-10-04T07:39:15.866+0800","caller":"raft/raft.go:714","msg":"8e9e05c52164694d became candidate at term 2"
"level":"info","ts":"2020-10-04T07:39:15.866+0800","caller":"raft/raft.go:848","msg":"8e9e05c52164694d received MsgVoteResp from 8e9e05c52164694d at term 2"
"level":"info","ts":"2020-10-04T07:39:15.867+0800","caller":"raft/raft.go:766","msg":"8e9e05c52164694d became leader at term 2"
"level":"info","ts":"2020-10-04T07:39:15.867+0800","caller":"raft/node.go:327","msg":"raft.node: 8e9e05c52164694d elected leader 8e9e05c52164694d at term 2"
"level":"info","ts":"2020-10-04T07:39:15.868+0800","caller":"etcdserver/server.go:2285","msg":"setting up initial cluster version","cluster-version":"3.5"
"level":"info","ts":"2020-10-04T07:39:15.876+0800","caller":"membership/cluster.go:523","msg":"set initial cluster version","cluster-id":"cdf818194e3a8c32","local-member-id":"8e9e05c52164694d","cluster-version":"3.5"
"level":"info","ts":"2020-10-04T07:39:15.876+0800","caller":"embed/serve.go:97","msg":"ready to serve client requests"
"level":"info","ts":"2020-10-04T07:39:15.876+0800","caller":"api/capability.go:75","msg":"enabled capabilities for version","cluster-version":"3.5"
"level":"info","ts":"2020-10-04T07:39:15.876+0800","caller":"etcdserver/server.go:2305","msg":"cluster version is updated","cluster-version":"3.5"
"level":"info","ts":"2020-10-04T07:39:15.876+0800","caller":"etcdserver/server.go:1863","msg":"published local member to cluster through raft","local-member-id":"8e9e05c52164694d","local-member-attributes":"Name:default ClientURLs:[http://localhost:2379]","request-path":"/0/members/8e9e05c52164694d/attributes","cluster-id":"cdf818194e3a8c32","publish-timeout":"7s"
"level":"info","ts":"2020-10-04T07:39:15.876+0800","caller":"etcdmain/main.go:47","msg":"notifying init daemon"
"level":"info","ts":"2020-10-04T07:39:15.876+0800","caller":"etcdmain/main.go:53","msg":"successfully notified init daemon"
"level":"info","ts":"2020-10-04T07:39:15.877+0800","caller":"embed/serve.go:139","msg":"serving client traffic insecurely; this is strongly discouraged!","address":"127.0.0.1:2379"
- name 表示节点名称,默认为 default。
- data-dir 表示 WAL 日志和 Snapshot 数据储存目录,默认为 ./default.etcd/ 目录。
- 使用 http://localhost:2380 和 etcd Cluster 中其他节点通信。
- 使用 http://localhost:2379 提供 HTTP API 服务,与客户端通信。
- heartbeat 为 100ms,表示 Leader 多久发送一次心跳到所有 Followers。
- election-timeout 为 1s,该参数的作用是重新投票的超时时间,如果 Follow 在该时间间隔内没有收到 Leader 发出的心跳包,就会触发重新投票。
- snapshot-count 为 100000,该参数的作用是指定有多少次事务被提交后触发快照截取动作并持久化到磁盘。
- cluster-id 为 cdf818194e3a8c32。
- raft.node 为 8e9e05c52164694d。
- 启动的时候,会运行 Raft,选举出 Leader:
elected leader 8e9e05c52164694d at term 2
。
上述方法只是简单的启动了一个 etcd Server。当然,在生产环境中,通常使用 Systemd 来进行管理。
- 建立相关目录:
$ mkdir -p /var/lib/etcd/
$ mkdir -p /etc/etcd/config/
- 设定 etcd 配置文件:
$ cat <<EOF | sudo tee /etc/etcd/config/etcd.conf
#以上是关于云计算与云原生 — ETCD 数据库完全解析的主要内容,如果未能解决你的问题,请参考以下文章