一、客户端如何找到 Leader
Raft 算法规定客户端将所有申请发送给 Leader。客户端启动的时候,如何晓得哪一个节点是 Leader 呢?具体办法是客户端随机筛选一个服务器进行通信,如果客户端选的服务器不是领导人,那么被筛选的服务器会回绝客户端的申请,并且提供它最近接管到的领导人的信息,即通过收到 Leader 发送的心跳的 RPC 失去 Leader 的网络地址。
还有一种状况,如果领导人曾经解体了,那么客户端的申请就会超时,客户端之后会再次重试随机筛选服务器的过程。
二、如何确保命令只执行 1 次
Raft 算法是可能屡次执行同一条命令的,官网也举了一个例子:
如果领导人在提交了这条日志之后,然而在响应客户端之前解体了,那么客户端会和新的领导人重试这条指令,导致这条命令就被再次执行了,因为日志曾经提交,只是这个响应没有被发送到客户端。
解决方案
客户端对于每一条指令都赋予一个惟一的序列号,状态机跟踪每条指令最新的序列号和相应的响应。如果接管到一条指令,它的序列号曾经被执行了,那么就立刻返回后果,而不从新执行指令。
即由状态机来保障不反复执行,因为这不是 Raft 算法中的内容,而是如何保障幂等性了,行业内也有很多计划,大略原理都是为申请生成惟一 ID,而后服务端判重,这里不详述了。
三、如何 100% 保障不脏读
后面说了所有读、写申请都发送 Leader,那是不是意味着从 Leader 发动读申请的时候就不必做什么解决,能够间接返回了?这里可能有脏读的状况,咱们来举个例子:
以后集群有 5 个节点:A、B、C、D、E
假如以后 Leader 是 A,当初因为网络起因导致网络分区,变成如下:
当初分成 2 个网络:A、B 所在网络与 C、D、E 的网络不通,因为 C、D、E 与 A 所在网络不通,因而 C、D、E 会触发选举,并且节点数为 3,满足集群节点少数的条件,因而能够选举出 Leader;
如果此时客户端有新的申请发到左边的新集群,假如是一个批改 key 为 test 的操作并且提交了,而此时同样一个客户端申请发到 A,读取 key 也为 test,因为左边集群曾经有新操作了,间接从 A 的状态机里返回数据就是旧的了。
针对这个问题,官网给的计划是须要额定 2 个额定的措施来保障:
1、领导人必须有对于被提交日志的最新信息
即在它的任期里必须马上提交一条空白的日志条目,即心跳;
这段话的意思是在一个节点成为 Leader 之前,至多向少数节点发送一次心跳来进行确认日志状况,在没收到心跳响应之前是不能响应客户端的;
2、领导人在解决只读的申请之前必须查看本人是否曾经被破除了
具体实现是 Leader 在响应只读申请之前,先和集群中的大多数节点替换一次心跳信息来解决这个问题,即发送一次心跳的 RPC,收到响应无误之后能力返回给客户端,即每次读申请要和少数成员做一次心跳以确认本人依然是 Leader。
回到下面的案例,因为网络分区了,客户端申请 A 的时候,A 向成员发送心跳时,只有 B 能响应,没有达到少数成员的要求,因而不能响应给客户端。
下面这种状况也叫 ReadIndex,有趣味的同学能够理解下相干实现。
四、一次申请残缺交互
最初以一次残缺的客户端申请来总结整个过程,包含客户端发送申请和 Leader 在什么时候响应;假如集群有 3 个节点:A、B、C,其中 A 以后为 Leader,一次残缺的申请的过程如下:
1、客户端提交到申请至 Leader;
2、Leader 本人先保留日志,留神这里不提交;
3、Leader 将日志同步给 Follower,这里只画了 1 条线,即只同步给 C,实际上是都会同步;
4、Follower 收到日志后保留日志并响应给 Leader;
5、Leader 只有收到一个 Follower 的响应后马上将这条日志提交并利用到状态机中;
6、Leader 利用到状态机实现后就能够返回给客户端了;
上图中后续的流程没有画,即 Follower 在什么时候提交日志,答案是 Leader 在下一次心跳的时候会将最新的 commitIndex 带上,Follower 因而提交日志并利用到状态机中。