ETCD 一致性读

2017-11-30 database

在分布式系统中,存在多种一致性模型,不同模型给应用提供的数据保证也不同,其代价也略有区别。一般来说,一致性越强,代价越高,同时应用也越友好。

简介

RAFT 的实现模式是一个 Leader 和多个 Followers,所有的更新请求都经由 Leader 处理,而 Leader 再通过日志同步的方式复制到 Follower 节点。

而对于读请求处理则没有进行限制,所有的节点都可以处理用户的读请求,但是由于部分原因,可能会导致数据不一致:

  • Leader 向 Follower 复制数据存在时间差,Follower 的状态总的落后于 Leader,而且 Follower 之间的状态也可能存在差异,直接读取会导致不一致;
  • 如果只从 Leader 读取数据,当发生网络分区时,剩余节点已选出了新 Leader 但是旧 Leader 并没有感知到,从而出现脑裂,而此时旧 Leader 的数据已经过时。

也就是说,如果不对读流程作特殊处理,就会导致非一致性的读。

ReadIndex

etcd 通过 ReadIndex 机制实现线性一致读,简单来说就是:Leader 在处理读请求时,需要与集群多数节点确认自己依然是 Leader,然后读取已经被应用到应用状态机的最新数据。

数据结构

raft/read_only.go 中保存了与只读查询相关的数据结构。

type ReadState struct {
        Index      uint64  // 读请求产生时当前节点的Commit
        RequestCtx []byte  // 客户端读请求的唯一标识,由应用判断请求
}

type readIndexStatus struct {
        req   pb.Message            // 在处理客户端读请求时向协议核心发起的ReadIndex请求
        index uint64                // Leader当前的Commit信息
        acks  map[uint64]struct{}   // 记录Followers响应,Follower确认了Leader的心跳消息后会记录一次
}

type readOnly struct {
        option           ReadOnlyOption
        pendingReadIndex map[string]*readIndexStatus
        readIndexQueue   []string   // 所有ReadIndex请求数组
}

ReadState

记录每个客户端的读请求的状态,在使用时需要确保调用 ReadIndex() 函数,并最终会通过 Ready 返回给应用,由应用负责处理客户端的读请求。

为了保证线性一致性读,应用需要根据该请求发起时的节点 Commit 信息决定返回何时的数据。

readIndexStatus

用来追踪 Leader 向 Followers 发送的心跳信息的响应。

readOnly

管理全局的读 ReadIndex 请求。

参考

关于一致性模型的更多描述可参考 Strong consistency models