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