当前位置:   article > 正文

Etcd 介绍与使用(入门篇)_etcd使用

etcd使用

etcd 介绍


etcd 简介


etc (基于 Go 语言实现)在 Linux 系统中是配置文件目录名;etcd 就是配置服务;

etcd 诞生于 CoreOS 公司,最初用于解决集群管理系统中 os 升级时的分布式并发控制、配置文件的存储与分发等问题。基于此,etcd 设计为提供高可用、强一致性的小型** kv 数据存储**服务。项目当前隶属于 CNCF 基金会,被包括 AWS、Google、Microsoft、Alibaba 等大型互联网公司广泛使用;

etcd 是一个可靠的分布式 KV 存储,其底层使用 Raft 算法保证一致性,主要用于共享配置、服务发现、集群监控、leader 选举、分布式锁等场景;

1)共享配置:配置文件的存储与分发,将配置文件存储在 etcd 中,其它节点加载配置文件;如需修改配置文件,则修改后,其它节点只需重新加载即可;

2)服务发现:客户端请求经过 etcd 分发给各个服务端,此时如果增加一个服务端并连接到 etcd,由于客户端在一直监听着 etcd,所以客户端能够快速拉去新增服务端地址,并将客户端请求通过 etcd 下发到新增的服务端;

3)集群监控:客户端会通过 etcd 监控所有的 master、slave 节点,一旦有节点发生宕机,客户端能够及时发现;

4)leader 选举:假设一个 master 节点连接多个 slave 节点,如果 master 节点发生宕机,此时 etcd 会从 slave 节点中选取一个节点作为 master 节点;

5)分布式锁:常规情况下锁的持有者和释放者是进程中的线程,而在分布式情况下,锁的持有者和释放者可以是微服务或进程;

etcd 安装


1)安装 golang 环境;

2)下载并编译安装 etcd;

  1. // 下载源代码
  2. git clone https://gitee.com/k8s_s/etcd.git
  3. // 设置源代理
  4. go env -w GO111MODULE=on
  5. go env -w GOPROXY=https://goproxy.cn,direct
  6. // 进入 etcd 目录
  7. cd etcd
  8. // 切换最新分支
  9. git checkout release-3.5
  10. go mod vendor
  11. ./build
  12. 在 etcd/bin 目录生成对应的执行文件 etcd、etcdctl 和 etcdutl
  13. // 查看 etcd 版本
  14. ./etcdctl version

说明:可以到 Gitee - 基于 Git 的代码托管和研发协作平台 网站搜 etcd 下载最新的即可!

执行结果如下:

etcd 使用


etcd 的启动与使用

  1. cd etcd/bin
  2. // 启动 etcd
  3. nohup ./etcd > ./start.log 2>&1 &
  4. // 使用 v3 版本 api
  5. export ETCDCTL_API=3
  6. // ./etcdctl + etcd 命令即可
  7. ./etcdctl put key val

执行结果如下所示:

etcd v2 和 v3 比较


扩展:一般情况下一个请求需要建立一条连接,比较浪费资源,所以有了 http + json 通信模式(json 是一种协议),但 json 加解密非常慢;

  • 使用 gRPC + protobuf 取代 http + json 通信,提高通信效率;gRPC 只需要一条连接;http 是每个请求建立一条连接;protobuf(是一种二进制协议所以包体小)加解密比 json 加解密速度得到数量级的提升;包体也更小;
  • v3 使用 lease (租约)替换 key ttl 自动过期机制(lease 将过期日期一致的 key 绑定到实体(该实体被称为 lease),通过检测实体的过期时间达到批量检查 key 过期时间的效果,效率更高);
  • v3 支持事务和多版本并发控制(一致性非锁定读)的磁盘数据库;而 v2 是简单的 kv 内存数据库(可靠性低,一旦服务器宕机数据无法得到保存);
  • v3 是扁平的 kv 结构;v2 是类型文件系统的存储结构;

扩展:

1)文件系统的存储结构

  • /node
  • /node/node1
  • /node/node2
  • /node/node1/sub1
  • /node/node1/sub2

2)扁平的 kv 结构

  • node
  • node1
  • node2
  • node3
  • 使用 get node --prefix 命令获取对应文件

etcd 架构(体系结构)


etcd 体系结构如下所示:

  • boltdb 是一个单机的支持事务的 kv 存储,etcd 的事务是基于 boltdb 的事务实现的;boltdb 为每一个 key 都创建一个索引(B+树);该 B+ 树存储了 key 所对应的版本数据;
  • wal(write ahead log)预写式日志实现事务日志的标准方法;执行写操作前先写日志,跟 mysql 中 redo 类似,wal 实现的是顺序写,而若按照 B+ 树写,则涉及到多次 io 以及随机写;
  • snapshot 快照数据,用于其他节点同步主节点数据从而达到一致性地状态;类似 redis 中主从复制中 rdb 数据恢复;流程:1. leader 生成 snapshot;2. leader 向 follower 发送 snapshot;3. follower 接收并应用 snapshot;gRPC server ectd 集群间以及 client 与 etcd 节点间都是通过 gRPC 进行通讯;

etcd APIs


数据版本号机制


  • term:leader 任期,leader 切换时 term 加一;全局单调递增,64bits;
  • revision:etcd 键空间版本号,key 发生变更,则 revision 加一;全局单调递增,64bits;
  • kvs:
    • create_revision 创建数据时,对应的版本号;
    • mod_revision 数据修改时,对应的版本号;
    • version 当前的版本号;标识该 val 被修改了多少次;

示例分析:

  1. # ./etcdctl put key2 val2
  2. OK
  3. # ./etcdctl get key2
  4. key2
  5. val2
  6. # ./etcdctl get key2 -w json
  7. {"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":9,"raft_term":7},"kvs":[{"key":"a2V5Mg==","create_revision":9,"mod_revision":9,"version":1,"value":"dmFsMg=="}],"count":1}

参数说明:

  • cluster_id:集群 id;
  • member_id:当前 etcd 节点 id;
  • revision:整个 etcd 的版本 id,且只要 key 发生变更(增、删、改),则 revision 加一;全局单调递增,64bits;
  • raft_term:leader 任期,leader 切换时 term 加一;全局单调递增,64bits;
  • kvs:
    • create_revision 创建数据时,对应的版本号;
    • mod_revision 数据修改时,对应的版本号;
    • version 当前的版本号;标识该 val 被修改了多少次;

注:"key":"a2V5Mg==" 和 "value":"dmFsMg==" 是因为值被加密了,在 get 时会对其进行解密!

执行结果:

设置


设置即存储共享配置信息;

  1. NAME:
  2. put - Puts the given key into the store
  3. USAGE:
  4. etcdctl put [options] <key> <value> (<value> can also be given fromstdin) [flags]
  5. DESCRIPTION:
  6. Puts the given key into the store.
  7. When <value> begins with '-', <value> is interpreted as a flag.
  8. Insert '--' for workaround:
  9. $ put <key> -- <value>
  10. $ put -- <key> <value>
  11. If <value> isn't given as a command line argument and '--ignorevalue' is not specified,this command tries to read the value from standard input.
  12. If <lease> isn't given as a command line argument and '--ignorelease' is not specified,this command tries to read the value from standard input.
  13. For example,
  14. $ cat file | put <key>
  15. will store the content of the file to <key>.
  16. OPTIONS:
  17. -h, --help[=false] help for put
  18. --ignore-lease[=false] updates the key using its current lease
  19. --ignore-value[=false] updates the key using its current value
  20. --lease="0" lease ID (in hexadecimal) to attach to thekey
  21. --prev-kv[=false] return the previous key-value pair beforemodification

语法命令:

  1. put key val
  2. // 存储 key value 的同时返回上一次存储的 key value
  3. put key val --prev-kv

删除


删除 key vla;

  1. NAME:
  2. del - Removes the specified key or range of keys [key, range_end)
  3. USAGE:
  4. etcdctl del [options] <key> [range_end] [flags]
  5. OPTIONS:
  6. --from-key[=false] delete keys that are greater than or equal to the given key using byte compare
  7. -h, --help[=false] help for del
  8. --prefix[=false] delete keys with matching prefix
  9. --prev-kv[=false] return deleted key-value pairs

语法命令:

  1. del key
  2. // 删除成功,返回 1
  3. // 若 key 不存在,则返回 0

获取


获取 key vla;

  1. NAME:
  2. get - Gets the key or a range of keys
  3. USAGE:
  4. etcdctl get [options] <key> [range_end] [flags]
  5. OPTIONS:
  6. --consistency="l" Linearizable(l) or Serializable(s)
  7. --count-only[=false] Get only the count
  8. --from-key[=false] Get keys that are greater than or equal to the given key using byte compare
  9. -h, --help[=false] help for get
  10. --keys-only[=false] Get only the keys
  11. --limit=0 Maximum number of results
  12. --order="" Order of results; ASCEND or DESCEND(ASCEND by default)
  13. --prefix[=false] Get keys with matching prefix
  14. --print-value-only[=false] Only write values when using the "simple" output format
  15. --rev=0 Specify the kv revision
  16. --sort-by="" Sort target; CREATE, KEY, MODIFY, VALUE, or VERSION

语法命令:

  1. get key
  2. // 获取前缀匹配 key 的所有 key val
  3. get key --prefix
  4. // 获取字符串小于 key2 的所有 key val
  5. get key key2
  6. // 获取字符串大于等于 key2 的所有 key val
  7. get key2 --from-key
  8. // 只获取字符串等于 key2 的 key
  9. get key2 --keys-only
  10. // 获取前缀匹配 key 的所有 key
  11. get key --prefix --keys-only
  12. // 获取前缀匹配 key 的前两个 key
  13. get key --prefix --keys-only --limit=2
  14. // 先排序,再获取前缀匹配 key 的前两个 key
  15. get key --prefix --keys-only --limit=2 --sort-by=value

get “小于” 案例

  1. // 获取所有前缀和 key 匹配的 key val
  2. # ./etcdctl get key --prefix
  3. key
  4. val2023
  5. key1
  6. val1
  7. key2
  8. val2
  9. key20
  10. val20
  11. key2024
  12. val2024
  13. // 范围查询,获取 key2 之前(范围区间为左闭右开)的 key val
  14. # ./etcdctl get key key2
  15. key
  16. val2023
  17. key1
  18. val1

注:比较范围区间时是按字符串进行比较的,如:key、key1、key2、key20、key2024 中只有 key、key1 小于 key2;

执行结果:

get “大于等于” 案例

  1. # ./etcdctl get key --prefix
  2. key
  3. val2023
  4. key1
  5. val1
  6. key2
  7. val2
  8. key20
  9. val20
  10. key2024
  11. val2024
  12. # ./etcdctl get key2 --from-key
  13. key2
  14. val2
  15. key20
  16. val20
  17. key2024
  18. val2024

执行结果:

监听


用来实现监听和推送服务;

  1. NAME:
  2. watch - Watches events stream on keys or prefixes
  3. USAGE:
  4. etcdctl watch [options] [key or prefix] [range_end] [--] [execcommand arg1 arg2 ...] [flags]
  5. OPTIONS:
  6. -h, --help[=false] help for watch
  7. -i, --interactive[=false] Interactive mode
  8. --prefix[=false] Watch on a prefix if prefix is set
  9. --prev-kv[=false] get the previous key-value pair before the event happens
  10. --progress-notify[=false] get periodic watch progress notification from server
  11. --rev=0 Revision to start watching

语法命令:

  1. // 监听 key 的变动
  2. watch key
  3. 1) 启两个 session
  4. 2) 在 session A 中执行:WATCH key
  5. 3) 在 session B 中执行操作 key 的命令,如:PUT key val,DEL key 等,则同时会在 session A 中显示具体操作
  6. // 当前事件发生前先获取前一个 key val
  7. watch key --prev-kv
  8. // 监听多个 key 的变动
  9. watch key --prefix

说明:监听时也可以指定监听范围和版本等信息;

事务


用于分布式锁以及 leader 选举;保证多个操作的原子性;确保多个节点数据读写的一致性;

有关数据版本号信息请参考上述:数据版本号机制 部分;

  1. NAME:
  2. txn - Txn processes all the requests in one transaction
  3. USAGE:
  4. etcdctl txn [options] [flags]
  5. OPTIONS:
  6. -h, --help[=false] help for txn
  7. -i, --interactive[=false] Input transaction in interactive mode
  8. 事务
  9. 1. 比较
  10. 1. 比较运算符 > = < !=
  11. 2. create 获取key的create_revision
  12. 3. mod 获取key的mod_revision
  13. 4. value 获取key的value
  14. 5. version 获取key的修改次数
  15. 2. 比较成功,执行下述代码
  16. 1. 成功后可以操作多个 del put get
  17. 2. 这些操作保证原子性
  18. 3. 比较失败,执行下述代码
  19. 1. 成功后可以操作多个 del put get
  20. 2. 这些操作保证原子性

语法命令:

TXN if/ then/ else ops

mod 比较案例

  1. # ./etcdctl put key val1995
  2. OK
  3. # ./etcdctl get key -w json
  4. {"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":12,"raft_term":7},"kvs":[{"key":"a2V5","create_revision":2,"mod_revision":12,"version":5,"value":"dmFsMTk5NQ=="}],"count":1}
  5. # ./etcdctl put key val2024
  6. OK
  7. # ./etcdctl get key -w json
  8. {"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":13,"raft_term":7},"kvs":[{"key":"a2V5","create_revision":2,"mod_revision":13,"version":6,"value":"dmFsMjAyNA=="}],"count":1}
  9. # ./etcdctl txn -i
  10. compares:
  11. mod("key")="9"
  12. Error: malformed comparison: mod("key")="9"; got mod("key") ""
  13. # ./etcdctl txn -i
  14. compares:
  15. mod("key") = "12"
  16. success requests (get, put, del):
  17. get key
  18. failure requests (get, put, del):
  19. get key --rev=12
  20. FAILURE
  21. key
  22. val1995

从上述执行结果来看,代码走的是 比较失败 的逻辑;

注:mod("key") = "12" 等号前后要有空格,不然会报错!

执行结果:

create 比较案例

  1. # ./etcdctl txn -i
  2. compares:
  3. create("key") = "2"
  4. success requests (get, put, del):
  5. get key
  6. failure requests (get, put, del):
  7. del key
  8. SUCCESS
  9. key
  10. val2024

执行结果:

version 比较案例

  1. # ./etcdctl put key val2020
  2. OK
  3. # ./etcdctl get key -w json
  4. {"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":14,"raft_term":7},"kvs":[{"key":"a2V5","create_revision":2,"mod_revision":14,"version":7,"value":"dmFsMjAyMA=="}],"count":1}
  5. # ./etcdctl put key val2023
  6. OK
  7. # ./etcdctl get key -w json
  8. {"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":15,"raft_term":7},"kvs":[{"key":"a2V5","create_revision":2,"mod_revision":15,"version":8,"value":"dmFsMjAyMw=="}],"count":1}
  9. # ./etcdctl txn -i
  10. compares:
  11. version("key") = "7"
  12. success requests (get, put, del):
  13. get key
  14. failure requests (get, put, del):
  15. get key --rev=14
  16. FAILURE
  17. key
  18. val2020

执行结果:

租约


用于集群监控以及服务注册发现;

  1. etcdctl lease grant <ttl> [flags] 创建一个租约
  2. etcdctl lease keep-alive [options] <leaseID> [flags] 续约
  3. etcdctl lease list [flags] 枚举所有的租约
  4. etcdctl lease revoke <leaseID> [flags] 销毁租约
  5. etcdctl lease timetolive <leaseID> [options] [flags] 获取租约信息
  6. OPTIONS:
  7. --keys[=false] Get keys attached to this lease

语法命令:

  1. // 创建一个 100 秒的租约
  2. lease grant 100
  3. // 如果租约创建成功会显示如下输出
  4. lease 694d7b82c54a9309 granted with TTL(100s)
  5. // 将多个 key 绑定到租约
  6. put key1 vla1 --lease=694d7b82c54a9309
  7. put key2 vla2 --lease=694d7b82c54a9309
  8. put key3 vla3 --lease=694d7b82c54a9309
  9. // 获取具有匹配前缀的 key(包括:绑定租约的 key 和未绑定租约的 key)
  10. get key --prefix
  11. // 输出结果
  12. key1
  13. vla1
  14. key2
  15. vla2
  16. key3
  17. vla3
  18. // 销毁租约
  19. lease revoke 694d7b82c54a9309
  20. // 获取具有匹配前缀的 key(因为租约已被销毁,所以此时返回的只有未绑定租约的 key)
  21. get key --prefix
  22. // 获取租约信息(如果租约未过期,则输出结果会显示租约的剩余日期;如果租约已过期,则显示已过期)
  23. lease timetolive 694d7b82c54a9309
  24. // 输出结果(租约已过期)
  25. lease 694d7b82c54a9309 already expired
  26. // 续约(可以让租约剩余日期一直保持在设定时间;续约前提是当前租约未过期)
  27. lease keep-alive 694d7b82c54a9309


  1. USAGE:
  2. etcdctl lock <lockname> [exec-command arg1 arg2 ...] [flags]
  3. OPTIONS:
  4. -h, --help[=false] help for lock
  5. --ttl=10 timeout for session

Go 操作 etcd


驱动包安装


不能直接 go get go.etcd.io/etcd/clientv3(官方提供驱动包)不然会报错的;因为 gRPC 版本过新的缘故;

这里我们需要指定 gRPC 的版本信息;

  1. # 指定 gRPC 版本为 v1.26.0
  2. go mod edit --require=google.golang.org/grpc@v1.26.0
  3. # 下载安装 gRPC 驱动包
  4. go get -u -x google.golang.org/grpc@v1.26.0
  5. # 下载安装 etcd 驱动包
  6. go get go.etcd.io/etcd/clientv3

Go 操作 etcd 实例


启动 etcd

1)方式一

  1. nohup ./etcd > ./start.log 2>&1 &
  2. // 查看端口对外开放情况(etcd 默认端口为 2379)
  3. lsof -i:2379

执行结果:

从上述执行结果可知,使用方式一启动时,etcd 的端口号只能在本地连接。

2)方式二

  1. nohup ./etcd --listen-client-urls 'http://0.0.0.0:2379' --advertise-client-urls 'http://0.0.0.0:2379' > ./start.log 2>&1 &
  2. // 查看端口对外开放情况(etcd 默认端口为 2379)
  3. lsof -i:2379

执行结果:

从上述执行结果可知,使用方式一启动时,etcd 的端口号可以被外部连接。

注:使用方式二启动 etcd!


注:如果 etcd 所在机器是公司内部机器,需要把安全组对应端口号放开,即需要放开 2379!


put、get 使用

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/coreos/etcd/clientv3"
  7. )
  8. func main() {
  9. // 创建连接
  10. cli, err := clientv3.New(clientv3.Config{
  11. // Endpoints 是一个切片,可同时连接多个服务器
  12. Endpoints: []string{"120.92.144.250:2379"},
  13. DialTimeout: 5 * time.Second, // 连接超时时间
  14. })
  15. if err != nil {
  16. panic(err)
  17. }
  18. // 程序执行结束前释放连接资源
  19. defer cli.Close()
  20. // v3 通讯服务使用的是 gRPC,需设置超时控制(即如果 put 命令执行后,在超时时间内没有返回结果,则取消 put 命令的执行)
  21. ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  22. _, err = cli.Put(ctx, "key", "mark")
  23. cancel()
  24. if err != nil {
  25. panic(err)
  26. }
  27. // 获取 key
  28. ctx, cancel = context.WithTimeout(context.Background(), time.Second)
  29. /*
  30. 此处的 get 等同于在终端执行 ./etcdctl get key -w json
  31. 输出结果:
  32. {"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":17,"raft_term":7},
  33. "kvs":[{"key":"a2V5","create_revision":2,"mod_revision":15,"version":8,"value":"dmFsMjAyMw=="}],"count":1}
  34. */
  35. resp, err := cli.Get(ctx, "key")
  36. cancel()
  37. if err != nil {
  38. panic(err)
  39. }
  40. for _, ev := range resp.Kvs {
  41. fmt.Printf("%s:%s\n", ev.Key, ev.Value)
  42. }
  43. }

watch 使用

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/coreos/etcd/clientv3"
  6. )
  7. func main() {
  8. // 创建连接
  9. cli, err := clientv3.NewFromURL("120.92.144.250:2379")
  10. if err != nil {
  11. panic(err)
  12. }
  13. defer cli.Close()
  14. // watch key 的操作
  15. //watch := cli.Watch(context.Background(), "key")
  16. // watch 大于等于 key3 的操作,监听对象由第三个参数控制
  17. watch := cli.Watch(context.Background(), "key", clientv3.WithFromKey())
  18. for resp := range watch {
  19. for _, ev := range resp.Events {
  20. fmt.Printf("Type: %s Key: %s Value: %s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
  21. }
  22. }
  23. }

执行上述代码后会阻塞等待其它用户操作 etcd,如下所示:

1)在终端执行 etcd 操作

2)在 go 客户端查看监听情况

lease 使用

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/coreos/etcd/clientv3"
  6. )
  7. func main() {
  8. // 创建连接
  9. cli, err := clientv3.NewFromURL("120.92.144.250:2379")
  10. if err != nil {
  11. panic(err)
  12. }
  13. defer cli.Close()
  14. // 创建租约
  15. lease, err := cli.Grant(context.Background(), 5)
  16. if err != nil {
  17. panic(err)
  18. }
  19. fmt.Println("lease id", lease.ID)
  20. // 把 key-val 绑定到租约
  21. _, err = cli.Put(context.Background(), "key", "mark", clientv3.WithLease(lease.ID))
  22. if err != nil {
  23. panic(err)
  24. }
  25. // 续租:长期续租、短期续租
  26. // 长期续租:不停的续租
  27. if false {
  28. ch, err := cli.KeepAlive(context.Background(), lease.ID)
  29. if err != nil {
  30. panic(err)
  31. }
  32. for {
  33. recv := <-ch
  34. fmt.Println("time to live", recv.TTL)
  35. }
  36. }
  37. // 短期续租:只续租一次
  38. if true {
  39. res, err := cli.KeepAliveOnce(context.Background(), lease.ID)
  40. if err != nil {
  41. panic(err)
  42. }
  43. fmt.Println("time to live", res.TTL)
  44. }
  45. }

lock 使用

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/coreos/etcd/clientv3"
  6. "github.com/coreos/etcd/clientv3/concurrency"
  7. )
  8. func main() {
  9. // 创建连接
  10. cli, err := clientv3.New(clientv3.Config{
  11. Endpoints: []string{"127.0.0.1:2379"},
  12. })
  13. if err != nil {
  14. panic(err)
  15. }
  16. defer cli.Close()
  17. // 创建 session1
  18. s1, err := concurrency.NewSession(cli, concurrency.WithContext(context.Background()), concurrency.WithTTL(10))
  19. if err != nil {
  20. panic(err)
  21. }
  22. defer s1.Close()
  23. // 为 session1 创建锁
  24. m1 := concurrency.NewMutex(s1, "lock")
  25. // 创建 session2
  26. s2, err := concurrency.NewSession(cli, concurrency.WithContext(context.Background()))
  27. if err != nil {
  28. panic(err)
  29. }
  30. defer s2.Close()
  31. // 为 session2 创建锁
  32. m2 := concurrency.NewMutex(s2, "lock")
  33. // 对 session1 加锁
  34. if err := m1.Lock(context.Background()); err != nil {
  35. panic(err)
  36. }
  37. fmt.Println("s1 acquired lock")
  38. // 创建管道
  39. m2ch := make(chan struct{})
  40. // 开启协程,对 session2 加锁,但由于已经被 session1 锁住,所以 session2 的加锁操作,阻塞等待
  41. go func() {
  42. defer close(m2ch)
  43. if err := m2.Lock(context.Background()); err != nil {
  44. panic(err)
  45. }
  46. }()
  47. // session1 释放锁
  48. if err := m1.Unlock(context.Background()); err != nil {
  49. panic(err)
  50. }
  51. fmt.Println("s1 released lock")
  52. // 通知 session2 session1 已经释放锁,此时 session2 可执行加锁操作
  53. <-m2ch
  54. fmt.Println("s2 acquired lock")
  55. }

注:Go 项目在创建好之后,需要在终端执行:go mod init 项目名称,生成 go.mod 文件。

etcd 存储原理及读写机制


存储原理


etcd 为每个 key 创建一个索引;一个索引对应着一个 B+ 树;B+ 树 key 为 revision,B+ 树节点存储的值为 value;B+ 树存储着 key 的版本信息从而实现了 etcd 的 mvcc;etcd 不会任由版本信息膨胀,通过定期的 compaction 来清理历史数据;

etcd 为了加速索引数据,在内存中维持着一个 B 树;B 树 key 为 key-val 中的 key,value 为该 key 的 revision;示意图如下:

etcd 不同命令执行流程:

  • etcd get 命令执行流程:etcd 在执行 get 获取数据时,先从内存中的 B 树中寻找,如果找不到,再从 B+ 树中寻找,从 B+ 树中找到数据后,将其缓存到 B 树并输出到客户端;
  • etcd put 命令执行流程:etcd 在执行 put 插入或修改数据时,先从内存中的 B 树中寻找,如果找到了,则对其进行修改并将其写入到 B+ 树;

问题:mysql 的 mvcc 是通过什么实现的?

答:undolog;

问题:mysql B+ 树存储什么内容?

答:具体分为聚簇索引和二级索引;

问题:mysql 为了加快索引数据,采用什么数据结构?

答:MySQL 采用自适应 hash 来加速索引;

扩展:B-树和 B+ 树区别?

  • B-树和 B+ 树都是多路平衡搜索树;采用中序遍历的方式会得到一个有序的结构;都是通过 key 的方式来维持树的有序性;
  • B-树一个节点中 n 个元素对应着 n+1 个指针;而 B+ 树一个节点中 n 个元素对应着 n 个指针;
  • B-树每个节点都存储节点信息,B+ 树只有叶子节点存储节点信息,非叶子节点只存储索引信息;
  • B+ 树叶子节点之间通过双向链表连接,对于范围查询速度更快,这样减少了磁盘 io;

读写机制


etcd 是串行写(避免不必要的加锁),并发读;

并发读写时(读写同时进行),读操作是通过 B+ 树 mmap 访问磁盘数据;写操作走日志复制流程;可以得知如果此时读操作走 B 树出现脏读幻读问题;通过 B+ 树访问磁盘数据其实访问的事务开始前的数据,由 mysql 可重复读隔离级别下 MVCC 读取规则可智能避免脏读和幻读问题;

并发读时,可走内存 B 树;

注:由于 etcd 写的时候是先写到内存中的 B 树,然后再写到磁盘上的 B+ 树,因此并发读写时需要读 B+ 树数据,否则容易出现脏读幻读问题;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/568595
推荐阅读
相关标签
  

闽ICP备14008679号