

ETCD对写请求的处理
前文简要的梳理了Raft协议在ETCD节点中的实现和数据流。本节以ETCD3中的一次普通的数据写(V2/V3 API)为例介绍ETCD中存储系统的设计和实现。注意本节之所以只关注“写”操作,是因为读操作直接访问本地存储,不涉及复杂的分布式决策流程。V3 API中引入了MVCC存储,本质上与V2中的处理流程是一样的,区别在于最终执行时针对的本地存储不同(基于V3.1.8源码)。
先看V3 API中的Put操作的实现,Fig 3.1为V3中Put操作的gRPC Handler定义。
Fig 3.1 V3 Put的gRPC Handler

这里的KVServer是一个Interface,具体的KVServer.Put实现在ETCDServer中。
Fig 3.2 V3 API Put操作的实现

这里有两个比较有意思的点:
· V3 Put消息(指令)传入Raft状态机后是怎么被执行的?
· Raft状态机是一个分布式的一步过程,客户端访问V3 API时如何实现等待分布式计算的结果?
先看V3 Put指令在Raft状态机的执行过程。在ETCD中Raft状态机是对每个消息进行“协商”的过程,这个过程其实是不关注其具体内容的,因此这里提到的“执行”的范畴,实际上是消息已经通过分布式节点协商(Committed),要在本地存储中执行(Apply)的过程。
Fig 3.3 applyEntryNormal中执行V3指令

值得一提的是,ETCD中采用一种有趣的方式来实现客户端等待异步任务的策略,客户端请求注册一个Waiter并监听这个Waiter对应的一个Channel,客户端会一直阻塞,直到这个Waiter被触发或超时。
V2指令的执行过程基本类似,Fig 3.4为V2 API一次Set操作的gRPC接口定义。
Fig 3.4 V2 Set操作gRPC Handler

决策并提交指令之后,就需要在状态机中执行并修改内存数据库的状态。
Fig 3.5 applyEntryNormal中执行V2指令
