github.com/XiaoMi/Gaea@v1.2.5/models/etcdv3/README.md (about) 1 # Gaea 小米数据库中间件 Ectd V3 API 升级过程说明 2 3 > 因为 Etcd V3 API 使用 gRPC 作为沟通的协定,效能上会有所提升,有必要进行升级,但是 V2 API 和 V3 API 在功能上有所不同,整理成以下内容作为记录 4 5 ## 1 Etcd 测试环境设置 6 7 > 直接到 Etcd V3 https://github.com/etcd-io/etcd/releases 找到容器 etcd v3.5.1 的启动方式,以下为启动指令 8 9 ```bash 10 # 连结到 Etcd GitHub 11 $ firefox https://github.com/etcd-io/etcd/releases 12 13 # 删除之前容器相关档案 14 $ docker stop etcd-gcr-v3.5.1 15 $ docker rm etcd-gcr-v3.5.1 16 $ sudo rm -rf /tmp/etcd-data.tmp 17 18 # 执行以下指令去执行 etcd v3.5.1 容器 19 $ rm -rf /tmp/etcd-data.tmp && mkdir -p /tmp/etcd-data.tmp && \ 20 docker rmi gcr.io/etcd-development/etcd:v3.5.1 || true && \ 21 docker run \ 22 -p 2379:2379 \ 23 -p 2380:2380 \ 24 --mount type=bind,source=/tmp/etcd-data.tmp,destination=/etcd-data \ 25 --name etcd-gcr-v3.5.1 \ 26 gcr.io/etcd-development/etcd:v3.5.1 \ 27 /usr/local/bin/etcd \ 28 --name s1 \ 29 --data-dir /etcd-data \ 30 --listen-client-urls http://0.0.0.0:2379 \ 31 --advertise-client-urls http://0.0.0.0:2379 \ 32 --listen-peer-urls http://0.0.0.0:2380 \ 33 --initial-advertise-peer-urls http://0.0.0.0:2380 \ 34 --initial-cluster s1=http://0.0.0.0:2380 \ 35 --initial-cluster-token tkn \ 36 --initial-cluster-state new \ 37 --log-level info \ 38 --logger zap \ 39 --log-outputs stderr 40 41 # 执行以下指令进行初期测试 42 $ docker exec etcd-gcr-v3.5.1 /bin/sh -c "/usr/local/bin/etcd --version" 43 $ docker exec etcd-gcr-v3.5.1 /bin/sh -c "/usr/local/bin/etcdctl version" 44 $ docker exec etcd-gcr-v3.5.1 /bin/sh -c "/usr/local/bin/etcdctl endpoint health" 45 $ docker exec etcd-gcr-v3.5.1 /bin/sh -c "/usr/local/bin/etcdctl put foo bar" 46 $ docker exec etcd-gcr-v3.5.1 /bin/sh -c "/usr/local/bin/etcdctl get foo" 47 $ docker exec etcd-gcr-v3.5.1 /bin/sh -c "/usr/local/bin/etcdutl version" 48 ``` 49 50 这时发现,用 etcd v2 api 原始程式码会发生以下错误 51 52 ```bash 53 # response is invalid json. The endpoint is probably not valid etcd cluster endpoint. 54 ``` 55 56 ## 2 Etcd V3 GUI 介面 57 58 可以安装 Etcd 图形介面工具去观察 Etcd 的写入状况 59 60 ```bash 61 # 安装 Etcd 图形介面工具 62 $ snap install etcd-manager 63 ``` 64 65 只要填入正确的 IP 和 Address,就可以按下连线测试 66 67 <img src="./assets/image-20220113164918661.png" alt="image-20220113164918661" style="zoom:80%;" /> 68 69 ## 3 Etcd 的连线测试 70 71 Etcd V3 只要接通后,就会一直保持连线,所以不用另外在写 Ping 函数进行侦测 72 73 所以在 V3 API 版本进行以下修改 74 75 执行测试时,如果发生连线错误,会有以下讯息,单元测试还是会通过,但会给出警告 76 77 -> 目前找不到可实验的 Etcd 服务器 (红色字体显示) 78 79 <img src="./assets/image-20220113163203011.png" alt="image-20220113163203011" style="zoom:80%;" /> 80 81 ## 4 Etcd 的写入和租约测试 82 83 > 整个测试是针对 Etcd V3 进行测试,和之前 V2 版本有些不同,V3 的 API 是利用测式函数 Test_EtcdV3 进行测试功能是否正常 84 85 以下为 测式函数 Test_EtcdV3 的代码内容 86 87 <img src="./assets/image-20220117035130712.png" alt="image-20220117035130712" style="zoom:80%;" /> 88 89 以下的表会整理所有的测试过程 90 91 | 项目编号 | 测试项目 | 测试内容 | 92 | :------: | :----------- | :----------------------------------------------------------- | 93 | 1 | 新增测试 | 新增 key1 和 key2 | 94 | 2 | 删除测试 | 删除 key1 | 95 | 3 | 到时删除测试 | 新增 key3,但 key3 只存在 5 秒 | 96 | 4 | 追踪测试 | 监测 key 值的变化,1 秒后,先不设定 TTL,之后 key5 的 TTL 修正为 5 秒<br />因为 key5 被更新两次,所以监控函数 Watch 会传送更新讯息两次到通道内 | 97 | 5 | 租约测试 | 先建立 5 秒的租约,再利用此租约去建立 key6 和 key7<br />因为租约的关系,key6 和 key7 在 5 秒后会消失 | 98 | 6 | 复原测试环境 | 在复原测试环境时,要删除测试环境存在所有的 key 值 | 99 100 ### 测试项目1 新增测试 101 102 - 使用 Update 函数,先只新增 key1 和 key2 103 104 <img src="./assets/image-20220117040619272.png" alt="image-20220117040619272" style="zoom: 50%;" /> 105 106 ### 测试项目2 删除测试 107 108 - 使用 Delete 函数,删除 key1 只留 key2 109 110 <img src="./assets/image-20220117040820166.png" alt="image-20220117040820166" style="zoom:50%;" /> 111 112 ### 测试项目3 到时删除测试 113 114 - 使用 UpdateWithTTL 函数,让 key3 只存在 5 秒,5 秒后 key3 会被自动删除 115 116 (5 秒之前,key2 和 key3 是存在的) 117 118 <img src="./assets/image-20220117041532732.png" alt="image-20220117041532732" style="zoom:50%;" /> 119 120 (5 秒之后,key3 会被自动删除,只留 key2) 121 122 <img src="./assets/image-20220117040820166.png" alt="image-20220117040820166" style="zoom:50%;" /> 123 124 ### 测试项目4 追踪测试 125 126 1. 使用监控函数 Watch 去观察前缀为 key 的 key 值的新增,只要有一只新的 key 值新增,通道内就会触发讯息通知 127 2. 先用协程启动 监控函数 Watch,先暂停 1 秒,让 Watch 函数完整启动 128 3. 用更新函数 Update,先新增 key5,目前还没有 TTL 的设定,为永远保存 129 4. 监控函数 Watch 会把触发讯息传送到通道,为第 1 则讯息,因为通道可以取出讯息,所以测试可以正常继续执行 130 5. 用时间更新函数 UpdateWithTTL 更新 key5,TTL 设定为 1 秒,1 秒后 Key5 会自动删除 131 6. 因为 key5 再一次更新,监控函数 Watch 又会再把触发讯息传送到通道,为第 2 则讯息,通道可以取出讯息,测试又可以正常继续 132 133 (1 秒之前,key2 和 key5 是存在的) 134 135 <img src="./assets/image-20220117143235293.png" alt="image-20220117143235293" style="zoom:50%;" /> 136 137 (1 秒之后,key5 会被自动删除,只留 key2) 138 139 <img src="./assets/image-20220117040820166.png" alt="image-20220117040820166" style="zoom:50%;" /> 140 141 ### 测试项目5 租约测试 142 143 - 先用租约函数 Lease 产生 5 秒的租约 144 - 再利用租约更新函数 UpdateWithLease,把 此 5 秒租约 和 key6 、 key7 绑在一起 145 - key6 和 key7 因为和 5 秒租约绑定,所以 5 秒后,资料会自动删除 146 - 5 秒之前,可以使用列表函数 List,查询目前所存在的 key 值,这时会有 key2、key6 和 key7 会存在 147 - 可以使用读取函数 Read,去查询 key2、key6 和 key7 的资料内容 148 - 5 秒之后,再使用列表函数 List,查询目前所存在的 key 值,只剩 key2 存在 149 150 (5 秒之前,key2、key6 和 key7 是存在的) 151 152 <img src="./assets/image-20220117042408542.png" alt="image-20220117042408542" style="zoom:50%;" /> 153 154 (5 秒之后,key6 和 key7 会被自动删除,只留 key2) 155 156 <img src="./assets/image-20220117040820166.png" alt="image-20220117040820166" style="zoom:50%;" /> 157 158 ### 测试项目6 复原测试环境 159 160 - 使用删除函数 Delete,删除所有资料 161 162 (清除所有 key 值,所有的资料在 etcd 会不存在) 163 164 <img src="./assets/image-20220117043339429.png" alt="image-20220117043339429" style="zoom:50%;" /> 165 166 ## 5 Etcd V2 和 V3 API 的差异 167 168 V2 版 API 和 V3 版 API 的功能差异整理成下表 169 170 | 项目 | V2 API | V3 API | 171 | :--------------------------- | :----: | :----: | 172 | 有目录观念 | 有 | 没有 | 173 | 有租约观念 | 没有 | 有 | 174 | 建立目录函数 Mkdir | 有 | 没有 | 175 | 建立函数 Create | 有 | 没有 | 176 | 更新函数 Update | 有 | 有 | 177 | 时间更新函数 UpdateWithTTL | 有 | 有 | 178 | 租约产生函数 Lease | 没有 | 有 | 179 | 租约更新函数 UpdateWithLease | 没有 | 有 | 180 | 删除函数 Delete | 有 | 有 | 181 | 读取函数 Read | 有 | 有 | 182 | 列表函数 List | 有 | 有 | 183 | 监测函数 Watch | 有 | 有 | 184 | 共同路径函数 BasePrefix | 有 | 有 | 185 186 结论为 187 188 1. V2 版本 API 有目录的观念,但是 V3 版本没有 189 2. V3 版本 API 有租约观念,但是 V2 版本没有 190 3. 其余观念,V2 版本 和 V3 版本重叠 191 4. 个人认为,在制定接口时,要指定保留特别重要的函数,比如 Update、Delete、List、Read、UpdateWithTTL 和 Watch 函数,剩下由各版本 API 自由自行发展 192 193 ## 6 Etcd V2 API 错误回报 194 195 > 在制作 V3 API 时,同时也发现旧版 V2 API 疑似有死结的问题发生 196 197 在 V2 版本的程式码有以下内容,其中在 c.Lock() 和 defer c.Unlock() 这两行会造成死结 198 199 - 这会有一个现象,当时用监测函数 Watch 时,会把 EtcdClient 物件上锁 200 - EtcdClient 物件上锁之后,就无法再用更新函数 Update 进行更新或新增资料等操作 201 - 监测函数 Watch 也一直去等待资料的更新才会进行解锁 202 - 更新函数 Update 也要等待解锁后,才能进行更新操作 203 - 监测函数 Watch 和 更新函数 Update互相等待,造成死结 204 205 <img src="./assets/image-20220117112954091.png" alt="image-20220117112954091" style="zoom:80%;" /> 206 207 会建议改成以下版本,去避免死结问题发生 208 209 - 先移除 defer c.Unlock() 此行,目的是要避免死结 210 - 如果整个监控函数 Watch 都不上锁,会发生未能及时获得资料的问题 211 - 只能尽量遵守 上锁后在最佳时机解锁 的原则去防止严重的死结问题发生 212 213 ```go 214 func (c *EtcdClientV3) Watch(path string, ch chan string) error { 215 c.Lock() // 在这里上锁 216 // defer c.Unlock() // 移除此行,避免死结发生 217 if c.closed { 218 c.Unlock() // 上锁后记得解锁,去防止死结问题发生 219 panic(ErrClosedEtcdClient) 220 } 221 222 rch := c.kapi.Watch(context.Background(), path, clientv3.WithPrefix()) 223 224 c.Unlock() // 上锁后在适当时机解锁,去防止死结问题发生 225 // 在这里解锁是最好的,因为解锁后立刻可以进行监听 226 227 for wresp := range rch { 228 for _, ev := range wresp.Events { 229 ch <- string(ev.Kv.Key) 230 } 231 } 232 233 return nil 234 } 235 ``` 236 237 ## 7 总结 238 239 这次工作完成以下内容 240 241 - 比较后, Etcd V2 和 Etcd V3 有部份观念和机制不同,保留重复和重要的函数 242 - 这次把 Etcd V3 版本的 API 修改完成,同时也发现 Etcd V2 版本疑似潜藏的 Bug 找出 243 244 PR 准备发两次 245 246 1. 第一次,送上 Etcd V3 API 的代码和修改旧的 V2 版本的代码,标题为 Etcd API 更新 247 2. 第二次,数据库中间件接上 Etcd V3 API ,同时修改 Etcd 的接口,标题为整合数据库中间件和 Etcd V3 API 248