https://xie.infoq.cn/article/3329de088beb60f5803855895
一、白话 etcd 与 zookeeper
二、etcd 的 4 个核心机制
三、Leader 选举与客户端交互
四、etcd 的应用场景
4.1. kubernetes 大脑
4.2. 服务注册与发现
4.3. 健康检查与状态变更通知
4.4.分布式锁
4.5.实现消息队列(纯扯淡)
五、etcd 安装
六、jetcd 的编码实现配置管理
大家好啊,我是字母哥,今天写一篇关于 etcd 的文章,其实网上也有很多关于 etcd 的介绍,「我就简明扼要,总结提炼,期望大家通过这一篇文章掌握 etcd 的核心知识以及编码技能」!
本文首先用大白话给大家介绍一下 etcd 是什么?这部分内容网上已经有很多了。
etcd 有哪些应用场景?这些应用场景的核心原理是什么?
最后不能光动嘴不动手。先搭建一个 etcd 单机版,再使用 java 的客户端操作 etcd 数据。
本文旨在帮助大家理解 etcd,从宏观角度俯瞰 etcd 全局,掌握 etcd 的基本操作技能。后续我还会写一个系列的文章,将每一种应用场景代码化,期待大家关注我和我的公众号:字母哥杂谈。后续计划章节内容如下:
《搭建高可用 etcd 集群》
《基于 etcd 实现分布式锁(java 代码实现)》
《基于 etcd 实现配置变更通知(java 代码实现)》
《基于 etcd 实现服务注册与发现(java 代码实现)》
《基于 etcd 实现分布式系统节点 leader 选举(java 代码实现)》
用过 linux 的朋友请举手,好的,我看见了! 在 linux 中所有自动安装的系统软件配置文件都存储在一个名为/etc
的目录中。“d”表示「distributed」分布式,etcd 为分布式模型,所以 etcd 的核心应用场景是:「分布式系统的配置信息存储」。
网上很多文章上来第一句话照搬英文官网:「etcd 是一个高度一致的分布式键值存储系统」。很多朋友看完就问了,这玩意和 redis 有啥区别? 笔者要说,真的不要这么比,etcd 从名字上就已经告诉你了,它是存储配置信息(元数据)的。和 redis 在架构应用上就不在一个层面,它对标的产品应该是 zookeeper。虽然 zookeeper 在很多 java 的分布式系统的应用中比较广泛,但是 etcd 作为后起之秀,乘 kubernetes 的东风,大有超越 zookeeper 的趋势。
zookeeper 是使用 java 写的, etcd 是使用 go 语言编写的。zookeeper 使用了 TCP 协议,其交互报文规则是完全自定义的,如果不使用 zookeeper 提供的 SDK 就无法操作数据。而 etcd 使用的是 google 的 gRPC 协议,普适性更好一些。
zookeeper 对于一次请求,开启一个 socket 进行监听。而 etcd 的监听管道 channel 可以反复被利用,从 IO 性能到系统资源的利用的角度,etcd 无疑是更优秀的。
zookeeper 使用 zab 协议保证集群节点配置信息的一致性,etcd 使用 raft 协议。期望详细了解 raft 协议的,点击《raft协议中文介绍》。
「大部分功能和 zookeeper 都是一样的,目前看 java 程序员用 zookeeper 的更多,其他程序员用 etcd 更多。都是基于习惯,但笔者推荐 etcd。」
etcd 以 key-value 的形式进行数据的存储. 配合下面的这四种机制,使得 etcd 的应用场景更加的广泛.
「Prefix 机制」:即前缀机制,也称「目录机制」,客户端向 etcd 放入 2 个键值对配置, 假如一个 key 是“/test/key1" , 另一个 key 是"/test/key2". 则通过前缀"/test"查询 etcd,返回一个列表包含 key 为“/test/key1" 和"/test/key2"的键值对数据;
「Watch 机制」:即监听机制,watch 机制针对某个 key 进行监听,也支持针对前缀进行范围监听. 当被监听的 key 或前缀范围发生变化的时候,客户端会收到变更通知;
「Lease 机制」:即租约机制(TTL,Time To Live),支持为 key-value 增加一个存活时间,超过这个时间 key-value 将过期被删除. 支持解约(删除 key-value),续约(增加 TTL 时间)等操作.
「Revision 机制」:每个 key 带有一个 全局唯一的 Revision 号,每一次事务加 1,它是全局唯一的,所以通过 Revision 可以判定数据写操作的顺序,对于实现分布式锁和队列非常有帮助.
使用 etcd 的时候,为了保证高可用,通常采用集群的部署方式。部署奇数个节点,通常建议是 3 个或 5 个,因为 etcd 集群之间需要「通过网络交互保证配置信息的一致性」。分布式多节点保证了高可用,但是节点太多了也不好,越多的节点网络消耗越大。至于为什么是奇数个?这就涉及到 Leader 选举的问题,奇数个方便投票出结果。
etcd 使用 raft 算法保证集群内各个节点之间数据一致性。raft 算法将集群内的节点分为 Leader, Follower, Candidate(候选人)这三个角色。
集群初始化的时候,每个节点都是 Follower 角色。通过 raft 算法选举投票,选出一个节点作为 Leader。
Leader 作为主节点,与其他节点维持心跳,并同步数据至其他节点。
当 Follower 一段时间内没有收到 leader 的心跳,就会将自己角色改为 Candidate 候选者,并发起一次新的选举,选举新的 Leader。
客户端在操作 etcd 集群数据的时候:
读操作:客户端可以访问任意节点进行数据的读操作
写操作:客户端访问任意节点进行写操作,如果该节点是 Follower,则将请求转发给 Leader。由 Leader 负责数据的写操作(增删改),将数据持久化,并向 Follower 发送同步数据的消息。
目前,etcd 的最典型的应用场景就是作为 Kubernetes 集群的大脑。
如果把 kubernetes 比作一个大饭店,那么 etcd 就是这个饭店的进销存+客户关系管理系统。
kubernetes 作为容器编排服务,将面向客户提供的各种服务进行合理的资源分配,服务编排。
不可避免地,有一些 kubernetes 集群的配置和状态数据,例如 pod 的数量、它们的状态、命名空间等。需要有一个统一的记录、管理的地方,它就是 etcd。
最重要的是:「etcd 具备 watch 监听的功能,一旦某个配置或者某个状态发生变更,集群内所有的服务全都可以通过 watch 监听机制实时获取到消息,进而做出进一步的响应。」 几乎 etcd 的所有应用场景,都是基于 watch 监听机制产生的,包括我们后面为大家介绍的服务注册发现和订阅通知。
其实 kubernetes 也利用 etcd 实现服务注册发现机制,但是上面的那张图不太好说明,我新画了两张图说明 etcd 在实现服务注册发现机制中的作用。
所谓的服务注册实现原理就是:服务在启动的时候,向 etcd 写入一条配置数据,该条配置数据说明自己的服务名称,服务 ip 地址,服务端口等信息。
所谓的服务发现实现原理举例:服务 C 的某个实例希望访问服务 A,服务 C 向 etcd 询问服务 A 的访问地址,etcd 响应结果:服务 A 有三个实例,地址列表如:xxx.xxx.xxx.xxx:端口
、yyy.yyy.yyy.yyy:端口
,zzz.zzz.zzz.zzz:端口
。服务 C 不需要访问三个实例,访问其中一个就可以得到结果,所以它按照自己的负载均衡算法选了一个,这个就叫做:客户端负载均衡。
衔接上文:「服务 C 下一次访问服务 A 的时候,还需要访问 etcd 么?答案是不需要」,它访问过一次之后,就会自己维护一个服务 A 访问地址的列表,「除非这个列表发生变化,否则是不会再次去询问 etcd 的。」那么一个服务怎么知道另一个服务的列表发生变化呢?比如:服务 A 的实例注册状态发生变化。可能是由于某种原因挂掉了,可能是 OOM 或者是网络问题等。
服务在注册到 etcd 之后,会保存一个关于该服务的注册配置信息,该注册配置信息由一个 TTL,etcd 同时会与该服务维持心跳。一旦超过 TTL 时间,无法得到服务的心跳响应,etcd 就认为该节点的健康状态出现了问题,就会将该节点下线(注册配置信息删除)。
服务在注册到 etcd 之后,会保持对 etcd 状态数据变更的监听,一旦获取监听结果:服务 A 的实例状态发生变更,该服务就会从 etcd 重新拉取服务 A 的注册列表。
跨进程跨系统的多线程操作公共资源,发生多线程竞争,为了避免线程不安全,需要使用分布式锁。如果多线程在单个进程内发生资源竞争,就是用 Lock 就可以了,不需要分布式锁。比如:你在 mysql 库里面有一个用户余额数据,多个进程内的线程同时更改这个值,可能发生并发的数据覆盖。为了避免这样的问题,多个进程排排队,A 先来,A 释放了锁 B 再来,B 释放了锁 C 再来。
举例:上图的 3 个 client 代表三个服务,都要操作某个资源数据。
在尝试调用加锁 API 的时候,client1 获取到的 revision=1,它优先获得加锁的资格。加锁就是加一个带有 revision 的配置记录。其他的所有的服务,都通过 watch 机制监听锁的释放。
client 在尝试调用加锁 API 的时候,被分配了 revision。并且按照 revision 进行了排序,监听距离自己 revision 差值最小,而且小于自己的 Revision,不会产生惊群效应。
我觉得使用 etcd 实现消息队列,是一种纯扯淡的做法。如果大家有什么异议,欢迎留言!
不是说做不了,确实写个 demo 是可以的。往 etcd 里面放数据,再通过 watch 机制进行监听,这不就是一个典型的消息队列么?扯淡!如果我只为了实现消息数据的发布订阅,其实有很多办法,我还用搭一个 etcd 集群?Spring 的 Event 机制,java 的响应式编程,哪怕自己搞一个 BlockQueue 呢,是不是都能实现消息的发布订阅。
我们之所以使用 kafka、RocketMQ 这样的消息队列,肯定是因为我们的异步数据达到一定的规模了。达到规模的异步消息数据传递根本就不是 etcd 的应用场景,正如本文开头所述:别忘了它叫做 etc 阿就 d,「它就是一个为分布式系统存储配置信息的,不是消息中间件。」
本文为大家安装一个可以用于实验环境的 etcd 单机版,我们可以用它进行实验,后续我还会写文章介绍 etcd 集群的安装方式.下载 etcd 的安装包,访问github-etcd,我使用的是 linux 操作系统 64 位,所以下载的安装包是:etcd-v3.5.4-linux-amd64.tar.gz .如果网络条件不允许,可以搜索"etcd 国内下载加速",选择合适的下载安装包进行安装即可.
首先将安装包解压,解压之后 cd 进入安装目录,将 etcd 和 etcdctl 两个命令 copy 到/usr/local/bin/
目录下面.
通过etcd --version
命令查看 etcd 的版本,同时可以验证安装结果.如果不想敲全路径,可以把/usr/local/bin
目录加入系统的 PATH 环境变量.
启动 etcd,这里的 listen-client-urls 和 advertise-client-urls 配置的作用是允许远程连接,0.0.0.0
表示监听当前服务器的所有 ip, 监听端口是 2379. 假如你的服务器有多块网卡,多个固定 ip,你想指定 etcd 服务在某一个 ip 上提供服务,就可以用这个 ip 替换0.0.0.0
etcd 启动之后, 可以通过 etcdctl 命令向 etcd 中添加配置,如下所示使用 put 命令添加一个key=/dir1
,value=aaa
的键值对数据.可以使用 get 命令获取该配置信息.
下面为大家介绍通过 java API 的方式操作 etcd 的数据,首先通过 maven 的坐标引入 jetcd.我使用的版本相对比较旧,最新的版本已经是 0.7.8,不过我在使用的时候出现了与 netty 版本不一致的情况,报错:找不到 netty 相关的一些类.所以我就回退到 0.3.0 版本,使用方式上都是一样的.
下面的代码是使用 jetcd 操作 etcd 的配置数据,实现了数据的写操作,读操作,删除操作.详细用法看代码吧.下面的代码是 Junit 5 的单元测试用例的写法.
上面的代码只介绍了 etcd 的最基本的 key-value 操作,其实 etcd 客户端还提供了很多的 API,这些都将在我后续的文章中分布式锁,服务注册发现,配置变更监听,分布式系统 Leader 选举的内容中为大家介绍.
版权声明: 本文为 InfoQ 作者【字母哥哥】的原创文章。
原文链接:【https://xie.infoq.cn/article/3329de088beb60f5803855895】。未经作者许可,禁止转载。