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
|
测试方法同上即可