官网:https://etcd.io/docs/v3.3/learning/client-architecture/
参考blog : https://www.cnblogs.com/huageyiyangdewo/p/17558210.html
主要参考和引用:https://blog.csdn.net/qq_24433609/article/details/120653747
架构
client
引入了 balancer 来做负载。

索引类型
在 内存中维护了一个 treeIndex(key:版本) 的非聚去索引(secondery index),在持久化存储中维护了一个 B+ 树的。boltDB 中的 kv 是 ‘版本:value’ 的组合
to speed up range queries over keys. The keys in the btree index are the keys of the store exposed to user. The value is a pointer to the modification of the persistent b+tree.
内存中的 btree 主要是为了加速range queries,节点存储的是指向

写入操作流程
应用到 WAL 日志后,就认为持久化成功了,可以返回确认信息。


读取操作流程

串行读
直接读状态机数据返回、无需通过 Raft 协议与集群进行交互。
由于 raft 只需要一半以上节点认同就认为数据存储成功了,所以串行读取数据可能是读取到旧版本的数据。
// WithSerializable makes 'Get' request serializable. By default,
// it's linearizable. Serializable requests are better for lower latency
// requirement.
func WithSerializable() OpOption {
return func(op *Op) { op.serializable = true }
}
func (e *EtcdReader) ServiceDiscovery(key string) error {
// 根据服务名称的前缀,获取所有的注册服务
resp, err := e.cli.Get(context.Background(), key, clientv3.WithPrefix(),clientv3.WithSerializable())
if err != nil {
return err
}
// 遍历key-value,打印
for _, kv := range resp.Kvs {
fmt.Println(string(kv.Key), string(kv.Value))
}
return nil
}
线性读
golang 的 api 中,默认进行的该读取方式。
线性读取会走 共识模块,需要从 follower 收集心跳信息保证 leader 的合法性(防止脑裂问题,保证分区可用)
接收请求的 follower 节点会对比当前的最新版本号和 leader 传递过来的数据版本号,如果大于等于已经共识的版本号就回返回。(这里存在一个问题,获取的数据可能由于)

数据持久化机制
预写日志 WAL 模块、快照 Snapshot 模块、 boltdb 模块
WAL
WAL 和 Mysql 的WAL 机制类似,都是为了保障机器意外崩溃后依然还能恢复数据。
Snapshot
- 默认超过 10000 条日志记录就进行一次快照
- 删除旧日志,防止日志膨胀
etcd appends all key changes to a log file. This log grows forever and is a complete linear history of every change made to the keys. A complete history works well for lightly used clusters but clusters that are heavily used would carry around a large log.
To avoid having a huge log etcd makes periodic snapshots. These snapshots provide a way for etcd to compact the log by saving the current state of the system and removing old logs.
boltdb
boltdb是单机kv数据库,所有数据都保存在一个文件中,通过内存映射的方式进行数据读取和写入,存储结构采用类B+树的组织形式,支持一写多读的事务机制,支持bucket增删改查、bucket嵌套和k/v增删改查等功能,etcd的底层存储系统就是基于boltdb实现的,不过etcd是fork了bolt项目并基于此进行了优化。
内存映射:映射其实就是在虚拟地址空间和磁盘地址空间建立了一个一一对应关系,在逻辑地址空间为文件复配一个大小相等的逻辑空间。在文件映射过程中并没有数据拷贝,文件并没有放入内存,只是逻辑上放入了内存中,具体到代码就是建立了初始化数据结构,这个过程由mmap()系统调用实现的,所以映射效率非常高。
映射过程如下:
调用mmap()函数,相当于给磁盘文件分配虚拟内存空间,它返回一个指针ptr,这个指针指向的是一个逻辑地址,操作系统要操作其中的数据必须通过MMU翻译才能获取对应的内存物理地址。
建立内存映射并没有数据拷贝,这时通过MMU翻译ptr是无法找到与之对应的内存物理地址的,也就是MMU失败,产生一个缺页中断,缺页中断响应函数会在磁盘的交换区查找相应页面,如果找不到证明文件还未读入磁盘,则通过mmp()建立的映射关系将文件加载到物理内存中,如果在交换区找到对应文件则换入。
如果在数据拷贝过程中发现内存不够用,则通过虚拟内存管理将部分页面数据换出到交换区。
事物实现
类似 redis 的事物
- 请求会被统一封装在一次执行动作里。
- 每个 tx 只会导致版本号升级一次。
- 基于 CAS 的思想进行实现,需要事物涉及的值版本号都不变。
操作系统是通过 COW (copy-on-write) 技术进行 page管理,通过 cow 技术,系统可实现无锁的读写并发,但是无法 实现无锁的写写并发,这类数据库读性能超高,但写性能一般,因此非常适合于 “读多写少”的场景。同时BoltDB 支持完全可序列化的ACID 事务。因此最适合作为etcd 的底层存储引擎。(https://blog.csdn.net/zhangxm_qz/article/details/121314551)
A transaction is an atomic If/Then/Else construct over the key-value store. It provides a primitive for grouping requests together in atomic blocks (i.e., then/else) whose execution is guarded (i.e., if) based on the contents of the key-value store. Transactions can be used for protecting keys from unintended concurrent updates, building compare-and-swap operations, and developing higher-level concurrency control.
A transaction can atomically process multiple requests in a single request. For modifications to the key-value store, this means the store’s revision is incremented only once for the transaction and all events generated by the transaction will have the same revision. However, modifications to the same key multiple times within a single transaction are forbidden.
All transactions are guarded by a conjunction of comparisons, similar to an If statement. Each comparison checks a single key in the store. It may check for the absence or presence of a value, compare with a given value, or check a key’s revision or version. Two different comparisons may apply to the same or different keys. All comparisons are applied atomically; if all comparisons are true, the transaction is said to succeed and etcd applies the transaction’s then / success request block, otherwise it is said to fail and applies the else / failure request block.
raft 协议
原论文:https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf
https://etcd.io/docs/v3.3/dev-internal/discovery_protocol/
共识主流程:
实现中,只有 leader 可以处理写请求,读请求可以由 follower 来处理。
脑裂问题
通过收集心跳信息来保证,如果数量超过
leader失效问题
- leader 长时间失效后重新上线通过全局的递增唯一 term ID 来过滤重新上线的 leader 的请求
- leader 失效时间大于心跳检测时间后 follwer 会晋升为 candidate 来参加选举
- leader 段时间失效,导致服务段时间不可访问
- 通过唯一递增的 request id 来判断消息是否已经过期
过期消息过滤处理(保证一致性)
消息过滤:term小于当前、上一条数据在 prevLog 不存在相同 index和term 的、上一条 index(理解为全局的唯一id) 相同但是pre term不同

配置建议
默认内存使用量:2G,建议不超过 8G
节点数配置: 5/7
跨地域:不建议,因为共识会产生网络IO消耗,影响性能。
节点变更:先移除再添加,防止产生平票或者票数一直不足的饥饿问题。
默认报警:平响 > 100ms 会产生,可能原因为 IO、请求量大、cpu超载
调优
官网:https://etcd.io/docs/v3.5/tuning/
常见问题
quota 配额不足:当配置的内存可用空间不足的时候,会禁止写入,并产生警告,当重新分配内存的时候,需要发送取消警告的消息

被折叠的 条评论
为什么被折叠?



