有了 AI,现在学习成本很低,有很多开源项目都很值得学习,先从 Etcd 始。
搭建 Etcd 测试环境
本机电脑有 Docker 环境就好办了,创建 ~/Portable/etcd-launch/docker-compose.yml,内容如下:
services:
etcd1:
image: quay.io/coreos/etcd:v3.7.2
container_name: etcd1
command:
- /usr/local/bin/etcd
- --name=etcd1
- --data-dir=/etcd-data
- --listen-client-urls=http://0.0.0.0:2379
- --advertise-client-urls=http://etcd1:2379
- --listen-peer-urls=http://0.0.0.0:2380
- --initial-advertise-peer-urls=http://etcd1:2380
- --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- --initial-cluster-state=new
- --initial-cluster-token=etcd-cluster-1
ports:
- "2379:2379"
volumes:
- etcd1-data:/etcd-data
networks:
- etcd-net
etcd2:
image: quay.io/coreos/etcd:v3.7.2
container_name: etcd2
command:
- /usr/local/bin/etcd
- --name=etcd2
- --data-dir=/etcd-data
- --listen-client-urls=http://0.0.0.0:2379
- --advertise-client-urls=http://etcd2:2379
- --listen-peer-urls=http://0.0.0.0:2380
- --initial-advertise-peer-urls=http://etcd2:2380
- --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- --initial-cluster-state=new
- --initial-cluster-token=etcd-cluster-1
ports:
- "22379:2379"
volumes:
- etcd2-data:/etcd-data
networks:
- etcd-net
etcd3:
image: quay.io/coreos/etcd:v3.7.2
container_name: etcd3
command:
- /usr/local/bin/etcd
- --name=etcd3
- --data-dir=/etcd-data
- --listen-client-urls=http://0.0.0.0:2379
- --advertise-client-urls=http://etcd3:2379
- --listen-peer-urls=http://0.0.0.0:2380
- --initial-advertise-peer-urls=http://etcd3:2380
- --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- --initial-cluster-state=new
- --initial-cluster-token=etcd-cluster-1
ports:
- "32379:2379"
volumes:
- etcd3-data:/etcd-data
networks:
- etcd-net
volumes:
etcd1-data:
etcd2-data:
etcd3-data:
networks:
etcd-net:
driver: bridge
启动
cd ~/Portable/etcd-launch
docker compose up -d
图像01
查看服务启动状态
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
etcd1 quay.io/coreos/etcd:v3.7.2 "/usr/local/bin/etcd…" etcd1 28 seconds ago Up 26 seconds 0.0.0.0:2379->2379/tcp, [::]:2379->2379/tcp, 2380/tcp
etcd2 quay.io/coreos/etcd:v3.7.2 "/usr/local/bin/etcd…" etcd2 28 seconds ago Up 26 seconds 2380/tcp, 0.0.0.0:22379->2379/tcp, [::]:22379->2379/tcp
etcd3 quay.io/coreos/etcd:v3.7.2 "/usr/local/bin/etcd…" etcd3 28 seconds ago Up 26 seconds 2380/tcp, 0.0.0.0:32379->2379/tcp, [::]:32379->2379/tcp
查看节点健康状态
$ docker exec etcd1 etcdctl \
--endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379 \
endpoint health
http://etcd2:2379 is healthy: successfully committed proposal: took = 42.461004ms
http://etcd3:2379 is healthy: successfully committed proposal: took = 42.715752ms
http://etcd1:2379 is healthy: successfully committed proposal: took = 44.439445ms
更详细的信息
$ docker exec etcd1 etcdctl \
--endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379 \
endpoint status --write-out=json
格式化后:
[
{
"Endpoint": "http://etcd1:2379",
"Status": {
"header": {
"cluster_id": 9572721052113347000,
"member_id": 11090874293180130000,
"revision": 1,
"raft_term": 2
},
"version": "3.7.2",
"dbSize": 20480,
"leader": 15904053625288548000,
"raftIndex": 8,
"raftTerm": 2,
"raftAppliedIndex": 8,
"dbSizeInUse": 16384,
"storageVersion": "3.7.0",
"dbSizeQuota": 2147483648,
"downgradeInfo": {}
}
},
{
"Endpoint": "http://etcd2:2379",
"Status": {
"header": {
"cluster_id": 9572721052113347000,
"member_id": 15904053625288548000,
"revision": 1,
"raft_term": 2
},
"version": "3.7.2",
"dbSize": 20480,
"leader": 15904053625288548000,
"raftIndex": 8,
"raftTerm": 2,
"raftAppliedIndex": 8,
"dbSizeInUse": 16384,
"storageVersion": "3.7.0",
"dbSizeQuota": 2147483648,
"downgradeInfo": {}
}
},
{
"Endpoint": "http://etcd3:2379",
"Status": {
"header": {
"cluster_id": 9572721052113347000,
"member_id": 9754871488702160000,
"revision": 1,
"raft_term": 2
},
"version": "3.7.2",
"dbSize": 20480,
"leader": 15904053625288548000,
"raftIndex": 8,
"raftTerm": 2,
"raftAppliedIndex": 8,
"dbSizeInUse": 16384,
"storageVersion": "3.7.0",
"dbSizeQuota": 2147483648,
"downgradeInfo": {}
}
}
]
目前大多都看不懂是干什么的,先不用管,总之 etcd 集群在本机运行良好,先执行几个命令看下。
键值的读写
在第一个节点写入一个键值对:
$ docker exec etcd1 etcdctl put /demo/name dongdong
OK
从另一个节点读取:
$ docker exec etcd2 etcdctl get /demo/name
/demo/name
dongdong
从 Node-2、Node-3 都能读到 Node-1 设置的键值对,在 etcd 中,节点 Node 有专属的称呼:member(etcd 集群里的一个 etcd 实例,本例集群中有 3 个 members)
这里有两个知识点:
- "/demo/name" 这样像是目录一样的 Key 写法是常见约定俗成和推荐风格,类似于 Redis Key(module:thing)使用冒号分隔;
- Put 命令既可以用于创建,也可用于更新;
$ docker exec etcd1 etcdctl put /demo/name dondone
OK
$ docker exec etcd2 etcdctl get /demo/name
/demo/name
dondone
查询数据统计信息
先写入几条测试数据
$ docker exec etcd1 etcdctl put /demo/key1 v1
$ docker exec etcd1 etcdctl put /demo/key2 v2
$ docker exec etcd1 etcdctl put /demo/key3 v3
$ docker exec etcd1 etcdctl put /demo/key4 v4
$ docker exec etcd1 etcdctl put /demo/key5 v5
$ docker exec etcd1 etcdctl put /demo/key6 v6
查询某前缀下有多少个 Key
$ docker exec etcd1 etcdctl \
get /demo \
--prefix \
--count-only \
--write-out=fields
输出如下,其中的 Count: 7 跟上文存进入的 Key 数量对的上。
"ClusterID" : 9572721052113345815
"MemberID" : 11090874293180130209
"Revision" : 9
"RaftTerm" : 2
"More" : false
"Count" : 7
看 etcd backend DB 大小
$ docker exec etcd1 etcdctl \
--endpoints=http://etcd1:2379 \
endpoint status -w json \
| jq '.[] | {
dbSize: .Status.dbSize,
dbSizeInUse: .Status.dbSizeInUse
}'
- dbSize: backend 数据库文件当前大小
- dbSizeInUse: 实际正在使用的数据页大小
监听指键值的变化
$ docker exec -it etcd1 etcdctl watch /demo --prefix
另启终端执行
$ docker exec etcd2 etcdctl put /demo/name dongdong
OK
$ docker exec etcd3 etcdctl put /demo/age 30
OK
$ docker exec etcd1 etcdctl put /demo/name xiaoming
OK
$ docker exec etcd2 etcdctl del /demo/age
1
可以看到监听的终端输出如下内容
PUT
/demo/name
dongdong
PUT
/demo/age
30
PUT
/demo/name
xiaoming
DELETE
/demo/age
两个知识点:
- 终端 watch 的是 /demo 前缀,不是某一个具体 key;
- 写请求可以发给任意 member,watch 仍然会看到整个集群最终产生的事件;
Revision 修订概念
获取键值的详细信息
$ docker exec etcd1 etcdctl get /demo/name -w json | jq
{
"header": {
"cluster_id": 9572721052113345815,
"member_id": 11090874293180130209,
"revision": 13,
"raft_term": 2
},
"kvs": [
{
"key": "L2RlbW8vbmFtZQ==",
"create_revision": 2,
"mod_revision": 12,
"version": 4,
"value": "eGlhb21pbmc="
}
],
"count": 1
}
重点看几个值:
header.revision:13: 当前 etcd 全局 Revision,etcd 不仅每个 key 有单独维护的版本,整个 KV 存储也有一个全局递增的 revision,发生一次写事务,revision 通常就会向前推进。当前响应所看到的 etcd 数据库状态位于全局版本 13;create_revision: 2: 表示这个 key 第一次被创建时,etcd 的全局 revision 是 2;mod_revision: 12: 这个 key 最近一次被修改时,全局 revision 是 12;version: 4: 当前 key 自创建以来,被写入了多少次;
执行一次
$ docker exec etcd1 etcdctl put /demo/name dongdong
返回
$ docker exec etcd1 etcdctl get /demo/name -w json | jq
{
"header": {
"cluster_id": 9572721052113345815,
"member_id": 11090874293180130209,
"revision": 14,
"raft_term": 2
},
"kvs": [
{
"key": "L2RlbW8vbmFtZQ==",
"create_revision": 2,
"mod_revision": 14,
"version": 5,
"value": "ZG9uZ2Rvbmc="
}
],
"count": 1
}
更新了 /demo/name 键的值,revision 按照预期增长 1,create_revision 因为未发生删除重建,所以保持不变,mod_revision 本次这个 Key 就是最后的修改,对应的就是全局的最新版本,和 revision 相等,version 也加一符合预期。
另外值得一提的是 etcd 的 key 和 value 本质上都是字节序列,可以存储二进制数据。etcdctl -w json 会把 key 和 value 用 Base64 编码,以便安全地放进 JSON 字符串里。
从历史版本开始监听
了解了 revision 概念后,可以通过命令从某个时间节点开始获取事件。
$ docker exec -it etcd1 etcdctl watch /demo --prefix --rev=13
DELETE
/demo/age
PUT
/demo/name
dongdong
$ docker exec -it etcd1 etcdctl watch /demo --prefix --rev=10
PUT
/demo/name
dongdong
PUT
/demo/age
30
PUT
/demo/name
xiaoming
DELETE
/demo/age
PUT
/demo/name
dongdong
可以看到 watch 可以获取历史事件(虽说用途不同,但从表象看,感觉跟 Redis AOF 和 Kafka offset 都很像呢 🤔)
为什么能看到过去的事件? etcd 是 MVCC 存储,每次写入都会产生新的全局 revision,旧版本不会立刻被覆盖,所以可以按 revision 读取历史,也支持 Watch 从某个历史 revision 开始。
历史保存在哪里? 历史版本保存在 etcd 的后端数据库里,底层主要是 bbolt 持久化存储。
历史能保存多久? 不是固定时间。历史会一直保留,直到被 compaction(压缩) 清理。 可以手动压缩,也可以配置自动压缩,例如按时间或 revision 数保留。
本篇 etcd 的学习暂到这里,了解了 etcd 的基础功能,接下来看需要看往哪个方向再探索。