# etcd-presentation **Repository Path**: gitchenjh/etcd-presentation ## Basic Information - **Project Name**: etcd-presentation - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-08-04 - **Last Updated**: 2022-09-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ETCD入门及高可用实践 ## 概要介绍 etcd 是 CoreOS 团队于 2013 年 6 月发起的开源项目,它的目标是构建一个高可用的分布式键值(key-value)数据库。具有以下特点: - 简单:安装配置简单,而且提供了 HTTP API 进行交互,使用也很简单 - 键值对存储:将数据存储在分层组织的目录中,如同在标准文件系统中 - 监测变更:监测特定的键或目录以进行更改,并对值的更改做出反应 - 安全:支持 SSL 证书验证 - 快速:根据官方提供的 benchmark 数据,单实例支持每秒 2k+ 读操作 - 可靠:采用 raft 算法,实现分布式系统数据的可用性和一致性 - etcd 采用 Go 语言编写,它具有出色的跨平台支持,很小的二进制文件和强大的社区。 etcd 机器之间的通信通过 Raft 算法处理。 etcd项目地址:[https://github.com/coreos/etcd/](https://github.com/coreos/etcd/) > Distributed reliable key-value store for the most critical data of a distributed system ## 工作原理 ### 1.整体架构 ![架构图](./image/etcd-arch.jpg) 从etcd的架构图中我们可以看到,etcd主要分为四个部分。 - HTTP Server: 用于处理用户发送的API请求以及其它etcd节点的同步与心跳信息请求。 - Store:用于处理etcd支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等,是etcd对用户提供的大多数API功能的具体实现。 - Raft:Raft强一致性算法的具体实现,是etcd的核心。 - WAL:(Write Ahead Log)的作用是记录了整个数据变化的全部历程。在 etcd 中,所有数据的修改在提交前,都要先写入到 WAL 中。使用 WAL 进行数据的存储使得 etcd 拥有两个重要功能。 - **故障快速恢复**: 当你的数据遭到破坏时,就可以通过执行所有 WAL 中记录的修改操作,快速从最原始的数据恢复到数据损坏前的状态。 - **数据回滚(undo)/ 重做(redo)**:因为所有的修改操作都被记录在 WAL 中,需要回滚或重做,只需要方向或正向执行日志中的操作即可。 通常,一个用户的请求发送过来,会经由HTTP Server转发给Store进行具体的事务处理,如果涉及到节点的修改,则交给Raft模块进行状态的变更、日志的记录,然后再同步给别的etcd节点以确认数据提交最后进行数据的提交,再次同步。 ### 2. 数据一致性解决方案 etcd 使用 raft 协议来维护集群内各个节点状态的一致性。简单说,etcd 集群是一个分布式系统,由多个节点相互通信构成整体对外服务,每个节点都存储了完整的数据,并且通过 Raft 协议保证每个节点维护的数据是一致的。 每个 etcd 节点都维护了一个状态机,并且,任意时刻至多存在一个有效的主节点。主节点处理所有来自客户端写操作,通过 Raft 协议保证写操作对状态机的改动会可靠的同步到其他节点。 ### 3. 数据模型 etcd 的设计目标是用来存放非频繁更新的数据,提供可靠的 Watch插件,它暴露了键值对的历史版本,以支持低成本的快照、监控历史事件。这些设计目标要求它使用一个持久化的、多版本的、支持并发的数据数据模型。 当 etcd 键值对的新版本保存后,先前的版本依然存在。从效果上来说,键值对是不可变的,etcd 不会对其进行 in-place (就地运算) 的更新操作,而总是生成一个新的数据结构。为了防止历史版本无限增加,etcd 的存储支持压缩(Compact)以及删除老旧版本。 一个很重要的资料: [raft算法论文翻译](https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md) ## ETCD常见使用场景 ### 1. 服务发现(Service Discovery) 服务发现也是分布式系统中最常见的问题之一,即在同一个分布式集群中的进程或服务,要如何才能找到对方并建立连接。本质上来说,服务发现就是想要了解集群中是否有进程在监听 udp 或 tcp 端口,并且通过名字就可以查找和连接。要解决服务发现的问题,需要有下面三大支柱,缺一不可。 1. 一个强一致性、高可用的服务存储目录。 基于 Raft 算法的 etcd 天生就是这样一个强一致性高可用的服务存储目录。 2. 一种注册服务和监控服务健康状态的机制。 用户可以在 etcd 中注册服务,并且对注册的服务设置key TTL,定时保持服务的心跳以达到监控健康状态的效果。 3. 一种查找和连接服务的机制。 通过在 etcd 指定的主题下注册的服务也能在对应的主题下查找到。 ![图 1 服务发现示意图](./image/etcd-service-discovery1.jpg) 图 1 服务发现示意图 下面我们来看服务发现对应的具体场景。 微服务协同工作架构中,服务动态添加。随着 Docker 容器的流行,多种微服务共同协作,构成一个相对功能强大的架构的案例越来越多。透明化的动态添加这些服务的需求也日益强烈。通过服务发现机制,在 etcd 中注册某个服务名字的目录,在该目录下存储可用的服务节点的 IP。在使用服务的过程中,只要从服务目录下查找可用的服务节点去使用即可。 ![图 2 微服务协同工作](./image/etcd-service-discovery2.jpg) 图 2 微服务协同工作 PaaS 平台中应用多实例与实例故障重启透明化。PaaS 平台中的应用一般都有多个实例,通过域名,不仅可以透明的对这多个实例进行访问,而且还可以做到负载均衡。但是应用的某个实例随时都有可能故障重启,这时就需要动态的配置域名解析(路由)中的信息。通过 etcd 的服务发现功能就可以轻松解决这个动态配置的问题。 ![图 3 云平台多实例透明化](./image/etcd-service-discovery3.jpg) 图 3 云平台多实例透明化 ### 2. 消息发布与订阅 在分布式系统中,最适用的一种组件间通信方式就是消息发布与订阅。即构建一个配置共享中心,数据提供者在这个配置中心发布消息,而消息使用者则订阅他们关心的主题,一旦主题有消息发布,就会实时通知订阅者。通过这种方式可以做到分布式系统配置的集中式管理与动态更新。 - 应用中用到的一些配置信息放到 etcd 上进行集中管理。这类场景的使用方式通常是这样:应用在启动的时候主动从 etcd 获取一次配置信息,同时,在 etcd 节点上注册一个 Watcher 并等待,以后每次配置有更新的时候,etcd 都会实时通知订阅者,以此达到获取最新配置信息的目的。 - 分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存放在 etcd 中,供各个客户端订阅使用。使用 etcd 的`key TTL`功能可以确保机器状态是实时更新的。 ![图 4 消息发布与订阅](./image/etcd-topic.jpg) 图 4 消息发布与订阅 ### 3. 负载均衡 分布式系统中,为了保证服务的高可用以及数据的一致性,通常都会把数据和服务部署多份,以此达到对等服务,即使其中的某一个服务失效了,也不影响使用。由此带来的坏处是数据写入性能下降,而好处则是数据访问时的负载均衡。因为每个对等服务节点上都存有完整的数据,所以用户的访问流量就可以分流到不同的机器上。 - etcd 本身分布式架构存储的信息访问支持负载均衡。etcd 集群化以后,每个 etcd 的核心节点都可以处理用户的请求。所以,把数据量小但是访问频繁的消息数据直接存储到 etcd 中也是个不错的选择。 - 利用 etcd 维护一个负载均衡节点表。etcd 可以监控一个集群中多个节点的状态,当有一个请求发过来后,可以轮询式的把请求转发给存活着的多个状态。类似 KafkaMQ,通过ZooKeeper来维护生产者和消费者的负载均衡。同样也可以用 etcd 来做ZooKeeper的工作。 ![负载均衡](./image/etcd-balance.jpg) 图 5 负载均衡 ### 4. 分布式通知与协调 这里说到的分布式通知与协调,与消息发布和订阅有些相似。都用到了 etcd 中的 Watcher 机制,通过注册与异步通知机制,实现分布式环境下不同系统之间的通知与协调,从而对数据变更做到实时处理。实现方式通常是这样:不同系统都在 etcd 上对同一个目录进行注册,同时设置 Watcher 观测该目录的变化(如果对子目录的变化也有需要,可以设置递归模式),当某个系统更新了 etcd 的目录,那么设置了 Watcher 的系统就会收到通知,并作出相应处理。 - 通过 etcd 进行低耦合的心跳检测。检测系统和被检测系统通过 etcd 上某个目录关联而非直接关联起来,这样可以大大减少系统的耦合性。 - 通过 etcd 完成系统调度。某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改了 etcd 上某些目录节点的状态,而 etcd 就把这些变化通知给注册了 Watcher 的推送系统客户端,推送系统再作出相应的推送任务。 - 通过 etcd 完成工作汇报。大部分类似的任务分发系统,子任务启动后,到 etcd 来注册一个临时工作目录,并且定时将自己的进度进行汇报(将进度写入到这个临时目录),这样任务管理者就能够实时知道任务进度。 ![图 6 分布式协同工作](./image/etcd-coordinate.jpg) 图 6 分布式协同工作 ### 5. 分布式锁 因为 etcd 使用 Raft 算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。锁服务有两种使用方式,一是保持独占,二是控制时序。 - 保持独占即所有获取锁的用户最终只有一个可以得到。etcd 为此提供了一套实现分布式锁原子操作 CAS(`CompareAndSwap`)的 API。通过设置`prevExist`值,可以保证在多个节点同时去创建某个目录时,只有一个成功。而创建成功的用户就可以认为是获得了锁。 - 控制时序,即所有想要获得锁的用户都会被安排执行,但是获得锁的顺序也是全局唯一的,同时决定了执行顺序。etcd 为此也提供了一套 API(自动创建有序键),对一个目录建值时指定为`POST`动作,这样 etcd 会自动在目录下生成一个当前最大的值为键,存储这个新的值(客户端编号)。同时还可以使用 API 按顺序列出所有当前目录下的键值。此时这些键的值就是客户端的时序,而这些键中存储的值可以是代表客户端的编号。 ![图 7 分布式锁](./image/etcd-lock.jpg) 图 7 分布式锁 ### 6. 集群监控与 Leader 竞选 通过 etcd 来进行监控实现起来非常简单并且实时性强。 1. 前面几个场景已经提到 Watcher 机制,当某个节点消失或有变动时,Watcher 会第一时间发现并告知用户。 2. 节点可以设置`TTL key`,比如每隔 30s 发送一次心跳使代表该机器存活的节点继续存在,否则节点消失。 这样就可以第一时间检测到各节点的健康状态,以完成集群的监控要求。 另外,使用分布式锁,可以完成 Leader 竞选。这种场景通常是一些长时间 CPU 计算或者使用 IO 操作的机器,只需要竞选出的 Leader 计算或处理一次,就可以把结果复制给其他的 Follower。从而避免重复劳动,节省计算资源。 这个的经典场景是搜索系统中建立全量索引。如果每个机器都进行一遍索引的建立,不但耗时而且建立索引的一致性不能保证。通过在 etcd 的 CAS 机制同时创建一个节点,创建成功的机器作为 Leader,进行索引计算,然后把计算结果分发到其它节点。 ![图 8 Leader 竞选](./image/etcd-leader.jpg) 图 8 Leader 竞选 ## 简单使用 ### kv操作 - set 指定某个键的值。例如: ``` $ etcdctl set /testdir/testkey "Hello world" Hello world ``` 支持的选项包括: ``` --ttl '0' 该键值的超时时间(单位为秒),不配置(默认为0)则永不超时 --swap-with-value value 若该键现在的值是value,则进行设置操作 ``` - mk 如果给定的键不存在,则创建一个新的键值。例如: ``` $ etcdctl mk /testdir/testkey "Hello world" Hello world ``` 当键存在的时候,执行该命令会报错,例如: ``` $ etcdctl mk /testdir/testkey "Hello world" Error: 105: Key already exists (/testdir/testkey) [8] ``` 支持的选项为: ``` --ttl '0' 超时时间(单位为秒),不配置(默认为 0)。则永不超时 ``` - mkdir 如果给定的键目录不存在,则创建一个新的键目录。例如: ``` $ etcdctl mkdir testdir2 ``` 当键目录存在的时候,执行该命令会报错,例如: ``` $ etcdctl mkdir testdir2 Error: 105: Key already exists (/testdir2) [9] ``` 支持的选项为: ``` --ttl '0' 超时时间(单位为秒),不配置(默认为0)则永不超时。 ``` - setdir 创建一个键目录。如果目录不存在就创建,如果目录存在更新目录TTL。 ``` $ etcdctl setdir testdir3 ``` 支持的选项为: ``` --ttl '0' 超时时间(单位为秒),不配置(默认为0)则永不超时。 ``` - rm 删除某个键值。例如: ``` $ etcdctl rm /testdir/testkey PrevNode.Value: Hello ``` 当键不存在时,则会报错。例如: ``` $ etcdctl rm /testdir/testkey Error: 100: Key not found (/testdir/testkey) [7] ``` 支持的选项为: ``` --dir 如果键是个空目录或者键值对则删除 --recursive 删除目录和所有子键 --with-value 检查现有的值是否匹配 ``` - rmdir 删除一个空目录,或者键值对。 ``` $ etcdctl setdir dir1 $ etcdctl rmdir dir1 ``` 若目录不空,会报错: ``` $ etcdctl set /dir/testkey hi hi $ etcdctl rmdir /dir Error: 108: Directory not empty (/dir) [17] ``` - update 当键存在时,更新值内容。例如: ``` $ etcdctl update /testdir/testkey "Hello" Hello ``` 当键不存在时,则会报错。例如: ``` $ etcdctl update /testdir/testkey2 "Hello" Error: 100: Key not found (/testdir/testkey2) [6] ``` 支持的选项为: ``` --ttl '0' 超时时间(单位为秒),不配置(默认为 0)则永不超时。 ``` - updatedir 更新一个已经存在的目录。 ``` $ etcdctl updatedir testdir2 ``` 支持的选项为: ``` --ttl '0' 超时时间(单位为秒),不配置(默认为0)则永不超时。 ``` - get 获取指定键的值。例如: ``` $ etcdctl get /testdir/testkey Hello world ``` 当键不存在时,则会报错。例如: ``` $ etcdctl get /testdir/testkey2 Error: 100: Key not found (/testdir/testkey2) [5] ``` 支持的选项为: ``` --sort 对结果进行排序 --consistent 将请求发给主节点,保证获取内容的一致性。 ``` - ls 列出目录(默认为根目录)下的键或者子目录,默认不显示子目录中内容。 例如: ``` $ etcdctl ls /testdir /testdir2 /dir $ etcdctl ls dir /dir/testkey ``` 支持的选项包括: ``` --sort 将输出结果排序 --recursive 如果目录下有子目录,则递归输出其中的内容 -p 对于输出为目录,在最后添加/进行区分 ``` ### watch操作 - watch 监测一个键值的变化,一旦键值发生更新,就会输出最新的值并退出。 例如:用户更新testkey键值为Hello watch。 ``` $ etcdctl get /testdir/testkey Hello world $ etcdctl set /testdir/testkey "Hello watch" Hello watch $ etcdctl watch testdir/testkey Hello watch ``` 支持的选项包括: ``` --forever 一直监测直到用户按CTRL+C退出 --recursive 返回所有的键值和子键值 ``` - exec-watch 监测一个键值的变化,一旦键值发生更新,就执行给定命令。 例如:用户更新testkey键值。 ``` $ etcdctl exec-watch testdir/testkey -- sh -c 'ls' config Documentation etcd etcdctl README-etcdctl.md README.md READMEv2-etcdctl.md ``` 支持的选项包括: ``` --recursive 返回所有的键值和子键值 ``` ### lease操作 ETCD租约特性简介 1. 租约具有生命周期,需要为租约授予一个 TTL(time to live) 2. 将租约绑定到一个 key 上,那么 key 的生命周期与租约所授予的 TTL 一致 3. 可续租 4. 可撤销租约 - lease grant 获取一份租约 ```bash etcdctl lease grant 30 lease 2ccc684b35cb7c0f granted with TTL(30s) ``` 创建一条数据并绑定到上面租约上 ```bash etcdctl put --lease=2ccc684b35cb7c0f "kk" "hello word" ``` 租约的编号为 2ccc684b35cb7c0f 租约的 TTL 为 30s 在 30s 过后这个租约将消失,所绑定的 key 也会消失 - lease list 列出所有租约 ```bash etcdctl lease list found 1 leases 2ccc684b35cb7c0f ``` 当前有一个编号为2ccc684b35cb7c0f的租约 - lease timetolive 获取租约的剩余时间 ```bash etcdctl lease timetolive 2ccc684b35cb7c0f lease 2ccc684b35cb7c0f granted with TTL(30s), remaining(5s) ``` 租约的编号为 2ccc684b35cb7c0f,剩余时间为5s - lease keep-alive 阻塞方法,持续为租约续约 ```bash etcdctl lease keep-alive 2ccc684b35cb7c0f lease 2ccc684b35cb7c0f keepalived with TTL(30) lease 2ccc684b35cb7c0f keepalived with TTL(30) lease 2ccc684b35cb7c0f keepalived with TTL(30) ``` 租约的编号为2ccc684b35cb7c0f,持续续约 - lease revok 收回租约,并删除租约上绑定的key ```bash etcdctl lease revoke 2ccc684b35cb7c0f lease 2ccc684b35cb7c0f revoked ``` 租约的编号为2ccc684b35cb7c0f,已收回 ### lock操作 - lock 获取锁 ```bash etcdctl lock /lock/kk /lock/kk/694d73b8455c3111 $ ``` 获取到锁 /lock/kk ## ETCD在kkbinlog中的高可用实践 [etcd选主实现故障主备秒级切换高可用架构](http://www.kailing.pub/article/index/arcid/254.html)