「etcd」- ReadIndex

读取到旧数据(异步提交的数据不一致)

那各个节点数据在任意时间点读出来都是一致的吗?什么情况下会读到旧数据呢?

1)Client 发起一个更新 hello 为 world 请求;

2)Leader 收到写请求,它会将此请求持久化到 WAL 日志,并广播给各个节点(并发的,一方面广播给 Follower 节点,一方面自己写 WAL);
若一半以上节点持久化成功,则该请求对应的日志条目被标识为已提交,etcdserver 模块异步从 Raft 模块获取已提交的日志条目,应用到状态机(如 boltdb 等);

Client 发起一个读取 hello 的请求,假设此请求直接从状态机中读取, 如果连接到的是 C 节点,若 C 节点磁盘 I/O 出现波动,可能导致它应用已提交的日志条目很慢,则会出现更新 hello 为 world 的写命令,在 client 读 hello 的时候还未被提交到状态机,因此就可能读取到旧数据;

ReadIndex

前面我们聊到串行读时提到,它之所以能读到旧数据,主要原因是 Follower 节点收到 Leader 节点同步的写请求后,应用日志条目到状态机是个异步过程,那如何保证读取到最新的数据呢?

其实这个机制就是叫 ReadIndex,它是在 etcd 3.1 中引入的。该机制保证:在读取的时候,确保最新的数据已写入状态机中;

原理如图:

线性读通过 ReadIndex 机制保证数据一致性原理,大致原理如下:

当 Follower C 收到线性读请求时,将由其线性模块处理,Follower C 首先会从 Leader 获取集群最新的已提交的日志索引 (committed index),如上图中的流程二所示;

Leader 收到 ReadIndex 请求时,为防止脑裂等异常场景,Leader 会向所有 Follower 发送心跳确认,一半以上 Follower 确认 Leader 身份后,Leader 才能将 Committed Index 返回给 Follower C(上图中的流程三);

Follower C 则会等待(上图中的流程四),直到其状态机 Applied Index >= Leader’s Committed Index,然后将通知读请求其数据已赶上 Leader,可以从状态机中访问数据(上图中的流程五);

当然还有其它机制也能实现线性读,例如,在早期 etcd 3.0 中读请求通过走一遍 Raft 协议保证一致性,这种 Raft log read 机制依赖磁盘 IO,性能相比 ReadIndex 较差;