base
- 分布式架构系统面临一些与生俱来的问题
- 比如部署复杂、响应时间慢、运维复杂
- 最根本的是多个节点之间的数据共享问题
- 要解决这个问题有两种
- 自己实现一个可靠的共享存储
- 依赖一个可靠的共享存储服务
- etcd是一款分布式存储中间件,使用Go语言编写。并通过Raft一致性算法处理和确保分布式一致性,解决了分布式系统中数据一致性的问题
- etcd 常用语微服务架构中的服务注册与发现中心,相较于ZooKeeper部署更简单,而且具有数据持久化、支持SSL客户端安全认证的独特优势
- etcd中涉及了数据一致性、多版本并发控制、watch监控、磁盘IO读写等知识点,深入学习etcd可以帮助我们从开源项目中学习底层原理,进一步提高分布式架构设计的能力
- etcd作为云原生架构中重要的基础组件,各个微服务之间通过etcd保证调用的可用性和正确性,其他许多知名项目(包括Kubernetes、CoreDNS和TiKV等),也都依赖etcd来实现可靠的分布式数据存储
- etcd在提供分布式键-值存储方面发挥着关键作用,其存储功能不仅具有很高的可用性,而且能够满足大规模Kubernetes集群所提出的强一致性要求。
- etcd作为一个可信赖的分布式键值存储服务,它能够为整个分布式集群存储一些关键数据协助分布式集群的正常运转
- 目前 etcd 可以存储百万到千万级别的 key。
- 单实例(V3)支持每秒10KQps
- etcd可以扮演两大角色:
- 持久化的键值存储系统
- 分布式系统数据一致性服务提供者
why
- 从本质上来讲,云原生中的微服务应用属于分布式系统的一种落地实践
- 最常见的、最大的难点就是数据存储不一致的问题,多个服务实例自身的数据或者获取到的数据各不相同
- CAP原理 只能3选2,并且P是必须要满足的
- Consistency(一致性)、Availability(可用性) 和 Partition tolerance(分区容错性)
- 数据存在的节点越多,分区容忍性越高,但要复制更新的数据就越多,一致性就越难保证。
- 为了保证一致性,更新所有节点数据所需要的时间就越长,可用性就会降低。
- 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值,即写操作之后的读操作,必须返回该值。(分为弱一致性、强一致性和最终一致性)
- 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
- 分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
- 分区容错P:
- 分区容错就是各分区断开也提供服务(网络原因)(所以一定要进行复制日志)?
- 分区不容错,就是在产生分区的时候,各个分区都不能对外提供服务。像这个例子中,ClientA通过ServerB访问分布式系统,ServerB发现访问不到ServerC和ServerD了,就认为系统发生了故障,这个时候就给ClientA返回一个错误,告诉它“我现在找不到C、D了,我不能为你提供服务”,同样的ClientB也会在访问这个分布式系统时得到相同的结果。
- 具有分区容错性的话,在产生分区的时候,各个分区将各自对外提供服务。像这个例子中,ClientA通过ServerB访问这个分布式系统,ServerB同样发现ServerC和ServerD访问不到了,但它将不再在意ServerC和ServerD的意见了,它只需要问问ServerA的意见,然后就成功给ClientA返回结果。同样的ClientB在通过ServerD访问这个分布式系统也能成功得到结果,这个结果只征询了ServerC和ServerD的意见,不会在意ServerA和ServerB的意见。
- 以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
- 取舍
- CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。
- CP without A:如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。
- Consistency(一致性)、Availability(可用性) 和 Partition tolerance(分区容错性)
what
-
ETCD : etc是放linux全局环境配置文件,d就是服务的意思,etcd就是全局配置服务中心。 : A highly-available key value store for shared configuration and service discovery. 即一个用于配置共享和服务发现的键值存储系统。
-
etcd归根结底是一个存储组件,且可以实现配置共享和服务发现
-
一款实现了元数据信息可靠存储的组件-etcd可集中管理配置信息
-
完成CAP理论中的CP指标
-
术语
术语 描述 备注 Raft Raft算法,etcd实现一致性的核心 etcd有etcd-raft模块 Follower Raft中的从属节点 竞争Leader失败 Leader Raft中的领导协调节点,用于处理数据提交 Leader节点协调整个集群 Candidate 候选节点 当Follower接收Leader节点的消息超时会转变为Candidate Node Raft状态机的实例 Raft中涉及多个节点 Member etcd实例,管理若对应的Node节点 可处理客户端请求 Peer 同一个集群中的另一个Member 其他成员 Cluster etcd集群 拥有多个etcd Member Lease 租期 key设置的租期,过期删除 Watch 监测机制 监控键值对的变化 Term 任期 某个节点成为Leader,到下一次竞选的时间 WAL 预写式日志 用于持久化存储的日志格式 Client 客户端 向etcd发起请求的客户 -
简单、存储、Watch机制、安全通信、高性能、一致可靠
-
提供服务注册与发现、键值对存储、消息发布与订阅、分布式锁,都是在基于基础组件实现。
-
核心架构
- etcd server:对外接收和处理客户端的请求
- gRPC server:etcd与其他etcd节点之间的通信和信息同步。
- MVCC:多版本控制,etcd的存储模块,键值对的每一次操作行为都会被记录存储,这些数据底层存储在BoltDB数据库中。
- WAL:预写式日志- etcd中数据提交都会被记录到日志。
- Snapshot:快照-以防WAL日志过多,用于存储某一时刻etcd的所有数据
- WAL+SNAPSHOT : 可以有效的进行数据存储和节点故障恢复操作
how
单机安装
-
官网下载安装包https://github.com/etcd-io/etcd/tags
-
选择最新版下载,点进去 找到对应的linux版本进行下载
-
解压并查看是否安装成功
tar -zxvf /data/etcd/etcd-v3.5.15-linux-amd64.tar.gz --transform s/etcd-v3.5.15-linux-amd64/etcd-v3.5.15/ #查看安装是否成功的相关命令 /data/etcd/etcd-v3.5.15/etcd --version /data/etcd/etcd-v3.5.15/etcdctl version /data/etcd/etcd-v3.5.15/etcdutl version
-
添加环境变量
vim /etc/profile ------------------------------------ export ETCD_HOME=/data/etcd/etcd-v3.5.15 export PATH=.:$ETCD_HOME:$PATH ————————————————---------------- source /etc/profile
-
后台启动etcd
-
chmod 777 /data/etcd/etcd-v3.5.15/etcd /data/etcd/etcd-v3.5.15/etcd &
-
测试etcd写入
etcdctl --endpoints=localhost:2379 put foo bar etcdctl --endpoints=localhost:2379 get foo
-
-
或者直接使用github提供的命令 – Linux # 能访问外网
-
# 创建安装脚本 vim etcd 写入如下内容 ETCD_VER=v3.5.15 # choose either URL GOOGLE_URL=https://storage.googleapis.com/etcd GITHUB_URL=https://github.com/etcd-io/etcd/releases/download DOWNLOAD_URL=${GOOGLE_URL} rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1 rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz /tmp/etcd-download-test/etcd --version /tmp/etcd-download-test/etcdctl version /tmp/etcd-download-test/etcdutl version
-
启动本地etcd
# start a local etcd server /tmp/etcd-download-test/etcd # write,read to etcd /tmp/etcd-download-test/etcdctl --endpoints=localhost:2379 put foo bar /tmp/etcd-download-test/etcdctl --endpoints=localhost:2379 get foo
-
-
查看启动状态 :etcdctl member list
-
卸载只需要删除对应文件和目录
集群安装(未实践)
在生产环境中,为了整个集群的高可用,Etcd正常都会集群部署,避免单点故障,集群启动有三种方式,分别是静态启动、动态发现、DNS发现。
- 静态启动:
Etcd
集群要求提前知道集群中所有成员信息,在启动时通过--initial-cluster
参数直接指定好Etcd的各个节点地址。 - Etcd动态发现:静态发现前提是在搭建集群之前已经提前知道各节点成员的信息,而实际应用中可能存在预先并不知道各节点ip的情况,这时可
通过已搭建的Etcd集群来辅助搭建新的Etcd集群
。 - DNS发现:通过DNS查询方式获取其他节点地址信息。
集群个数通常部署为奇数个数,因为:
- 偶数节点数选主时有较大概率等额选票,从而触发下来一轮选举
- 偶数个节点集群在某些网络分割的场景下无法正常工作。当网络分割发生后,将集群节点对半分割开。
此时集群将无法工作。按照RAFT协议,此时集群写操作无法使得大多数节点同意,从而导致写失败,集群无法正常工作。
客户端操作
-
Etcd在微服务和
Kubernates
集群中不仅可以作为服务注册
与发现
,还可以作为 key-value 存储的中间件。 -
etcdctl是一个命令行的客户端,它能提供一些简洁的命令,供用户直接跟etcd服务打交道,而无需基于 HTTP API 方式。方便对服务进行测试或者手动修改数据库内容。
-
我们刚开始可以通过
etdctl
来熟悉相关操作。这些操作跟 HTTP API 基本上是对应的。etcdctl在两个不同的 etcd 版本下的行为方式也完全不同。export ETCDCTL_API=2 export ETCDCTL_API=3 # 主要以API 3为主
-
ETCD v2 VS v3 ---- 优化
-
使用
gRPC
+protobuf
取代 HTTP+JSON 通信,提高通信效率;另外通过gRPC gateway 来继续保持对 HTTPJSON 接口的支持。 -
使用更轻 级的基于
租约(lease)
的 key 自动过期机制,取代了基于TTL key 的自动过期机制 -
观察者(watcher)
机制也进行了重新设计。etcd v2 的观察者机制是基于HTTP 长连接的事件驱动机制;而 etcd v3 的观察者机制是基于HTTP/2的server push ,并且对事件进行了多路复用( multiplexing )优化。 -
etcd v3 的数据模型也发生了较大的改变,
v2
是一个简单的 key-value 的内存数据库
,而v3
则是支持事务和多版本并发控制的磁盘数据库
。
-
-
在命令层面,可分为非数据库操作和数据库操作命令
非数据库操作
etcd --version # 查看etcd版本命令
etcdctl h # 查看常用命令
[常用options]
--debug 输出CURL命令,显示执行命令的时候发起的请求 例:etcdctl get foo --debug
--no-sync 发出请求之前不同步集群信息
--output, -o 'simple' 输出内容的格式(simple 为原始信息,json 为进行json格式解码,易读性好一些)
--peers, -C 指定集群中的同伴信息,用逗号隔开(默认为: "127.0.0.1:4001")
--cert-file HTTPS下客户端使用的SSL证书文件
--key-file HTTPS下客户端使用的SSL密钥文件
--ca-file 服务端使用HTTPS时,使用CA文件进行验证
--help, -h 显示帮助命令信息
--version, -v 打印版本信息
-w=json 读取版本号
数据库操作
-
etcd的数据库操作主要是围绕键值对和目录的CRUD。
-
etcd在键的组织上采用的是层次化结构(类似于文件系统),用户指定的键可以为单独的名字,实际上放在/根目录下面,如果指定键为路径名 /data/soft/key, 那么etcd将创建相应路径。
-
读写操作
# 写操作 etcdctl put /testdir/testkey "Hello world" etcdctl put /testdir/testkey2 "Hello world" etcdctl put /testdir/testkey3 "Hello world" etcdctl put /testdir/testkey "Hello world-fix" #读操作 etcdctl get /testdir/testkey #读取 etcdctl put /testdir/testkey --hex #以16进制进行读取 etcdctl get /testdir/testkey /testdir/testkey3 #读取指定范围内的值 注:半开区间,前必后开,返回2 etcdctl get --prefix /testdir/testkey #以前缀的方式查询读取值,返回3 etcdctl get --prefix --limit=2 /testdir/testkey #以前缀的方式查询返回,并限制limit=2,返回2 #读取版本旧值 --当前版本信息在put的响应标头里 etcdctl get /testdir/testkey #读取最新值 etcdctl get --rev=1 /testdir/testkey #读取集群版本1时值,这个是revison,非key-version etcdctl get /testdir/testkey -w=json #读取版本号 其中revision是集群版本,version是key版本 etcdctl compact 5 # 压缩修订版本到5,5之前的所有版本不可访问 #读取大于等于指定键的 byte 值的键 etcdctl put a 123 etcdctl put b 456 etcdctl put c 789 etcdctl get --from-key b #读取大于等于键 b 的 byte 值的键的命令 返回bc #删除键 etcdctl del foo # 返回删除键的个数1 etcdctl del foo foo1 #删除从 foo to foo1 范围的键 返回1 前闭后开 etcdctl del --prev-kv a #删除键a, 并返回删除的key-value etcdctl del --prefix foo #删除前缀为foo的键,返回删除个数 etcdctl del --prefix fo --prev-kv # 删除前缀为foo的键 ,并返回删除的 key-value etcdctl del --from-key b # 删除大于等于b的byte值的键,返回个数
-
监听操作 watch
-
watch 监测一个键值的变化,一旦键值发生更新,就会输出最新的值并退出。
[root@etcd01 ~]# etcdctl watch testkey # 在另一个节点上输入: [root@etcd02 ~]# etcdctl put testkey Hello watch OK # 结果: [root@etcd01 ~]# etcdctl watch testkey PUT testkey Hello
-
命令
etcdctl watch foo foo9 # 范围监听 - 同时监听foo 到 foo9 ------------ etcdctl watch -i watch foo watch zoo # 观测多个指定键 ------------ etcdctl watch --rev=2 foo # 从版本2开始监听 注:会返回版本2之后的所有修改,即使在监听前
-
-
租约
-
可以为etcd里面所有的键授予租约,当键被附加到租约时,它的存活时间被绑定到租约的存活时间,而租约的存活时间相应的被 time-to-live (TTL)管理。
-
一旦租约的TTL到期,所有绑定租约的键都会被清除。
etcdctl lease grant 20 # 生成100秒的租约,会返回租约id : lease 694d9197f6f23556 granted with TTL(20s) etcdctl put foo bar --lease=694d9197f6f23569(租约ID) # 将键绑定到租约上 etcdctl lease revoke $租约ID # 撤销租约 lease $租约ID keepalived with TTL(20) # 续租20秒 etcdctl lease timetolive $租约ID # 获取租约详情 : lease 694d9197f6f23571 granted with TTL(20s), remaining(13s) etcdctl lease timetolive --keys $租约ID #获取哪些key使用了此租约
-
v3核心API
-
所有的
ETCD V3 API
均在gRPC
服务中定义,该服务对Etcd
服务器可以理解的远程过程调用(RPC)
进行分类。 -
发送到ETCD 服务器的每个API请求都是一次gPRC远程调用,v3中的rpc接口根据功能来进行定义
- KV服务:增删查改键值对
- Watch服务:提供监视键的修改
- 租约Lease:提供活动消息
- 锁:etcd提供分布式共享锁支持
- 选举:暴露客户端选举机制
-
etcdAPI的所有响应都有一个附加的响应标头,包括响应的集群元数据信息 : 可以带上-w=json 获取
- Cluster_ID : 产生响应的集群的 ID。
- 应用服务可以通过 Cluster_ID 和 Member_ID 字段来分辨当前与之通信的正是预期的那个集群或者成员。
- Member_ID :产生响应的成员的 ID。
- Revision : 产生响应时键值存储的修订版本号。 类似于MVCC快照。
- 应用服务可以使用修订号字段来知悉当前键值存储库最新的修订号。当应用程序指定历史修订版进行查询并希望在请求时知道最新修订版时,此功能特别有用。
- Raft_Term : 产生响应时,成员的 Raft 称谓。
- 应用服务可以使用 Raft_Term 来检测集群何时完成一个新的 leader 选举。
- Cluster_ID : 产生响应的集群的 ID。
-
KV-service – 具体内部消息定义自行查阅
//rpc.proto service KV { rpc Range(RangeRequest) returns (RangeResponse) {} // 从键值存储中获取范围内的key; rpc Put(PutRequest) returns (PutResponse) {} // 设定key-value到Etcd键值存储,put 请求增加键值存储的修订版本并在事件历史中生成一个事件; rpc DeleteRange(DeleteRangeRequest) returns (DeleteRangeResponse) {} // 从键值存储中删除给定范围,删除请求增加键值存储的修订版本,并在事件历史中为每个被删除的key生成一个删除事件; rpc Txn(TxnRequest) returns (TxnResponse) {} // 在单个事务中处理多个请求,一个 txn请求增加键值存储的修订版本并为每个完成的请求生成带有相同修订版本的事件。不容许在一个txn中多次修改同一个key; rpc Compact(CompactionRequest) returns (CompactionResponse) {} // 压缩在etcd键值存储中的事件历史。键值存储应该定期压缩,否则事件历史会无限制的持续增长。 }
-
事务Transaction - Txn
- 事务运行etcd在单个事务中自动处理多个请求,这意味着,在一个事务中的所有操作只会让数据库的修订版本revision+1。事务中的所有事件都具有相同的修订版本。注:单个事务中禁止多次修改同一个key
- Txn 方法在单个事务中处理多个请求。txn 请求增加键值存储的修订版本并为每个完成的请求生成带有相同修订版本的事件。
- 事务中所有的比较都是原子操作,都会检查key是否存在、版本,全部为真才触发事务的then/success 请求块,否则,则认为失败并应用 else/failure 请求块。
- 其实在底层单个请求同样是一个事务。
-
压缩 Compact
-
所有修订版本比压缩修订版本小的键历史都将被物理删除,可以理解为即只保留压缩修订版本之后的快照
-
例:
-
插入一个KV: testcompact:1 ,当前集群revision版本为34,key的版本为1
-
更新KV :testcompact:2 ,当前集群revision版本为35,key的版本为2
-
查询快照版本为34时,key为testcompact的值
-
将快照版本压缩到35,再次查询34版本的key
-
只有compact会让数据消失,del删除操作都不行,删除操作实质上是新增了个版本。
-
-
-
-
Watch-service
-
Watch API 提供了一个基于事件的接口,用于异步监视键的更改。etcd 监视程序通过从给定的修订版本(当前版本或历史版本)连续监视来等待key更改,并将key更新流回客户端。
-
监视持续运行,并使用 gRPC 来流式传输事件数据。监视流是双向的,客户端写入流以建立监视事件,并读取以接收监视事件。单个监视流可以通过使用每个观察器标识符标记事件来复用许多不同的观察。这种多路复用有助于减少 etcd 群集上的内存占用量和连接开销。
-
Watch 事件具有如下三个特性:
- 有序,事件按修订顺序排序;如果事件早于已发布的事件,它将永远不会出现在列表上。
- 可靠,事件序列永远不会丢弃任何事件子序列;如果按时间顺序为 a < b < c 三个事件,那么如果 Watch 接收到事件 a 和 c,则可以保证接收到 b。
- 原子,保证事件清单包含完整的修订版;同一修订版中通过多个键进行的更新不会拆分为多个事件列表。
-
Watch 观察将要发生或者已经发生的事件。输入和输出都是流;输入流用于创建和取消观察,而输出流发送事件。一个观察 RPC 可以在一次性在多个 key 范围上观察,并为多个观察流化事件。整个事件历史可以从最后压缩修订版本开始观察。(当客户端希望从最近已知的修订版本开始恢复断开的观察者时有用)
-
无法监听压缩修订版本前的版本。
-
-
Lease-service
-
Lease service 提供租约的支持。Lease 是一种检测客户端存活状况的机制。集群授予具有生存时间的租约。如果Etcd集群在给定的 TTL 时间内未收到 keepAlive,则租约到期。
-
为了将租约绑定到键值上,每个 key 最多可以附加一个租约。当租约到期或被撤销时,该租约所附的所有 key 都将被删除。每个过期的key都会在事件历史记录中生成一个删除事件。
-
在 rpc.proto 中 Lease service 定义的接口如下:
service Lease { rpc LeaseGrant(LeaseGrantRequest) returns (LeaseGrantResponse) {} # 创建租约 rpc LeaseRevoke(LeaseRevokeRequest) returns (LeaseRevokeResponse) {} #撤销租约 rpc LeaseKeepAlive(stream LeaseKeepAliveRequest) returns (stream LeaseKeepAliveResponse) {} #维持租约 rpc LeaseTimeToLive(LeaseTimeToLiveRequest) returns (LeaseTimeToLiveResponse) {} # 获取租约信息 }
-
具体消息体自行查阅。
-
分布式锁简单实现
-
简而言之:通过在etcd中存入key值来实现上锁,删除key实现解锁
-
因为 etcd 使用 Raft 算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。
-
ETCD提供了lease租约、全局版本revision机制、prefix机制、以及watch监听机制,那么基于这些功能就可以实现一个分布式锁
-
租约:可以让获取锁的对象在耗时的情况下保持心跳,同时防止网络故障导致的死锁,即自动释放,保证了业务的可用性和安全性。
-
Revision:每个 key 带有一个 Revision 号,每进行一次事务便+1,它是全局唯一的, 通过 Revision 的大小就可以知道进行写操作的顺序。在实现分布式锁时,多个客户端同时抢锁, 根据 Revision 号大小依次获得锁,可以避免 “羊群效应” ,实现公平锁。本质上就是 etcd 维护的一个在集群范围内有效的 64计数器(单调递增)。只要 etcd 的键空间发生变化, revision 的值就会相应地增加。
-
prefix机制:例如一个名为/etcd/lock的锁,两个客户端一起争抢,同时写入了/etcd/lock/id1,/etcd/lock/id2 客户端自己的ID,但是客户端现在如何判断自己是否获得锁,利用prefix查询机制,查出这两条数据,然后利用Revision机制来判断自己写入的id是否是最先写入的,由此来判断自己是否获得锁。
-
watch机制:watch机制不仅可以监听一个key,同样也可以监听一个范围,
- 当前客户端抢锁失败的时候,就得等待获得锁的客户端释放锁。
- 如何知道自己已获取锁,那么就得监听自己的revision-1的key的锁释放,**这个key称为pre-key,**如果监听到pre-key的DELETE事件,那么就说明自己已获得锁。
-
-
流程
- 客户端初始化与建立连接;
- 创建可取消的租约,自动续租;
- 创建事务,获取锁;
- 执行业务逻辑,最后释放锁。
进阶
见一下回