8.3.1 注册服务中心Etcd
etcd是什么
etcd 是一个分布式键值对存储,设计用来可靠而快速的保存关键数据并提供访问。通过分布式锁, leader选举保障可靠的分布式协同。
etcd 特点
-
完全复制,集群中的每个节点均拥有全量数据
-
强一致性,etcd通过raft共识算法保证集群数据的一致性
-
数据结构简单,仅字符串格式
-
可存储的数据量较少
-
每个节点支持1000次/秒的写操作,2000次/秒的读操作
使用场景
-
服务注册与服务发现
-
分布式配置中心
-
分布式锁
-
分布式选主
-
读多写少的场景
架构图
-
Leader:Raft算法中通过竞选而产生的处理所有数据提交的节点
-
Follower:竞选失败的节点作为Raft中的从属节点,为算法提供强一致性保证
-
Candidate:当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始竞选
-
Learner:etcd集群中的只读节点,拥有全量数据,不参与Leader的竞选
-
BoltDB:一个支持事务的KV存储引擎,用于对ETCD数据的持久化,支持并发读事务,串行写事务
-
WAL 预写式日志:etcd中的数据提交都会先记录到日志,日志成功后才会做数据持久化
-
Snapshot,快照:防止WAL日志过多,用于存储某一时刻etcd的所有数据,可用于节点故障恢复
-
gRPC Server:节点与节点,节点与客户端通过gRPC 通信和信息同步,每个节点都是一个gRPC 服 务器
-
Raft:共识算法,etcd数据强一致性的保障
-
Client:etcd客户端,可以是命令行客户端 etcdctl ,也可以是各个语言版本的client库,可通过 grpc与http与server端通信
基本原理
MVCC(多版本并发控制)
-
作用
MVCC即在对数据修改时,不是去改变数据的值,而是新增一条数据记录,数据的版本号递增1。在读取 数据时实际上是读取到当前版本的数据记录。MVCC旨在提高数据库的并发性能,更好的解决读写之间的 冲突,从而实现读写冲突的情况下,不加锁并发读。
-
数据版本号:
revision:etcd键空间版本号,key发生变更,则revision加1;全局单调递增,64bits
create_revision: 创建数据时,对应的revision
mod_revision: 数据修改时,对应的revision
version: 表示当前key被修改的次数
分布式锁
-
锁的持有者为:前缀为 mymutex 且创建时间最早的key,即根据create version 正序第一个key值
-
etcd中任何数据变化都会导致全局的revision发生递增
-
客户端向etcd发送事务,事务内容:1.向etcd添加当前客户端的键值对,并获得结果;2.向etcd中 获取目前的锁持有者。
-
如果不存在锁持有者,则自身即为持有者;如果锁持有者的create version 等于当前自身的create version则自身即为持有者
-
如果自身不是持有者,则监听前缀为 mymutex 且 create version 比自身小一个版本号的key的删除事件,一旦删除,则自身获取到锁
数据写流程
-
只有leader节点能处理写流程,当follower节点收到写请求后会转发到leader
-
将请求提案广播到各个follower节点,并记录WAL,当大多数节点成功记录WAL之后,该请求提案 的状态将变成已提交状态。
-
由各个节点根据WAL应用数据,对数据进行持久化
-
响应写请求,返回处理结果
读流程
线性读:
-
etcd 默认读模式是线性读,在延时和吞吐量上相比串行读略差一点,适用于对数据一致性要求高的场景。
-
当收到一个线性读请求时,首先会从 Leader 获取集群最新的已提交的日志索引。
-
Leader 收到 ReadIndex 请求时,为防止脑裂等异常场景,会向 Follower 节点发送心跳确认,一半 以上节点确认 Leader 身份后才能将已提交的索引 (committed index) 返回给节点。
-
节点则会等待,直到状态机已应用索引 (applied index) 大于等于 Leader 的已提交索引时 (committed Index),然后去通知读请求。
串行读:
节点数据返回、无需通过 Raft 协议与集群进行交互。它具有低延时、高吞吐量的特点,适合对数据 一致性要求不高的场景
ETCD选项说明
-
etcd 集群运行时会同时监听2个端口,一个是客户端端口(如:2379)用于给客户端连接,一个是 集群成员之间(或伙伴,或 peer)通讯的端口(如:2380)。
-
etcd 集群配置的参数名称有一定规则,一般 --initial 前缀标记用于静态部署时启动集群,-- discovery 前缀标记在使用发现服务的方式启动集群
-
etcd 集群的安全从两个方面维护,一个是服务端集群成员(peer)之间通信使用 TLS 安全认证, 另一个是客户端(etcdctl)访问 etcd 集群使用 TLS 安全认证。
-
TLS 安全认证有一下几个文件组成
-
CA 证书:ca-file
-
私钥文件: key-file
-
证书文件: cert-file
-
证书吊销列表: crl-file
etcdctl命令行
注意事项
1、容器化部署后,需要通过docker exec 在容器中执行命令,使用容器内部的etcdctl命令行客户端
# 通过docker exec 在容器内部执行命令
docker exec etcd0 /usr/local/bin/etcdctl put key1 value1
2、etcdctl 默认访问当前节点2379端口,如果当前节点的etcd服务没有监听2379端口,那么需要指定- -endpoints,可以指定某一个节点,或者集群所有节点
# etcdctl客户端访问监听了默认端口的节点
docker exec etcd0 /usr/local/bin/etcdctl put key1 value1
# etcdctl客户端访问某一个节点
docker exec etcd1 /usr/local/bin/etcdctl put key1 value1 -- endpoints=192.168.239.149:22379
# etcdctl客户端访问集群,指定所有节点列表
docker exec etcd1 /usr/local/bin/etcdctl put key1 value1 --endpoints= [192.168.239.149:2379,192.168.239.149:12379,192.168.239.149:22379]
3、容器化部署后,需要与终端保持连接的命令需要启用docker 虚拟终端交互(指定 -it 选项),例 如:lock、elect、txn等
# 分布式锁,需要再多个终端执行
docker exec -it etcd0 /usr/local/bin/etcdctl lock mutex1 # 分布式选主,需要在多个终端推选不同的候选人
docker exec -it etcd0 /usr/local/bin/etcdctl elect myelect node1
docker exec -it etcd0 /usr/local/bin/etcdctl elect myelect node2
docker exec -it etcd0 /usr/local/bin/etcdctl elect myelect node3
全局参数
--cacert="" #验证启用TLS的安全服务器的证书
--cert="" # TLS证书文件 --command-timeout=5s # 运行命令超时时间
--debug[=false] # 启用客户端调试日志记录
--dial-timeout=2s # 客户端连接的拨号超时
-d, --discovery-srv="" # 用于查询描述群集端点的SRV记录的域名
--discovery-srv-name="" # 使用DNS发现时要查询的服务名称
--endpoints=[127.0.0.1:2379] # gRPC 端点
--hex[=false] # 将字节字符串打印为十六进制编码字符串
--insecure-discovery[=true] # 接受描述群集端点的不安全SRV记录
--insecure-skip-tls-verify[=false] # 跳过服务器证书验证(注意:此选项应仅用于测试目的)
--insecure-transport[=true] # 禁用客户端连接的传输安全
--keepalive-time=2s # 客户端连接的保持生存时间
--keepalive-timeout=6s # 客户端连接的保持活动超时
--key="" # TLS密钥文件
--password="" # 密码
--user="" # 用户名
-w, --write-out="simple" #设置输出格式 (fields, json, protobuf, simple, table)
数据操作
put/get/del(数据读写)
put #将给定的key写入到存储 --ignore-lease[=false] #使用当前租约更新key --ignore-value[=false] #使用当前值更新key --lease="0" # 要附加到key的租约ID(十六进制) --prev-kv[=false] # 返回修改前的上一个键值对
del #删除指定的键或键范围 --from-key[=false] # 删除大于等于给定key的所有key(按byte值比较) --prefix[=false] # 按前缀匹配删除 --prev-kv[=false] # 删除后是否返回被删除的键值对
get #获取给定key或给定范围的key --consistency="l" # 'l' 代表线性读(执行raft), 's' 代表串行化读 --count-only[=false] # 仅获取计数 --from-key[=false] #使用byte值比较获取大于或等于给定键的键,类似*key*,但不等于 --keys-only[=false] # 仅获取key --limit=0 #最大结果数 --order="" #结果排序,ASCEND 或 DESCEND (默认 ASCEND) --prefix[=false] # 获取具有匹配前缀的key,key* --print-value-only[=false] #用“simple”输出格式时仅打印值 --rev=0 #指定kv版本 --sort-by="" # 指定排序字段,CREATE, KEY, MODIFY, VALUE, or VERSION
示例:
docker exec etcd0 /usr/local/bin/etcdctl put key1 value1
docker exec etcd0 /usr/local/bin/etcdctl put key2 value2
docker exec etcd0 /usr/local/bin/etcdctl put key3 value2 # 根据指定key获取
docker exec etcd0 /usr/local/bin/etcdctl get key1
# 根据前缀获取 docker
exec etcd0 /usr/local/bin/etcdctl get key --prefix docker exec etcd0 /usr/local/bin/etcdctl put a valuea
docker exec etcd0 /usr/local/bin/etcdctl put b valueb
docker exec etcd0 /usr/local/bin/etcdctl put c valuec # 根据key值byte值比较,获取大于等于指定key值的所有key
docker exec etcd0 /usr/local/bin/etcdctl get b --from-key
lease(租约)
lease grant # 创建租约
lease keep-alive # 开启线程,自动续约 --once[=false] # 仅续约一次 lease list #列出所有活动的租约
lease revoke #撤销租约
lease timetolive # 获取租约信息 --keys[=false] #获取附加到此租约的key
示例:
# 创建租约,有效时长20s
docker exec etcd0 /usr/local/bin/etcdctl lease grant 20
# 获取租约列表
docker exec etcd0 /usr/local/bin/etcdctl lease list
# 根据leaseID 撤销租约
docker exec etcd0 /usr/local/bin/etcdctl lease revoke 694d869b526b302a
# 查询指定租约的有效时长信息
docker exec etcd0 /usr/local/bin/etcdctl lease timetolive 694d869b526b30c0
# 开启线程为指定租约自动续租
docker exec etcd0 /usr/local/bin/etcdctl lease keep-alive 694d869b526b30c2
lock(锁)
lock # 获取命名锁 --ttl=10 #session 超时时间
示例:
# 分布式锁,需要再多个终端执行
# 终端1
docker exec -it etcd0 /usr/local/bin/etcdctl lock mutex1
# 终端2
docker exec -it etcd0 /usr/local/bin/etcdctl lock mutex1
elect (选举)
选举逻辑与锁逻辑类似,前一个leader卸任之后,后续竞选者才能选出新的leader
elect #观察并参与领导人选举 -l, --listen[=false] # 观察模式
txn(事务)
txn # 在一个事务中处理所有请求 -i, --interactive[=false] # 交互模式下的输入事务
事务API由 if 语句、then语句、else语句组成。基本逻辑:在 If 语句中,可以添加一系列的条件表达式, 若条件表达式全部通过检查,则执行 Then 语句的 get/put/delete 等操作,否则执行 Else 的 get/put/delete 等操作,支持比较运算符 (>、<、=、!=)
if语句支持项:
-
key 的最近一次修改版本号 mod_revision,简称 mod,可以用于检查 key 最近一次被修改时的版 本号是否符合你的预期。例如:mod("key1") = "111902",检查key1最近一次修改版本号是否等于 111902
-
key 的创建版本号 create_revision,简称 create,可以用于检测 key 是否已存在。例如: create("key") = "0",来判断key1是否存在,不存在的话 create_revision 版本号就是0。
-
key 的修改次数 version;可以用于检查 key 的修改次数是否符合预期。例如:version("key1") > "3",来判断key1的修改次数是否大于3次
-
key 的值,可以用于检查 key 的 value 值是否符合预期。例如:value("key1") = "value1",来判断 key1 的值是否等于 value1
watch(事件监听)
watch #监听键值或前缀的事件流 -i, --interactive[=false] # 交互式模式 --prefix[=false] # 监听前缀 --prev-kv[=false] # 获取事件发生前的上一个键值对 --progress-notify[=false] # 从服务器获取定期监视进度通知 --rev=0 # 从指定版本开始监听
# 终端1-监听key2
docker exec etcd0 /usr/local/bin/etcdctl watch key2
# 终端2-对key2做操作
docker exec etcd0 /usr/local/bin/etcdctl put key2 value3
docker exec etcd0 /usr/local/bin/etcdctl put key2 value4
docker exec etcd0 /usr/local/bin/etcdctl del key2
参考连接:https://github.com/0voice