etcd 单节点和集群搭建
etcd
是使用Go
语言开发的一个开源、高可用的分布式key-value
存储系统,可以用于:
类似的项目或者说中间件还有zookeeper
和consul
,其中zookeeper
,在java
的技术栈中利用的最多,而在go
语言中更多的是使用etcd
或者consul
,这俩对比,etcd
的文档又比consul
更齐全。
etcd
通过raft
算法实现了强一致、高可用的服务存储目录,集群中每个节点都可以使用完整的存档,集群部署也实现了高可用。
应用场景
分布式配置中心
将分布式系统中的各种配置文件信息注册到 etcd 中,当服务启动的之后从 etcd 中的指定配置 key 下拉取配置信息,作为第一次配置文件同步。此后,在etcd节点上注册一个Watcher,实时监听 etcd 中对应的键值的变化,在监听到变更时,及时同步最新的配置信息到我们的业务系统中,以实现配置热发布和热加载
服务注册中心
服务发现要解决的也是分布式系统中最常见的问题,即在同一个分布式集群中的进程或服务,要如何才能找到对方并建立连接。本质上来说, 服务发现就是想要了解集群中是否有进程在监听 udp
或 tcp
端口,并且通过名字就可以查找和连接。 就像查字典一样。etcd
就能充当一个服务字典的角色,服务上线去往etcd
进行注册,etcd
与服务之间维持一个心跳,保证服务是否可用。
分布式锁
etcd 使用 raft 算法保证了数据的一致性,因此可以作为分布式锁使用
- 保持独占: 只有一个可以获得锁,
etcd
提供了一套实现分布式锁原子操作CAS(CompareAndSwap)
的API
.通过设置prevExist
值,可以保证多个节点同时去创建某个目录时,只有一个成功。而创建成功的那一个就可以认为是获得了锁。
- 控制时序: 对所有想要获得锁进行排序,这个顺序全局唯一,
etcd
提供了一套API
,用于自动创建有序键。
核心组件
http Server
用于处理用户发送的 API 请求以及与集群中其他 etcd 节点通信
Store
顾名思义,存储组件。用于支持 etcd 的各类功能性事务,主要包括数据索引、节点状态变更、监控和反馈、事件处理和执行
raft
raft 分布式一致性算法
WAL
Write Ahead Log 预写式日志,是 etcd 的数据存储方式,除了在内存中存放所有的数据之外,etcd 还会通过 WAL 进行持久化存储
搭建单节点 etcd
下面在博主的 mbp 上做演示:
去官方的github
下载最新版本的etcd
,找到最新的release
https://github.com/etcd-io/etcd/releases
直接解压后运行就好
1 2
| wu@WuuuudeMacBook-Pro ~/Projects/etcd-v3.5.17-darwin-arm64 ./etcd
|
不间断运行
1 2
| wu@WuuuudeMacBook-Pro ~/Projects/etcd-v3.5.17-darwin-arm64 nohup ./etcd &
|
测试
etcdcli是 etcd 官方提供的 etcd 命令行客户端,可在解压后的 etcd 目录下找见,并可以直接运行
1 2 3 4 5
| > ./etcdctl put name wuuu OK > ./etcdctl get name name wuuu
|
如果需要指定 etcd 端点(集群节点)的话可以加上–endpoints 参数
1 2 3
| > /etcdctl --endpoints=http://127.0.0.1:2379 get name name wuuu
|
搭建三节点 etcd 集群
如果处于测试或者开发的目的,我们可以使用单节点的 etcd,但是在生产环境中,为了确保 etcd 的高可用,应该使用 etcd 集群。与此同时,raft 算法在决策时需要半数以上的节点决策投票,因此一般在生产环境中都会部署奇数个节点的 etcd 集群。下面在博主的本地开发环境基于 docker 搭建三节点 etcd 集群
假设我们使用三个容器,分别命名为 etcd-node1
、etcd-node2
、etcd-node3
,以下是对应的 IP 地址(通过 Docker 网络实现):
容器名称 |
IP 地址 |
主机名 |
etcd-node1 |
172.20.0.2 |
node1 |
etcd-node2 |
172.20.0.3 |
node2 |
etcd-node3 |
172.20.0.4 |
node3 |
1.创建 docker 网络
为了保证容器之间可以彼此通信,先创建 etcd 集群的 docker 网络
1
| docker network create --subnet=172.20.0.0/16 etcd-network
|
2.创建并启动三个 etcd 节点,让其加入 docker 网络
第一个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| docker run -d \ --name etcd-node1 \ --net etcd-network \ --ip 172.20.0.2 \ --hostname node1 \ -p 23791:2379 -p 23801:2380 \ gcr.io/etcd-development/etcd:v3.5.9 \ etcd \ --name node1 \ --data-dir /etcd-data \ --listen-client-urls http://0.0.0.0:2379 \ --advertise-client-urls http://172.20.0.2:2379 \ --listen-peer-urls http://0.0.0.0:2380 \ --initial-advertise-peer-urls http://172.20.0.2:2380 \ --initial-cluster node1=http://172.20.0.2:2380,node2=http://172.20.0.3:2380,node3=http://172.20.0.4:2380 \ --initial-cluster-token etcd-cluster \ --initial-cluster-state new
|
第二个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| docker run -d \ --name etcd-node2 \ --net etcd-network \ --ip 172.20.0.3 \ --hostname node2 \ -p 23792:2379 -p 23802:2380 \ gcr.io/etcd-development/etcd:v3.5.9 \ etcd \ --name node2 \ --data-dir /etcd-data \ --listen-client-urls http://0.0.0.0:2379 \ --advertise-client-urls http://172.20.0.3:2379 \ --listen-peer-urls http://0.0.0.0:2380 \ --initial-advertise-peer-urls http://172.20.0.3:2380 \ --initial-cluster node1=http://172.20.0.2:2380,node2=http://172.20.0.3:2380,node3=http://172.20.0.4:2380 \ --initial-cluster-token etcd-cluster \ --initial-cluster-state new
|
第三个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| docker run -d \ --name etcd-node3 \ --net etcd-network \ --ip 172.20.0.4 \ --hostname node3 \ -p 23793:2379 -p 23803:2380 \ gcr.io/etcd-development/etcd:v3.5.9 \ etcd \ --name node3 \ --data-dir /etcd-data \ --listen-client-urls http://0.0.0.0:2379 \ --advertise-client-urls http://172.20.0.4:2379 \ --listen-peer-urls http://0.0.0.0:2380 \ --initial-advertise-peer-urls http://172.20.0.4:2380 \ --initial-cluster node1=http://172.20.0.2:2380,node2=http://172.20.0.3:2380,node3=http://172.20.0.4:2380 \ --initial-cluster-token etcd-cluster \ --initial-cluster-state new
|
这里,三个节点的主机名分别为 node1、node2、node3,映射到宿主机的端口分别为 23791、23792、23793
3. 验证集群状态
检查 etcd 容器是否运行
使用 etcdctl 验证集群
通过 etcdctl
检查集群成员和状态: member list用于列出同集群下的所有节点
1
| docker exec etcd-node1 etcdctl --endpoints=http://172.20.0.2:2379 member list
|
你将看到类似输出:
1 2 3
| b217c5c8b9eaba91, started, node1, http://172.20.0.2:2380, http://172.20.0.2:2379, false e6996497aa96d1fc, started, node3, http://172.20.0.4:2380, http://172.20.0.4:2379, false f01c391eb2944291, started, node2, http://172.20.0.3:2380, http://172.20.0.3:2379, false
|
测试键值存储
通过执行节点写入键值:
1
| docker exec etcd-node1 etcdctl --endpoints=http://172.20.0.2:2379 put foo bar
|
从其他节点读取键值:
1
| docker exec etcd-node2 etcdctl --endpoints=http://172.20.0.3:2379 get foo
|
或者我们可以直接使用etcd 安装包下的 etcdcli 可执行文件来发送命令,不过此时需要使用容器映射在宿主机上的端口
1
| > ./etcdctl --endpoints=http://172.20.0.2:2379 put foo bar
|
查看Leader节点
在 etcd 集群中,没有固定的“主节点”(如传统的主从数据库架构)。etcd 使用的是 Raft 共识算法,它通过选举机制动态选出一个 Leader(领导者),其余节点为 Follower(追随者)。Leader 负责处理所有写请求并将变更同步到 Follower。所有节点都可以处理读请求。
可以使用如下命令查看 etcd 集群中的 leader 节点:
1 2 3 4 5 6 7 8
| > ./etcdctl --endpoints=http://127.0.0.1:23791,127.0.0.1:23792,http://127.0.0.1:23793 endpoint status --write-out=table +------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS | +------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | http://127.0.0.1:23791 | ade526d28b1f92f7 | 3.5.9 | 20 kB | false | false | 2 | 9 | 9 | | | http://127.0.0.1:23792 | d282ac2ce600c1ce | 3.5.9 | 20 kB | false | false | 2 | 9 | 9 | | | http://127.0.0.1:23793 | bd388e7810915853 | 3.5.9 | 20 kB | true | false | 2 | 9 | 9 | | +------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|
IS LEADER 为 true 的节点即为集群中的 leader 节点
使用 docker-compose 方式
当然,也可以使用 docker-compose来编排这个 etcd 集群,在管理上更为便捷
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| version: '3.8' services: etcd1: image: gcr.io/etcd-development/etcd:v3.5.9 container_name: etcd1 networks: - etcd_network ports: - "23791:2379" - "23801:2380" environment: - ETCD_NAME=etcd1 - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd1:2380 - ETCD_ADVERTISE_CLIENT_URLS=http://etcd1:2379 - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 - ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster - ETCD_INITIAL_CLUSTER_STATE=new
etcd2: image: gcr.io/etcd-development/etcd:v3.5.9 container_name: etcd2 networks: - etcd_network ports: - "23792:2379" - "23802:2380" environment: - ETCD_NAME=etcd2 - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd2:2380 - ETCD_ADVERTISE_CLIENT_URLS=http://etcd2:2379 - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 - ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster - ETCD_INITIAL_CLUSTER_STATE=new
etcd3: image: gcr.io/etcd-development/etcd:v3.5.9 container_name: etcd3 networks: - etcd_network ports: - "23793:2379" - "23803:2380" environment: - ETCD_NAME=etcd3 - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd3:2380 - ETCD_ADVERTISE_CLIENT_URLS=http://etcd3:2379 - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 - ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster - ETCD_INITIAL_CLUSTER_STATE=new
networks: etcd_network: driver: bridge
|
测试方法同上即可