8 Client interaction
This section describes how clients interact with Raft, including how clients find the cluster leader and how Raft supports linearizable semantics [10]. These issues apply to all consensus-based systems, and Raft’s solutions are similar to other systems.
Clients of Raft send all of their requests to the leader. When a client first starts up, it connects to a randomly-chosen server. If the client’s first choice is not the leader, that server will reject the client’s request and supply information about the most recent leader it has heard from (AppendEntries requests include the network address of the leader). If the leader crashes, client requests will time out; clients then try again with randomly-chosen servers.
Our goal for Raft is to implement linearizable semantics (each operation appears to execute instantaneously, exactly once, at some point between its invocation and its response). However, as described so far Raft can execute a command multiple times: for example, if the leader crashes after committing the log entry but before responding to the client, the client will retry the command with a new leader, causing it to be executed a second time. The solution is for clients to assign unique serial numbers to every command. Then, the state machine tracks the latest serial number processed for each client, along with the associated response. If it receives a command whose serial number has already been executed, it responds immediately without re-executing the request.
Read-only operations can be handled without writing anything into the log. However, with no additional measures, this would run the risk of returning stale data, since the leader responding to the request might have been superseded by a newer leader of which it is unaware. Linearizable reads must not return stale data, and Raft needs two extra precautions to guarantee this without using the log. First, a leader must have the latest information on which entries are committed. The Leader Completeness Property guarantees that a leader has all committed entries, but at the start of its term, it may not know which those are. To find out, it needs to commit an entry from its term. Raft handles this by having each leader commit a blank no-op entry into the log at the start of its term. Second, a leader must check whether it has been deposed before processing a read-only request (its information may be stale if a more recent leader has been elected). Raft handles this by having the leader exchange heartbeat messages with a majority of the cluster before responding to read-only requests. Alternatively, the leader could rely on the heartbeat mechanism to provide a form of lease [9], but this would rely on timing for safety (it assumes bounded clock skew).
8 客户端交互
本章节描述了客户端是怎么和Raft交互的,包括客户端怎么发现集群leader和Raft是怎么支持线性语义的[10]。这些问题适用于所有基于一致性的系统,Raft的解决方案也和其他系统差不多。
Raft的客户端所有的请求都发送给leader。当客户端第一次启动时,它连接到一个随机选择的服务器。如果客户端第一次连接的不是leader,服务器就会拒绝请求并提供它所知道的最新leader的信息(AppendEntries请求包含leader的网络地址)。如果leader崩溃了,客户端请求就会超时;客户端就会再次尝试连接随机选择的服务器。
实现线性语义是我们对Raft的目标(每个操作的执行在调用和响应之间时恰好只有一次)。然而,到目前为止Raft可以执行一条命令多次:比如,leader在提交日志条目后但在响应客户端之前崩溃了,客户端在新leader下重新尝试一条命令,导致它执行了两次。解决方案就是给每个客户端命令分配唯一的序列号。然后,状态机跟踪处理客户端的最新序列号及其相关的响应。如果收到一个已经执行命令的序列号,就立刻响应,而不重新执行这个请求。
只读操作可以不写任何东西到日志进行处理。然而,没有额外的措施,它可能有会返回过时数据的风险,因为响应这个请求的leader可能是一个新的leader而它并不知道这些。线性读取肯定不能返回过时数据,Raft在不适用日志的情况下需要两个额外的预防措施来保证这个。首先,leader必须拥有最新的已提交条目。The Leader Completeness Property保证了leader拥有所有已提交的条目,但在term刚开始时,就不知道他们是什么了。为了找到答案,就需要在它的term里提交一个条目。Raft是通过让每个leader在它term开始时,提交一个空白无操作的条目到日志里来处理的。第二,leader必须检查它是否在处理read-only请求前被罢免了(如果一个更新的leader当选,它的信息可能过时)。Raft通过让leader在响应read-only请求前和集群中的大多数交换心跳来处理的。要不然,leader可以通过心跳机制来提供租赁形式[9],但这依赖于时序安全(它设定有界的时钟偏移)。