分布式系统中最关键数据的分布式、可靠的键值存储,CP模型
unix 的“/etc”文件夹和分布式系统 (“D”istribute system) 的 D,组合在一起表示 etcd 是用于存储分布式配置的信息存储服务。

Features

  • 扁平的key-value结构,B-Tree\boltdb
  • 支持客户端SSL认证
  • 支持ttl,Lease
  • 基于raft协议的分布式拓扑,Etcd使用PreVote机制在Follower变成Candidate前先预投票,如果有机会成为Leader才变为Candidate减少无效Leader选举
  • 适用于读多写少存储
  • 扁平的kv结构

Etcd v2问题

1.不支持范围查询和分页,查询容易产生大包导致昂贵的查询
2.不支持多key事务
3.watch机制不可靠:内存型、不支持保存key历史版本的数据库
4.性能瓶颈:基于Http 1.1 + JSON,无压缩 + 不能连接多路复用
5.内存开销:大内存的情况下持久化到磁盘会耗大量的CPU和磁盘I/O

V3如何解决V2问题

1.支持范围、分页查询,可避免大包等 expensive request
2.数据模型从层次型目录结构改成扁平的 key-value,实现了事务,支持多 key 原子更新
3.它通过引入 B-tree、boltdb 实现一个 MVCC 数据库,提供稳定可靠的事件通知
4.使用了 gRPC API,使用 protobuf 定义消息,消息编解码性能相比 JSON 超过 2 倍以上,并通过 HTTP/2.0 多路复用机制,减少了大量 watcher 等场景下的连接数,其次使用 Lease 优化 TTL 机制,每个 Lease 具有一个 TTL,相同的 TTL 的 key 关联一个 Lease,Lease 过期的时候自动删除相关联的所有 key,不再需要为每个 key 单独续期。
5.基于 boltdb 的持久化存储,显著降低了 etcd 的内存占用、避免了 etcd v2 定期生成快照时的昂贵的资源开销

etcd grpc-gateway可支持HTPP/1.x协议

MVCC模块主要由基于内存的treeIndex模块和boltdb模块组成,boltdb相当于Raft协议所指的状态机
存储层基于预写日志(WAL),快照、boltdb模块

线性读ReadIndex实现方式

客户端基于Round-robin算法通过轮询的方式依次从endpoint列表中选择一个endpoint访问(长连接),使server负载尽量均衡
写请求到达leader后,先写入WAL,然后广播到各个节点,一半以上节点持久化成功后,日志条目将被标记为已提交,etcdserver异步从Raft模块获取已添加的日志条目,应用到boltdb

etcd查询走的是boltdb,raft日志条目commit后走的是异步应用的boltdb,如果刚更新一个条目立即来查询可能会查到旧的数据,因此使用ReadIndex来解决这个问题

当Follower收到一个线性读请求时,会首先从Leader获取集群的最新已条件的日志索引,Leader收到ReadIndex请求后会向Follower节点发送心跳确认,一半以上节点确认Leader身份后才能将读已提交的committed index返回给Follower,Follower节点会等待,直到boltdb已应用的applied index大于等于Leader返回的committed index,并通知读请求可以去boltdb中查询了

etcd的串行读是指状态机直接返回数据,不通过Raft协议于集群交互的模式,具有低延时、高吞吐量的特点,适合对数据一致性要求不高的场景

MVCC

由treeIndex和嵌入式的KV持久化存储库boltdb组成

  • treeIndex 基于 Google 开源的内存版 btree 库实现
  • boltdb 基于 B+ tree 实现的 key-value 键值库,支持事务,提供 Get/Put 等简易 API 给 etcd 操作

treeIndex 模块只会保存用户的 key 和相关版本号信息,用户 key 的 value 数据存储在 boltdb 里面,相比 ZooKeeper 和 etcd v2 全内存存储,etcd v3 对内存要求更低

etcd并非每次查询都会进boltdb模块,前面还有一层读事务buffer,通过二分查找要访问的key是否在buffer里面,若命中直接返回

boltdb 里每个 bucket 类似对应 MySQL 一个表,用户的 key 数据存放的 bucket 名字的是 key,etcd MVCC 元数据存放的 bucket 是 meta。

etcd采用lazy delete,一方面立即删除可能会产生B+树节点再平衡,影响读写请求的性能,另一方面留给watcher时间做相应的处理。

防止雪崩

任何提交的请求都会进行限速判断,如applied index远小于committed index,会返回too many requests

提案交互

为请求生成唯一ID,将请求关联到对应的消息通知channel,向Raft发起提案后,KV Server模块会等待此put请求,等待写入结果通知channel或超时,默认超时时间7秒(5秒磁盘IO延时 + 2*1秒竟选超时时间)
如果超时会返回request timed out

Write Ahead Log

Len Field
Type - 类型
Crc - 校验码
Data - 记录内容
Len Field
Type
Crc
Data

Crash恢复

重启恢复时如何找回异常提案
关键点:如何基于WAL实现幂等回放
Raft协议日志条目本身存在index且全局单调递增,可以通过index判断WAL是否已经执行过了,但还不够安全,etcd引入consistent index来存储已经执行过的日志条目索引,实现幂等

身份验证

密码认证

身份验证开销昂贵,基于带有效期的Token提升性能
Simple Token描述性弱,引入JWT Token (Json Web Token)

header
paylod
signature=RSA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),key)

header描述算法、类型,paylod包含用户信息、版本、过期信息

证书认证

防篡改和中间人攻击

RBAC

Etcd提供了基于角色的权限控制
User、Role、Permission
目前支持三种权限,分别是 READ、WRITE、READWRITE。

Quota 模块

一方面默认 db 配额仅为 2G,当你的业务数据、写入 QPS、Kubernetes 集群规模增大后,你的 etcd db 大小就可能会超过 2G。另一方面我们知道 etcd v3 是个 MVCC 数据库,保存了 key 的历史版本,当你未配置压缩策略的时候,随着数据不断写入,db 大小会不断增大,导致超限。

其次你需要检查 etcd 的压缩(compact)配置是否开启、配置是否合理。etcd 保存了一个 key 所有变更历史版本,如果没有一个机制去回收旧的版本,那么内存和 db 大小就会一直膨胀,在 etcd 里面,压缩模块负责回收旧版本的工作。
超限后会返回 database space exceeded.

Watch机制

  • synced watcher 表示此类 watcher 监听的数据都已经同步完毕,在等待新的变更。
  • unsynced watcher 表示此类 watcher 监听的数据还未同步完成,落后于当前最新数据变更,正在努力追赶。追赶上后变为synced watcher

接收Watch事件的Channel存在一个默认容量为1024的buffer,如果buffer满后会将watcher和事件列表保存到victim的watcherBacth结构,相当于变成slow watcher,通过异步机制重试保证事件的可靠性

事件匹配Watcher

如果你创建了上万个 watcher 监听 key 变化,当 server 端收到一个写请求后,etcd 是如何根据变化的 key 快速找到监听它的 watcher 呢?一个个遍历 watcher 吗?
个个遍历 watcher的时间复杂度O(N)
方案:单key基于map O(1),range基于区间树O(LogN)

背景知识:
集群成员变更是最大的操作挑战之一

  • 新增成员导致leader过载:leader向新的follower同步大的快照,导致不能及时和其他follower进行心跳,其他follower任务leader挂了,发起新的leader选举周期
  • 网络分区场景:当leader仍然在quorum中,保存不变继续工作,当leader不在quorum中,leader会退化成follower,此时集群可用性受到影响,集群将重新选举leader。出现僵局,2-partion-2,任意分区都无法选出leader
  • 错误配置

为了解决上述问题,引入Raft Learner,一个新的状态,Leaner不参与选举,直到它同步了leader的所有log

etcd v3 authentication

  • v3使用grpc每个连接认证一次,v2基于restful每个request认证一次
  • 简单易用
  • 更强一致性保证
  • 每个client必须创建一个专门的连接来认证
  • 添加permission信息到到raft命令行
  • 每个请求在状态机层面被check,而不是api层

etcd versus other key-value stores (KVS)

Zookeeper(CP):

针对Zookeeper的改进

  • 动态的集群节点重配置
  • high load时稳定读写
  • mvcc 数据模型
  • 可靠的key监视,从不静默删除事件
  • 租约原语将连接与会话分离
  • 安全分布式共享锁的API

Consul(CP-保证一致性的前提下,尽量保证可用性):

etcd和Consul解决不同的问题。如果要寻找一个分布式一致的键值存储,etcd是比Consul更好的选择。如果要寻找端到端集群服务发现,etcd将没有足够的功能;请选择Kubernetes、consul。

use etcd for metadata

etcd复制单个一致复制组中的所有数据。对于以一致的顺序存储高达几GB的数据,这是最有效的方法。簇状态的每一个修改,可能会改变多个键,从一个单调递增的计数器分配一个全局唯一的ID,称为etcd中的修订。因为只有一个复制组,所以修改请求只需要通过raft协议来提交。通过将一致性限制为一个复制组,etcd通过一个简单的协议获得分布式一致性,同时实现低延迟和高吞吐量。

etcd后面的复制无法水平扩展,因为它缺少数据分片。相比之下,NewSQL数据库通常在多个一致的复制组之间共享数据,存储的数据集数量达到或超过万亿字节。但是,要为每个修改分配一个全局唯一且递增的ID,每个请求必须通过复制组之间的附加协调协议。此额外的协调步骤可能会在全局ID上发生冲突,从而迫使已排序的请求重试。结果是,对于严格的排序,结果是一个比etcd更复杂的方法,其性能通常比etcd差。

如果应用程序主要考虑元数据或元数据排序,例如为了协调进程,请选择etcd。如果应用程序需要跨越多个数据中心的大型数据存储,并且不严重依赖强全局排序属性,请选择NewSQL数据库。

use ectd for 分布式协调

etcd具有分布式协调原语,如事件监视、租约、选举和现成的分布式共享锁。这些原语都由etcd开发人员维护和支持;将这些原语留给外部库可以逃避开发基础分布式软件的责任,本质上使系统不完整。NewSQL数据库通常希望这些分布式协调原语由第三方编写。同样,著名的还有一个独立的菜谱管理员和一个独立的协作库。consul提供了一个本机锁定API,甚至道歉说它“不是一个银弹的方法”。

理论上,可以在任何提供强一致性的存储系统上构建这些原语。然而,算法往往是微妙的;很容易开发出一个看似有效的锁定算法,但由于雷鸣般的羊群效应和时间偏差而突然中断。此外,etcd支持的其他原语(如事务内存)依赖于etcd的MVCC数据模型;简单的强一致性是不够的。

对于分布式协调,选择etcd有助于避免操作上的麻烦并节省工程工作量。

posted on 2022-03-17 16:09  j.liu windliu  阅读(414)  评论(0编辑  收藏  举报