邯郸房产网站,新手做网站服务器用什么,邮箱号怎么注册?,盱眙县住房和城乡建设局网站etcd 介绍 etcd 简介 etc #xff08;基于 Go 语言实现#xff09;在 Linux 系统中是配置文件目录名#xff1b;etcd 就是配置服务#xff1b;
etcd 诞生于 CoreOS 公司#xff0c;最初用于解决集群管理系统中 os 升级时的分布式并发控制、配置文件的存储与分发等问题。基…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 节点一旦有节点发生宕机客户端能够及时发现
4leader 选举假设一个 master 节点连接多个 slave 节点如果 master 节点发生宕机此时 etcd 会从 slave 节点中选取一个节点作为 master 节点
5分布式锁常规情况下锁的持有者和释放者是进程中的线程而在分布式情况下锁的持有者和释放者可以是微服务或进程
etcd 安装 1安装 golang 环境
2下载并编译安装 etcd
// 下载源代码
git clone https://gitee.com/k8s_s/etcd.git// 设置源代理
go env -w GO111MODULEon
go env -w GOPROXYhttps://goproxy.cn,direct// 进入 etcd 目录
cd etcd// 切换最新分支
git checkout release-3.5go mod vendor
./build在 etcd/bin 目录生成对应的执行文件 etcd、etcdctl 和 etcdutl// 查看 etcd 版本
./etcdctl version
说明可以到 Gitee - 基于 Git 的代码托管和研发协作平台 网站搜 etcd 下载最新的即可
执行结果如下 etcd 使用 etcd 的启动与使用
cd etcd/bin// 启动 etcd
nohup ./etcd ./start.log 21 // 使用 v3 版本 api
export ETCDCTL_API3// ./etcdctl etcd 命令即可
./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 结构 nodenode1node2node3使用 get node --prefix 命令获取对应文件 etcd 架构体系结构 etcd 体系结构如下所示 boltdb 是一个单机的支持事务的 kv 存储etcd 的事务是基于 boltdb 的事务实现的boltdb 为每一个 key 都创建一个索引B树该 B 树存储了 key 所对应的版本数据walwrite ahead log预写式日志实现事务日志的标准方法执行写操作前先写日志跟 mysql 中 redo 类似wal 实现的是顺序写而若按照 B 树写则涉及到多次 io 以及随机写snapshot 快照数据用于其他节点同步主节点数据从而达到一致性地状态类似 redis 中主从复制中 rdb 数据恢复流程1. leader 生成 snapshot2. leader 向 follower 发送 snapshot3. follower 接收并应用 snapshotgRPC server ectd 集群间以及 client 与 etcd 节点间都是通过 gRPC 进行通讯
etcd APIs 数据版本号机制 termleader 任期leader 切换时 term 加一全局单调递增64bitsrevisionetcd 键空间版本号key 发生变更则 revision 加一全局单调递增64bitskvs create_revision 创建数据时对应的版本号mod_revision 数据修改时对应的版本号version 当前的版本号标识该 val 被修改了多少次
示例分析
# ./etcdctl put key2 val2
OK
# ./etcdctl get key2
key2
val2
# ./etcdctl get key2 -w json
{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集群 idmember_id当前 etcd 节点 idrevision整个 etcd 的版本 id且只要 key 发生变更增、删、改则 revision 加一全局单调递增64bitsraft_termleader 任期leader 切换时 term 加一全局单调递增64bitskvs create_revision 创建数据时对应的版本号mod_revision 数据修改时对应的版本号version 当前的版本号标识该 val 被修改了多少次 注key:a2V5Mg 和 value:dmFsMg 是因为值被加密了在 get 时会对其进行解密
执行结果 设置 设置即存储共享配置信息
NAME:put - Puts the given key into the storeUSAGE:etcdctl put [options] key value (value can also be given fromstdin) [flags]DESCRIPTION:Puts the given key into the store.When value begins with -, value is interpreted as a flag.Insert -- for workaround:$ put key -- value$ put -- key valueIf value isnt given as a command line argument and --ignorevalue is not specified,this command tries to read the value from standard input.If lease isnt given as a command line argument and --ignorelease is not specified,this command tries to read the value from standard input.For example,$ cat file | put keywill store the content of the file to key.OPTIONS:-h, --help[false] help for put--ignore-lease[false] updates the key using its current lease--ignore-value[false] updates the key using its current value--lease0 lease ID (in hexadecimal) to attach to thekey--prev-kv[false] return the previous key-value pair beforemodification
语法命令
put key val// 存储 key value 的同时返回上一次存储的 key value
put key val --prev-kv
删除 删除 key vla
NAME:del - Removes the specified key or range of keys [key, range_end)USAGE:etcdctl del [options] key [range_end] [flags]OPTIONS:--from-key[false] delete keys that are greater than or equal to the given key using byte compare-h, --help[false] help for del--prefix[false] delete keys with matching prefix--prev-kv[false] return deleted key-value pairs
语法命令
del key// 删除成功返回 1
// 若 key 不存在则返回 0
获取 获取 key vla
NAME:get - Gets the key or a range of keysUSAGE:etcdctl get [options] key [range_end] [flags]OPTIONS:--consistencyl Linearizable(l) or Serializable(s)--count-only[false] Get only the count--from-key[false] Get keys that are greater than or equal to the given key using byte compare-h, --help[false] help for get--keys-only[false] Get only the keys--limit0 Maximum number of results--order Order of results; ASCEND or DESCEND(ASCEND by default)--prefix[false] Get keys with matching prefix--print-value-only[false] Only write values when using the simple output format--rev0 Specify the kv revision--sort-by Sort target; CREATE, KEY, MODIFY, VALUE, or VERSION
语法命令
get key// 获取前缀匹配 key 的所有 key val
get key --prefix // 获取字符串小于 key2 的所有 key val
get key key2 // 获取字符串大于等于 key2 的所有 key val
get key2 --from-key// 只获取字符串等于 key2 的 key
get key2 --keys-only// 获取前缀匹配 key 的所有 key
get key --prefix --keys-only// 获取前缀匹配 key 的前两个 key
get key --prefix --keys-only --limit2// 先排序再获取前缀匹配 key 的前两个 key
get key --prefix --keys-only --limit2 --sort-byvalue
get “小于” 案例
// 获取所有前缀和 key 匹配的 key val
# ./etcdctl get key --prefix
key
val2023
key1
val1
key2
val2
key20
val20
key2024
val2024// 范围查询获取 key2 之前(范围区间为左闭右开)的 key val
# ./etcdctl get key key2
key
val2023
key1
val1
注比较范围区间时是按字符串进行比较的如key、key1、key2、key20、key2024 中只有 key、key1 小于 key2
执行结果 get “大于等于” 案例
# ./etcdctl get key --prefix
key
val2023
key1
val1
key2
val2
key20
val20
key2024
val2024
# ./etcdctl get key2 --from-key
key2
val2
key20
val20
key2024
val2024
执行结果 监听 用来实现监听和推送服务
NAME:watch - Watches events stream on keys or prefixesUSAGE:etcdctl watch [options] [key or prefix] [range_end] [--] [execcommand arg1 arg2 ...] [flags]OPTIONS:-h, --help[false] help for watch -i, --interactive[false] Interactive mode--prefix[false] Watch on a prefix if prefix is set--prev-kv[false] get the previous key-value pair before the event happens --progress-notify[false] get periodic watch progress notification from server--rev0 Revision to start watching
语法命令
// 监听 key 的变动
watch key 1) 启两个 session
2) 在 session A 中执行WATCH key
3) 在 session B 中执行操作 key 的命令如PUT key valDEL key 等则同时会在 session A 中显示具体操作// 当前事件发生前先获取前一个 key val
watch key --prev-kv// 监听多个 key 的变动
watch key --prefix
说明监听时也可以指定监听范围和版本等信息
事务 用于分布式锁以及 leader 选举保证多个操作的原子性确保多个节点数据读写的一致性
有关数据版本号信息请参考上述数据版本号机制 部分
NAME:txn - Txn processes all the requests in one transactionUSAGE:etcdctl txn [options] [flags]OPTIONS:-h, --help[false] help for txn-i, --interactive[false] Input transaction in interactive mode事务
1. 比较1. 比较运算符 !2. create 获取key的create_revision3. mod 获取key的mod_revision4. value 获取key的value5. version 获取key的修改次数
2. 比较成功执行下述代码1. 成功后可以操作多个 del put get2. 这些操作保证原子性
3. 比较失败执行下述代码1. 成功后可以操作多个 del put get2. 这些操作保证原子性
语法命令
TXN if/ then/ else ops
mod 比较案例
# ./etcdctl put key val1995
OK
# ./etcdctl get key -w json
{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}# ./etcdctl put key val2024
OK
# ./etcdctl get key -w json
{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}
# ./etcdctl txn -i
compares:
mod(key)9
Error: malformed comparison: mod(key)9; got mod(key)
# ./etcdctl txn -i
compares:
mod(key) 12success requests (get, put, del):
get keyfailure requests (get, put, del):
get key --rev12FAILUREkey
val1995
从上述执行结果来看代码走的是 比较失败 的逻辑
注mod(key) 12 等号前后要有空格不然会报错
执行结果 create 比较案例
# ./etcdctl txn -i
compares:
create(key) 2success requests (get, put, del):
get keyfailure requests (get, put, del):
del keySUCCESSkey
val2024
执行结果 version 比较案例
# ./etcdctl put key val2020
OK
# ./etcdctl get key -w json
{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}
# ./etcdctl put key val2023
OK
# ./etcdctl get key -w json
{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}
# ./etcdctl txn -i
compares:
version(key) 7success requests (get, put, del):
get keyfailure requests (get, put, del):
get key --rev14FAILUREkey
val2020
执行结果 租约 用于集群监控以及服务注册发现
etcdctl lease grant ttl [flags] 创建一个租约
etcdctl lease keep-alive [options] leaseID [flags] 续约
etcdctl lease list [flags] 枚举所有的租约
etcdctl lease revoke leaseID [flags] 销毁租约
etcdctl lease timetolive leaseID [options] [flags] 获取租约信息OPTIONS:--keys[false] Get keys attached to this lease
语法命令
// 创建一个 100 秒的租约
lease grant 100// 如果租约创建成功会显示如下输出
lease 694d7b82c54a9309 granted with TTL(100s)// 将多个 key 绑定到租约
put key1 vla1 --lease694d7b82c54a9309
put key2 vla2 --lease694d7b82c54a9309
put key3 vla3 --lease694d7b82c54a9309// 获取具有匹配前缀的 key(包括绑定租约的 key 和未绑定租约的 key)
get key --prefix // 输出结果
key1
vla1
key2
vla2
key3
vla3 // 销毁租约
lease revoke 694d7b82c54a9309// 获取具有匹配前缀的 key(因为租约已被销毁所以此时返回的只有未绑定租约的 key)
get key --prefix // 获取租约信息(如果租约未过期则输出结果会显示租约的剩余日期如果租约已过期则显示已过期)
lease timetolive 694d7b82c54a9309// 输出结果(租约已过期)
lease 694d7b82c54a9309 already expired// 续约(可以让租约剩余日期一直保持在设定时间续约前提是当前租约未过期)
lease keep-alive 694d7b82c54a9309
锁 USAGE:etcdctl lock lockname [exec-command arg1 arg2 ...] [flags]OPTIONS:-h, --help[false] help for lock--ttl10 timeout for session Go 操作 etcd 驱动包安装 不能直接 go get go.etcd.io/etcd/clientv3官方提供驱动包不然会报错的因为 gRPC 版本过新的缘故
这里我们需要指定 gRPC 的版本信息
# 指定 gRPC 版本为 v1.26.0
go mod edit --requiregoogle.golang.org/grpcv1.26.0# 下载安装 gRPC 驱动包
go get -u -x google.golang.org/grpcv1.26.0# 下载安装 etcd 驱动包
go get go.etcd.io/etcd/clientv3
Go 操作 etcd 实例 启动 etcd
1方式一
nohup ./etcd ./start.log 21 // 查看端口对外开放情况(etcd 默认端口为 2379)
lsof -i:2379
执行结果 从上述执行结果可知使用方式一启动时etcd 的端口号只能在本地连接。
2方式二
nohup ./etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379 ./start.log 21 // 查看端口对外开放情况(etcd 默认端口为 2379)
lsof -i:2379
执行结果 从上述执行结果可知使用方式一启动时etcd 的端口号可以被外部连接。
注使用方式二启动 etcd 注如果 etcd 所在机器是公司内部机器需要把安全组对应端口号放开即需要放开 2379 put、get 使用
package mainimport (contextfmttimegithub.com/coreos/etcd/clientv3
)func main() {// 创建连接cli, err : clientv3.New(clientv3.Config{// Endpoints 是一个切片可同时连接多个服务器Endpoints: []string{120.92.144.250:2379},DialTimeout: 5 * time.Second, // 连接超时时间})if err ! nil {panic(err)}// 程序执行结束前释放连接资源defer cli.Close()// v3 通讯服务使用的是 gRPC需设置超时控制(即如果 put 命令执行后在超时时间内没有返回结果则取消 put 命令的执行)ctx, cancel : context.WithTimeout(context.Background(), time.Second)_, err cli.Put(ctx, key, mark)cancel()if err ! nil {panic(err)}// 获取 keyctx, cancel context.WithTimeout(context.Background(), time.Second)/*此处的 get 等同于在终端执行 ./etcdctl get key -w json输出结果{header:{cluster_id:14841639068965178418,member_id:10276657743932975437,revision:17,raft_term:7},kvs:[{key:a2V5,create_revision:2,mod_revision:15,version:8,value:dmFsMjAyMw}],count:1}*/resp, err : cli.Get(ctx, key)cancel()if err ! nil {panic(err)}for _, ev : range resp.Kvs {fmt.Printf(%s:%s\n, ev.Key, ev.Value)}
}
watch 使用
package mainimport (contextfmtgithub.com/coreos/etcd/clientv3
)func main() {// 创建连接cli, err : clientv3.NewFromURL(120.92.144.250:2379)if err ! nil {panic(err)}defer cli.Close()// watch key 的操作//watch : cli.Watch(context.Background(), key)// watch 大于等于 key3 的操作监听对象由第三个参数控制watch : cli.Watch(context.Background(), key, clientv3.WithFromKey())for resp : range watch {for _, ev : range resp.Events {fmt.Printf(Type: %s Key: %s Value: %s\n, ev.Type, ev.Kv.Key, ev.Kv.Value)}}
}
执行上述代码后会阻塞等待其它用户操作 etcd如下所示
1在终端执行 etcd 操作 2在 go 客户端查看监听情况 lease 使用
package mainimport (contextfmtgithub.com/coreos/etcd/clientv3
)func main() {// 创建连接cli, err : clientv3.NewFromURL(120.92.144.250:2379)if err ! nil {panic(err)}defer cli.Close()// 创建租约lease, err : cli.Grant(context.Background(), 5)if err ! nil {panic(err)}fmt.Println(lease id, lease.ID)// 把 key-val 绑定到租约_, err cli.Put(context.Background(), key, mark, clientv3.WithLease(lease.ID))if err ! nil {panic(err)}// 续租长期续租、短期续租// 长期续租不停的续租if false {ch, err : cli.KeepAlive(context.Background(), lease.ID)if err ! nil {panic(err)}for {recv : -chfmt.Println(time to live, recv.TTL)}}// 短期续租只续租一次if true {res, err : cli.KeepAliveOnce(context.Background(), lease.ID)if err ! nil {panic(err)}fmt.Println(time to live, res.TTL)}
}
lock 使用
package mainimport (contextfmtgithub.com/coreos/etcd/clientv3github.com/coreos/etcd/clientv3/concurrency
)func main() {// 创建连接cli, err : clientv3.New(clientv3.Config{Endpoints: []string{127.0.0.1:2379},})if err ! nil {panic(err)}defer cli.Close()// 创建 session1s1, err : concurrency.NewSession(cli, concurrency.WithContext(context.Background()), concurrency.WithTTL(10))if err ! nil {panic(err)}defer s1.Close()// 为 session1 创建锁m1 : concurrency.NewMutex(s1, lock)// 创建 session2s2, err : concurrency.NewSession(cli, concurrency.WithContext(context.Background()))if err ! nil {panic(err)}defer s2.Close()// 为 session2 创建锁m2 : concurrency.NewMutex(s2, lock)// 对 session1 加锁if err : m1.Lock(context.Background()); err ! nil {panic(err)}fmt.Println(s1 acquired lock)// 创建管道m2ch : make(chan struct{})// 开启协程对 session2 加锁但由于已经被 session1 锁住所以 session2 的加锁操作阻塞等待go func() {defer close(m2ch)if err : m2.Lock(context.Background()); err ! nil {panic(err)}}()// session1 释放锁if err : m1.Unlock(context.Background()); err ! nil {panic(err)}fmt.Println(s1 released lock)// 通知 session2 session1 已经释放锁此时 session2 可执行加锁操作-m2chfmt.Println(s2 acquired lock)
}
注Go 项目在创建好之后需要在终端执行go mod init 项目名称生成 go.mod 文件。
etcd 存储原理及读写机制 存储原理 etcd 为每个 key 创建一个索引一个索引对应着一个 B 树B 树 key 为 revisionB 树节点存储的值为 valueB 树存储着 key 的版本信息从而实现了 etcd 的 mvccetcd 不会任由版本信息膨胀通过定期的 compaction 来清理历史数据
etcd 为了加速索引数据在内存中维持着一个 B 树B 树 key 为 key-val 中的 keyvalue 为该 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 个元素对应着 n1 个指针而 B 树一个节点中 n 个元素对应着 n 个指针B-树每个节点都存储节点信息B 树只有叶子节点存储节点信息非叶子节点只存储索引信息B 树叶子节点之间通过双向链表连接对于范围查询速度更快这样减少了磁盘 io 读写机制 etcd 是串行写避免不必要的加锁并发读
并发读写时读写同时进行读操作是通过 B 树 mmap 访问磁盘数据写操作走日志复制流程可以得知如果此时读操作走 B 树出现脏读幻读问题通过 B 树访问磁盘数据其实访问的事务开始前的数据由 mysql 可重复读隔离级别下 MVCC 读取规则可智能避免脏读和幻读问题
并发读时可走内存 B 树
注由于 etcd 写的时候是先写到内存中的 B 树然后再写到磁盘上的 B 树因此并发读写时需要读 B 树数据否则容易出现脏读幻读问题