大家好,我是三十一
[0],本次分享分布式神器etcd
[1]的 5 种部署方式和 10 种使用姿势,阅读全文预计花费 15 分钟。
在学习 etcd 之前,我们先来聊一聊 etcd 名字的由来
[2]。etcd 中的 etc 取自 unix 系统的/etc
目录,再加上一个d
代表distributed system
就组成了 etcd。在 unix 系统中 /etc 目录用于存储系统的配置数据,单从名字看 etcd 可用于存储分布式系统的配置数据,有时候也把 etcd 简单理解为分布式 /etc 配置目录。
etcd 简介
etcd 是一个可靠的分布式 key-value 存储系统,主要用于配置共享
和服务注册和发现
,具有以下特性:
- 简单:基于 gRPC 定义了清晰、面向用户的 API。
- 安全:支持可选的客户端 TLS 证书自动认证特性。
- 快速:支持每秒 10000 次的写入。
- 可靠:基于 Raft 算法协议保证一致性。
etcd 使用 Go 语言开发,底层基于 Raft 共识算法管理高可用的复制日志。当前已经被许多公司用于关键生产项目,比如:Kubernetes、locksmith、vulcand、Doorman 等。
当然,也有其他组件可以提供配置共享和服务注册和发现的功能,比如最为广泛和大家最为熟知的 Zookeeper,也被很多 Java 系的知名开源项目认可和使用,比如:Hadoop、HBase、Kafka 等。
但 etcd 是唯一一个可以媲美甚至超越 Zookeeper 的组件。
相较之下,Zookeeper 有如下缺点[3]:
- 复杂:Zookeeper 基于 ZAB 协议,属于类 Paxos 协议,而 Paxos 算法素以复杂难懂闻名;Zookeeper 的使用也比较复杂,需要安装客户端,而官方目前只提供了 Java 和 C 两种语言接口。
- 发展慢:由于基金会庞大的结构以及松散的管理,导致项目发展缓慢。
而 etcd 作为后起之秀,其优点也很明显:
- 简单:使用 Go 语言编写部署简单;使用 gRPC 定义接口,支持跨语言、跨平台特性;使用了易于用户理解的 Raft 算法保证一致性,优于 Paxos 算法。
- 发展快:etcd 正处于高速迭代开发中。
- 性能优越:官方提供的基准测试数据中,etcd 集群可以支持每秒 10000 次的写入,性能优于 Zookeeper。
- 安全性:etcd 支持 TLS 访问,而 ZooKeeper 在权限控制方面做得略显粗糙。
环境搭建
一、本地安装包部署
「下载」:下载最新的安装包(当前最新:v3.5.4),下载地址:https://github.com/etcd-io/etcd/releases/
「安装」:在解压后的文件目录下etcd
、etcdctl
分别为安装包和客户端的编译后的执行文件,可使用三种方法进行运行配置。
- 方法一:解压目录下直接运行
- 方法二:把
etcd
、etcdctl
文件复制到GOBIN
目录下。 - 方法三:在环境变量里添加
etcd
、etcdctl
文件所在的目录。
注:运行过程中,可能会涉及权限问题,授权即可。
「验证」:
代码语言:javascript复制# 验证 etcd 版本
$ etcd --version
etcd Version: 3.5.4
Git SHA: 08407ff76
Go Version: go1.16.15
Go OS/Arch: darwin/amd64
# 验证 etcdctl 版本
$ etcdctl version
etcdctl version: 3.5.4
API version: 3.5
二、本地编译部署
「下载」:使用以下命令克隆代码
代码语言:javascript复制# 下载最新版
$ git clone https://github.com/etcd-io/etcd.git
# 指定版本下载
$ git clone -b v3.5.4 https://github.com/etcd-io/etcd.git
「编译安装」:
代码语言:javascript复制# 编译
$ cd etcd
$ make build
# 安装
$ export PATH="$PATH:`pwd`/bin"
「验证」:
代码语言:javascript复制# 验证 etcd 版本
$ etcd --version
# 验证 etcdctl 版本
$ etcdctl version
# 验证 etcdutl 版本
$ etcdutl version
三、本地集群部署
首先需要安装goreman
组件,它基于 Procfile 配置文件管理 etcd 应用进程。
$ go install github.com/mattn/goreman@latest
「启动集群」:源码目录下Procfile
脚本已经构建好了本地演示集群,直接运行启动即可
$ goreman start
「验证」
代码语言:javascript复制$ etcdctl member list
8211f1d0f64f3269, started, infra1, http://127.0.0.1:12380, http://127.0.0.1:2379, false
91bc3c398fb3c146, started, infra2, http://127.0.0.1:22380, http://127.0.0.1:22379, false
fd422379fda50e48, started, infra3, http://127.0.0.1:32380, http://127.0.0.1:32379, false
该脚本创建包含 3 个 etcd 成员节点的集群,每个集群成员都接收键值的读取和写入。也可以按照Procfile.learner
脚本指导,学习集群新增节点的操作。
四、Docker 单机部署
此处通过docker-compose
进行实验配置。
「镜像拉取」
代码语言:javascript复制$ docker pull bitnami/etcd:3.5.2
「编辑docker-compose.yml」
代码语言:javascript复制version: '3.5'
services:
etcd:
container_name: builder-etcd
image: bitnami/etcd:3.5.2
ports:
- 2379:2379
environment:
- ALLOW_NONE_AUTHENTICATION=yes
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1002
- ETCD_QUOTA_BACKEND_BYTES=4294967296
volumes:
- ${DOCKER_ROOT_DIR:-.}/volumes/etcd/data:/bitnami/etcd
networks:
default:
name: builder_dev
「启动服务」
代码语言:javascript复制$ docker-compose -f docker-compose.yml up
「验证」:验证集群节点的版本
代码语言:javascript复制$ docker exec -it builder-etcd /bin/bash -c "etcd --version"
etcd Version: 3.5.2
Git SHA: 99018a77b
Go Version: go1.16.3
Go OS/Arch: linux/amd64
五、Docker 集群部署
「编辑docker-compose.yml」
代码语言:javascript复制version: '3.5'
services:
etcd1:
container_name: builder-etcd1
image: bitnami/etcd:3.5.2
ports:
- 12379:2379
environment:
- ALLOW_NONE_AUTHENTICATION=yes
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1002
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_NAME=etcd1
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd1:2380
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd1:2379
- ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
- ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- ETCD_INITIAL_CLUSTER_STATE=new
volumes:
- ${DOCKER_ROOT_DIR:-.}/volumes/etcd/data1:/bitnami/etcd
etcd2:
container_name: builder-etcd2
image: bitnami/etcd:3.5.2
ports:
- 22379:2379
environment:
- ALLOW_NONE_AUTHENTICATION=yes
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1002
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_NAME=etcd2
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd2:2380
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd2:2379
- ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
- ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- ETCD_INITIAL_CLUSTER_STATE=new
volumes:
- ${DOCKER_ROOT_DIR:-.}/volumes/etcd/data2:/bitnami/etcd
etcd3:
container_name: builder-etcd3
image: bitnami/etcd:3.5.2
ports:
- 32379:2379
environment:
- ALLOW_NONE_AUTHENTICATION=yes
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1002
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_NAME=etcd3
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd3:2380
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd3:2379
- ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
- ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- ETCD_INITIAL_CLUSTER_STATE=new
volumes:
- ${DOCKER_ROOT_DIR:-.}/volumes/etcd/data3:/bitnami/etcd
networks:
default:
name: builder_dev
部署配置文件 docker-compose.yml 细节,详见etcd docker-compose yml
[4]
「启动服务」
代码语言:javascript复制$ docker-compose -f docker-compose.yml up
「验证」:验证集群节点的版本
代码语言:javascript复制$ docker exec -it builder-etcd1 /bin/bash -c "etcd --version"
$ docker exec -it builder-etcd2 /bin/bash -c "etcd --version"
$ docker exec -it builder-etcd3 /bin/bash -c "etcd --version"
# 输出
etcd Version: 3.5.2
Git SHA: 99018a77b
Go Version: go1.16.3
Go OS/Arch: linux/amd64
API 学习
此处,通过使用etcdctl
[5]进行 API 学习验证,etcdctl 是一个用于与 etcd 服务器交互的命令行工具。
「1.查看版本」
代码语言:javascript复制$ etcdctl version
etcdctl version: 3.6.0-alpha.0
API version: 3.6
「2.写入 key」
代码语言:javascript复制$ etcdctl put foo bar
OK
「3.读取 key」
代码语言:javascript复制$ etcdctl get foo
foo
bar
# 只是获取值
$ etcdctl get foo --print-value-only
bar
「4.批量取值」
代码语言:javascript复制$ etcdctl put foo1 bar1
$ etcdctl put foo3 bar2
$ etcdctl put foo3 bar3
# 获取从 foo 到 foo3 的值,不包括 foo3
$ etcdctl get foo foo3 --print-value-only
bar
bar1
bar2
# 获取前缀为 foo 的值
$ etcdctl get --prefix foo --print-value-only
bar
bar1
bar2
bar3
# 获取符合前缀的前两个值
$ etcdctl get --prefix --limit=2 foo --print-value-only
bar
bar1
「5.删除 key」
代码语言:javascript复制# 删除 foo 的值
$ etcdctl del foo
1
# 删除 foo 到 foo2 且不包括 foo2 的值
$ etcdctl del foo foo2
1
# 删除前缀为 foo 的所有值
$ etcdctl del --prefix foo
2
「6.监听」
代码语言:javascript复制# 监听 foo 单个 key
$ etcdctl watch foo
# 另一个控制台执行:etcdctl put foo bar
PUT
foo
bar
# 同时监视多个值
$ etcdctl watch -i
$ watch foo
$ watch zoo
# 另一个控制台执行: etcdctl put foo bar
PUT
foo
bar
# 另一个控制台执行: etcdctl put zoo val
PUT
zoo
val
# 监视 foo 前缀命中的 key
$ etcdctl watch --prefix foo
# 另一个控制台执行:etcdctl put foo1 bar1
PUT
foo1
bar1
# 另一个控制台执行:etcdctl put fooz1 barz1
PUT
fooz1
barz1
「7.设置租约」当一个 key 被绑定到一个租约上时,它的生命周期与租约的生命周期即绑定。
代码语言:javascript复制# 设置10秒后过期时间
$ etcdctl lease grant 10
lease 32698142c52a170a granted with TTL(10s)
# 把 foo 和租约绑定,设置成 10 秒后过期
$ etcdctl put --lease=32698142c52a170a foo bar
OK
$ etcdctl get foo
foo
bar
# 10 秒后,获取不到 foo
$ etcdctl get foo
# 返回空
「8.撤销租约」通过租约 ID 撤销租约,撤销租约将删除其所有绑定的 key。
代码语言:javascript复制$ etcdctl lease grant 10
lease 32698142c52a170c granted with TTL(10s)
$ etcdctl put --lease=32698142c52a170c foo bar
OK
# 撤销租约
$ etcdctl lease revoke 32698142c52a170c
lease 32698142c52a170c revoked
$ etcdctl get foo
# 返回空
「9.续约」通过刷新 TTL 值来保持租约的有效,使其不会过期。
代码语言:javascript复制# 设置 10 秒后过期租约
$ etcdctl lease grant 10
lease 32698142c52a170f granted with TTL(10s)
# 把 foo 和租约绑定,设置成 10 秒后过期
$ etcdctl put foo bar --lease=32698142c52a170f
# 自动定时执行续约,续约成功后每次租约为 10 秒
$ etcdctl lease keep-alive 32698142c52a170f
lease 32695410dcc0ca06 keepalived with TTL(10)
lease 32695410dcc0ca06 keepalived with TTL(10)
lease 32695410dcc0ca06 keepalived with TTL(10)
...
「10.查看租约」查看租约信息,以便续租或查看租约是否仍然存在或已过期
代码语言:javascript复制# 设置 50 秒 TTL
$ etcdctl lease grant 50
lease 32698142c52a1711 granted with TTL(50s)
# zoo1 绑定 32698142c52a1711 租约
$ etcdctl put --lease=32698142c52a1711 zoo1 val1
OK
# 查看租约,remaining(32s) 剩余有效时间32秒;--keys 获取租约绑定的 key
$ etcdctl lease timetolive --keys 32698142c52a1711
lease 32698142c52a1711 granted with TTL(50s), remaining(32s), attached keys([zoo1])
注:一个租约支持绑定多个 key
代码语言:javascript复制$ etcdctl lease grant 50
lease 32698142c52a1713 granted with TTL(50s)
$ etcdctl put --lease=32698142c52a1713 zoo1 val1
OK
$ etcdctl put --lease=32698142c52a1713 zoo2 val2
OK
$ etcdctl put --lease=32698142c52a1713 zoo3 val3
OK
租约过期后,所有 key 值都会被删除,因此:
- 当租约只绑定了一个 key 时,想删除这个 key,最好的办法是撤销它的租约,而不是直接删除这个 key。
- 当租约没有绑定key时,应主动把它撤销掉,单纯删除 key 后,续约操作持续进行,会造成内存泄露。
# 方法一:直接删除`key`
# 设置租约并绑定 zoo1
$ etcdctl lease grant 50
lease 32698142c52a1715 granted with TTL(50s)
$ etcdctl --lease=32698142c52a1715 put zoo1 val1
OK
# 续约
$ etcdctl lease keep-alive 32698142c52a1715
lease 32698142c52a1715 keepalived with TTL(50)
# 另一个控制台执行:etcdctl del zoo1
# 单纯删除 key 后,续约操作持续进行,会造成内存泄露
lease 32698142c52a1715 keepalived with TTL(50)
lease 32698142c52a1715 keepalived with TTL(50)
lease 32698142c52a1715 keepalived with TTL(50)
...
# 方法二:撤销`key`的租约
# 设置租约并绑定 zoo1
$ etcdctl lease grant 50
lease 32698142c52a1717 granted with TTL(50s)
$ etcdctl --lease=32698142c52a1717 put zoo1 val1
OK
# 续约
$ etcdctl lease keep-alive 32698142c52a1717
lease 32698142c52a1717 keepalived with TTL(50)
lease 32698142c52a1717 keepalived with TTL(50)
# 另一个控制台执行:etcdctl lease revoke 32698142c52a1717
# 续约撤销并退出
lease 32698142c52a1717 expired or revoked.
$ etcdctl get zoo1
# 返回空
以上,就是今天的全部内容,几乎包含了 etcd 安装和使用的各种姿势,欢迎各位号友敬请尝试。
References
- [0] 三十一: http://www.lee31.cn/assets/image/ThirtyOneLee.jpeg
- [1] etcd: https://github.com/etcd-io/etcd
- [2] etcd 名字的由来: https://etcd.io/docs/v3.5/faq/
- [3] https://www.infoq.cn/article/etcd-interpretation-application-scenario-implement-principle/
- [4] etcd docker-compose yml: https://github.com/liyaodev/docker-compose
- [5] etcdctl: https://etcd.io/docs/v3.5/dev-guide/interacting_v3/