浅谈分布式系统与一致性协议(一)
浅谈分布式系统与一致性协议(二)
浅谈分布式系统与一致性协议(三)
深入浅出之etcd
深入浅出之etcd(二)
etcd版本之v3
etcd之安全性阐述
etcd的多版本并发控制
概述
etcd v3存储的数据通过KV API对外暴露,并在API的层级支持mini事务。并且为了保证向后兼容,保留了etcd v2的协议与API。也就是说etcd v2和etcd v3本质上是共享一套Raft协议代码的,区别是API不同,存储不同,数据互相隔离。v2的数据只能通过v2的API访问,v3的数据只能通过v3的API访问。
etcd v2 到 etcd v3
etcd广泛应用到分布式网络,服务发现,配置中心,分布式系统调度和负载均衡领域。etcd专注于key-value存储而不是完整的数据库,通过HTTP+JSON的方式暴露给外部API,watch机制提供持续监听某个key变化的功能,基于TTL的key自动过期机制,这些特性很好的满足了etcd的初步需求。但是,也出现了一些问题,客户端需要频繁的与服务端进行通信,集群即使在空闲时间也需要承受很大的压力,垃圾回收key的时间不稳定。所以,etcd v3借鉴了etcd v2的经验,做出了如下优化:
gRPC
gRPC是一个高性能,跨语言的RPC框架,基于HTTP/2协议实现。使用protobuf作为序列化和反序列化协议,即基于protobuf来声明数据模型和RPC接口服务
序列化和反序列化优化
protobuf效率高,远高于JSON,etcd v3的gRPC序列化和反序列化的速度是etcd v2的两倍多
减少了TCP连接
etcd v2的通信协议使用HTTP/1.1,gRPC支持HTTP/2,HTTP/2对HTTP通信进行多路复用,可以共享一个TCP连接。因此etcd v3大大减少了客户端与服务端的连接数,一个客户端与服务端建立一个TCP连接,而etcd v2,一个客户端需要与服务端建立多个连接,每个HTTP请求都需要建立连接
租约机制
etcd v2的key自动过期是基于TTL的,客户端可以为一个key设置自动过期时间,一旦TTL到了。服务端就自动删除该key。如果客户端不想服务端删除某个key,就需要定期更新这个key的TTL。也就是说即使整个集群都处于空闲状态,也会也很多客户端需要与服务器进行定期通信,以保证某个key不会自动被删除。而且TTL都是设置在key上,那么对于客户端想保留每个key,客户端需要对每个key进行定期更新,即使这些key的过期时间都是一样的
etcd v3使用(lease)租约机制,替代了基于TTL的自动过期机制。用户创建一个lease,然后将这个租约与key关联起来。一旦租约过期,etcd v3服务端就会删除与这个租约关联的所有的key。即,多个key的过期时间是一样的,这些key可以共享一个租约。这样减少了客户端请求的数量,对于共享一个租约的key,客户端只需要更新这个租约的过期时间即可,不需要更新所有的key
etcd v3 的watch机制
watch机制使得客户端可以监控一个key的变化,当key发生变化时,服务端将通知客户端,而不是让客户端定期向服务器发送请求去轮询key的变化。etcd v2的服务端对每个客户端的每个watch请求都维持着一个HTTP长连接,如果数千个客户端watch数千个key,那么etcd v2服务端的socket和内存资源会很快消耗掉。
etcd v3对同一个客户端的watch请求进行多路复用,这样的化,同一个客户端只需要与服务端维护一个TCP连接即可,这样大大减少了服务端压力
etcd v2的每个watcher都会占用一个TCP资源和一个goroutine资源,大概消耗30~40kb。etcd v3减少每个Watcher带来的资源消耗以此支持了大规模的watch,同样的用户不同的watcher只消耗一个go routine,这样再一次减轻了服务器资源的消耗
etcd v3的数据存储模型
etcd 时一个key-value数据库,etcd v2只保存key的最新的value,之前的value直接覆盖掉了。但是etcd v2维护了一个全局的key例是记录变更窗口,默认保存最新的1000个变更,。etcd v2只保存1000个历史变更,这样带来的后果是watch丢失事件。etcd v3抛弃了这种设计,引入了MVCC(多版本并发控制),采用了历史纪录为主索引的存储结构,保存了key的所有变更记录。etcd v3可以存储十万个历史记录进行快速查询,并且更具用户要求进行压缩合并。由于etcd v3实现了MVCC,记录了所有的历史记录,所以这些数据不能都放在内存中。因此,etcd v3是一个磁盘数据库,底层的存储引擎使用BoltDB
etcd v3的迷你事务
很多情况下,客户端需要同时去读或者写一个key,或者很多个key。提供同步原语防止数据竞争是重要的。etcd v3中引入了迷你事务(mini-transaction)的概念。每个迷你事务都可以包含一些列条件语句,只有在满足特定条件下事务才会执行。迷你事务支持原子的比较多个键值并且操作多个键值。之前CAS是特殊的针对单个key的迷你事务。
gRPC服务
发送至etcd v3服务器的每一个API请求均为gRPC远程过程调用。etcd v3将其归类为不同的服务(service),而service又可分为方法(method)定义和消息(message)定义。
根据etcd v3的所定义的不同服务,其API可以分为键值(KV),集群(Cluster),维护(Maintenance),认证/鉴权(Auth),观察(Watch)与租约(Lease)六大类
各类服务所包含的方法具体描述了与其对应的API所具备的功能,从大的角度概括,这些服务又可以分为两类,其中一类是管理集群的API,具体包括如下功能:
另外一大类是处理etcd键值空间的API,具体包括如下:
请求和响应
etcd v3的所有RPC都遵循相同得格式。每个RPC都形如一个函数声明,都有一个入参和返回值。
etcd v3 API的所有响应都携带一个响应头部,包含了etcd集群的元数据。示例代码如下:
message ResponseHeader {
uint64ccluster_id = 1 ;
uint64 member_id = 2 ;
int64 revision = 3;
uint64 raft_term = 4;
}
上述代码字段说明如下:
客户端可以通过revision的值获取发生该操作时,etcd集群后端键值存储最新revision。客户端可以通过Rafr_Term的值来检测etcd集群是否完成了一次新的领导人选举
KV API
键值对(key-value pair)是KV API所能处理的最小单位,每个键值对均包含一些protobuf格式的子段
message KetValue {
bytes key = 1;
bytes value = 2;
int64 create_revision = 3;
int64 mod_revision = 4;
int64 version = 5 ;
int64 lease = ;
}
从上面定义可以看出,在KV message中,除了key-value映射值及其lease信息之外,还有一类重要的revsiosn元数据(包括create_revision和mod_revsion)。这些revsion信息可以根据创建时间和修改时间对key进行排序
revision
etcd 的revision本质上就是etcd维护的一个在集群范围有效的64位计数器。只要etcd的键空间发生变化,ervision值也会行吟阁增加。也可以jiangrevision看成是全局的逻辑时钟,即所有针对后端存储的修改操作进行连续的排序。revision的值是单调递增的,而与某个revision相关联的数据则是那些改变了后端存储的数据。从内部实现来看,出现一个revision,就意味着某些修改写入了后端的B+树,而这些修改柴采用增大的revision作为索引
对于etcd v3的多版本并发控制,revision价值不言而喻。MVCC模型是指由于保存了键空间的历史,因此可以查看过去某个revision(版本)的key-value的存储。同时为了实现细粒度的存储管理,集群管理者可以自定义配置键空间历史保存策略。etcd v3会借助自定义的计时器废弃旧键的版本(revision)典型的etcd v3集群可以使被替代的key的数据保留数小时。因此,etcd v3具备对客户端长时间断开连接的可靠处理能力,突破了仅能处理暂态网络中断的限制。在这种情况下,watch客户端可以直接根据最近以此观察到的revision进行恢复。类似的,如果客户端希望读取某个时间点的key-value存储状态,则只需要在请求中附带某个revision的值即可返回该revision提交时间点的key空间状态
键区间
etcd v3数据模型采用扁平key空间,为所有key建立索引。该模型有别于其他常见的采用层级系统将key组建为目录(directory)的key-value存储系统。key不再以目录的形式列出,而是左闭右开的形式,如[ key1,keyN)。对于key去就按的操作,既保留了对目录形式key的查找能力。
事务
在 etcd v3中,事务就是一个原子的,针对key-value存储操作的If/Then/Else结构。事务提供了一个原语,用于将请求归并到一起放在原子块中,这些原子块的执行条件以key-value存储里的内容为依据。事务可以用来保护key不受其他并发更新操作的修改,也可以构建CAS操作,并以此作为更高层次并发控制的基础。
所有的事务都由一个比较“连接(conjunction)”来守护,类似于if声明,每个比较都会检查后台存储中的一个key。这个检查可以是如下内容:该key在后台存储是否有value?该key的value是否等于某个给定的值?除了value,还可以检查这个key的revision或者version。。如果所有的比较都返回true,那么就说明该事务成功执行了,并且执行该事务success请求块中的操作,反之就说明该事务失败了,转而执行该事务failure请求块中的操作
Event
对于每个key而言,发生的每一个变化都以Event消息进行表示。一个Event消息提供了变化的类型与对应改变的数据,其字段定义代码如下:
message Event{
enum EventType{
PUT = 0;
DELETE = 1;
}
EventType type =1;
KeyValue kv = 2; //与Event管来奶的key-value
KeyValue prev_kv = 3; //对应紧接着Event之前的revision的key-value
}
流式Watch
wacth操作是长期持续存在地请求,watch流是双向的。Client通过写入流来创建watch,另一方面,Client通过读入流来接收watch到的Event。
etcd v3的watch机制确保检测到的Event具有有序,可靠与原子化的特点,各个特点对应的意义如下:
Lease API
租约是一种检测客户端活跃度的机制。Lease机制应用广泛。租约是有生存时间的,集群为租约授予一个TTL。当key被授予一个租约时,他的生存时间即为Lease生存时间。当Lease的TTL到期时,所有与之关联的key都会被删除。
如果etcd集群在给定的TT周期内没有收到一个keepAlive消息维持租约,那么该租约将过期。etcd3所支持的租约机制可为etcd中的某一个或者多个key关联租约,一个key最多关联一个租约。当一个租约过期时,所有关联的key都会被自动删除。每个过期的key都会产生一个“删除”事件