1,Raft 协议什么作用
2,详细介绍 Raft 流程
我觉得以下这个流程是比较详细的了,
以下是带上了持久化和日志压缩的细节:
持久化:节点会定期将自己的信息,比如当前任期号、投票信息、日志条目和快照,持久化到硬盘。这是为了确保即使节点崩溃或者重启,也能从硬盘恢复到最新的状态。
恢复:当节点重启时,它首先从硬盘中读取持久化的信息,恢复其任期号、投票信息、日志条目和快照。然后,它会以跟随者的身份开始运行,等待领导者的心跳消息。
日志压缩:随着时间的推移,每个节点的日志可能会变得非常大,因此需要进行压缩。这个过程被称为快照(snapshotting)。在快照过程中,节点将当前的系统状态(状态机的状态)保存下来,并清除所有已被应用且索引值小于这个状态的日志条目。快照也需要持久化存储,以防节点重启时丢失状态。
3,follower 会响应 client 的读写操作吗
4,超过半数的决策机制如何保证 leader 日志的完整性
5,为什么使用 log,不直接写入状态机?
以下内容来自一篇知乎文章《6.5 日志(Raft Log)》,我觉得讲解比gpt4好得多:
1 Raft系统之所以对Log关注这么多的一个原因是,Log是Leader用来对操作排序的一种手段。这对于复制状态机(详见4.2)而言至关重要,对于这些复制状态机来说,所有副本不仅要执行相同的操作,还需要用相同的顺序执行这些操作。而Log与其他很多事物,共同构成了Leader对接收到的客户端操作分配顺序的机制。比如说,我有10个客户端同时向Leader发出请求,Leader必须对这些请求确定一个顺序,并确保所有其他的副本都遵从这个顺序。实际上,Log是一些按照数字编号的槽位(类似一个数组),槽位的数字表示了Leader选择的顺序。
2 Log的另一个用途是,在一个(非Leader,也就是Follower)副本收到了操作,但是还没有执行操作时。该副本需要将这个操作存放在某处,直到收到了Leader发送的新的commit号才执行。所以,对于Raft的Follower来说,Log是用来存放临时操作的地方。Follower收到了这些临时的操作,但是还不确定这些操作是否被commit了。我们将会看到,这些操作可能会被丢弃。
3 Log的另一个用途是用在Leader节点,我(Robert教授)很喜欢这个特性。Leader需要在它的Log中记录操作,因为这些操作可能需要重传给Follower。如果一些Follower由于网络原因或者其他原因短时间离线了或者丢了一些消息,Leader需要能够向Follower重传丢失的Log消息。所以,Leader也需要一个地方来存放客户端请求的拷贝。即使对那些已经commit的请求,为了能够向丢失了相应操作的副本重传,也需要存储在Leader的Log中。
4 所有节点都需要保存Log还有一个原因,就是它可以帮助重启的服务器恢复状态。你可能的确需要一个故障了的服务器在修复后,能重新加入到Raft集群,要不然你就永远少了一个服务器。比如对于一个3节点的集群来说,如果一个节点故障重启之后不能自动加入,那么当前系统只剩2个节点,那将不能再承受任何故障,所以我们需要能够重新并入故障重启了的服务器。对于一个重启的服务器来说,会使用存储在磁盘中的Log。每个Raft节点都需要将Log写入到它的磁盘中,这样它故障重启之后,Log还能保留。而这个Log会被Raft节点用来从头执行其中的操作进而重建故障前的状态,并继续以这个状态运行。所以,Log也会被用来持久化存储操作,服务器可以依赖这些操作来恢复状态。
为什么不直接写呢?
6,如何解决 split vote 的问题
分票(split vote): 这是一个选举问题,在Raft等一致性协议中可能会遇到。当一个集群中没有一个节点能够得到大多数节点的选票,从而无法选举出新的领导者时,我们就称之为发生了分票。这通常是由于网络延迟,节点启动时间不一致或者其他原因导致的。
解决方法通常是:
7,了解 paxos 吗
8,分布式锁和raft的关系
8.1 分布式锁和利用raft机制设置的锁的关系
8.2 raft的leader选举机制来实现分布式锁的一种流程
8.3 raft分布式锁和redlock分布式锁的差异
8.4 为什么raft分布式锁是强一致性的?
8.5 为什么Redlock只能提供最终一致性
8.6 redlock如何导致数据不一致
8.7 为什么节点之间的通信恢复,会发现这种冲突并解决
8.8 那先前两个客户端都持有多数锁而导致已经产生不一致的数据会被修复吗
8.9 为什么票务系统的缓存数据也要保证一致性,最终的一致性由数据库来保证不就好了吗
8.10 redlock有哪些应用场景
8.10 redlock与分布式锁的一些思考
8.11 Raft的leader 选举机制实现分布式锁时如果客户端宕机了,怎么释放锁
8.12 Raft的leader 选举机制实现分布式锁这种机制平常用的多吗
9,怎么使用分布式锁设计一个API(问的应该是怎么针对某一种业务设计一个分布式锁的API)
可以把raft实现分布式锁或者redlock分布式锁的那一套拿出来讲,大概就能让面试官满意。
10,多个请求者,加锁怎么维护状态
11,一致性
11.1 什么是线性一致性和顺序一致性
11.2 可以理解为线性一致性是支持强一致性的,但是顺序一致性是弱一致性吗
11.3 线性一致性可以理解为"所有节点上的这个操作都是要么全部执行成功,要么全部执行失败"吗
11.4 raft怎么实现线性一致性
11.5 因为raft还需要让主结点同步日志给从节点,同步的时候所有节点看到的不一定是同一份最新完整的数据吧
12,如果用同步复制呢,raft有什么问题
13,Quorum 读写这种方法相比 raft 的问题?
14,如果 Quorum 读写也去设置一个 leader ,然后先写 leader,这种情况会有什么问题
15,Etcd用过吗
16,为什么要写消息队列
17,Raft和消息队列是怎么结合起来的
18,哪些中间件用了raft?
19,cap是什么
20,zab算法
21,实现paxos算法的工程还有哪个?raft…
22,
23,为什么zk不用raft?
24,paxos的有哪些缺点
25,ZooKeeper 的ZAB协议介绍一下?
26,如果ZooKeeper中有超过半数的follower已经commit成功,但是有一个follower还没收到commit就挂掉了,重连后数据会不一致吗?
27,如果ZooKeeper中有超过半数的follower已经commit成功,但是这时leader挂掉了,重新选举时会选择之前没有成功commit的follower作为leader吗?
28,分布式系统什么情况下会出现脑裂?
29,对于一个6个节点的ZooKeeper集群,如果因为网络分区变成了两个小集群,各三个节点,请问服务还可用吗(还能进行写操作吗)?
30,raft leader与客户端交互
30.1 处理客户端的读操作时有哪些问题,如何保证读操作的安全性?
其实论文中有提到
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)
翻译:可以在不向日志中写入任何内容的情况下处理只读操作。然而,如果没有额外的措施,这将有返回陈旧数据的风险,因为响应请求的leader可能已经被它不知道的新leader所取代。线性化读取必须不返回陈旧的数据,Raft需要两个额外的预防措施来保证这一点,而不使用日志。
首先,leader必须拥有提交条目的最新信息。Leader完整性属性保证Leader拥有所有已提交的条目,但在其任期开始时,Leader可能不知道哪些是已提交的条目。为了找出答案,它需要提交其任期中的一个条目。Raft通过让每个leader com在其任期开始时在日志中添加一个空白的无操作条目来处理这个问题。其次,leader必须在处理一个只读请求之前检查它是否已经被删除(如果一个最近的leader被选举出来,它的信息可能是陈旧的)。Raft通过让leader在响应只读请求之前与大多数集群交换心跳消息来处理这个问题。或者,leader可以依赖心跳机制来提供一种形式的租赁,但这将依赖于安全的定时(它假设有界时钟倾斜)。
30.2 你能举一个 因为没有采取措施导致读取到旧数据的例子嘛?
答:其实就是30.3的例子
30.3 一个节点可能在什么时候不知道自己已经被新leader取代呢?
30.4 一般什么时候会网络分区
30.5 发生网络分区时,为什么流量还会被打给旧leader
30.6 应对流量打给旧leader的情况,raft会采取什么措施嘛
下面这种其实是应对写请求的流量打给旧leader导致提交错误写请求的方法,处理类似的读请求也是采用这种写的方法,就是前面论文中提到的“写no-op entry”:
30.7 当流量打给旧leader或者follower后,还会重定向到leader嘛
30.8 不能follower或者旧leader直接将请求交给leader节点处理,然后leader直接返回给客户端吗
30.9 为什么在一个新任期的开始,leader处理读操作前需要向从节点同步一个"no-op"条目呢?
因为leader选举的一个依据是日志的新旧程度(涉及到lastLogIndex即日志的最后一项索引值),但是我们一个节点的commitIndex并不是等于最后一项日志的索引值(lastLogIndex),它是依赖于各个从节点的日志复制进度的,而且commitIndex<=lastLogIndex, 所以如果在一个新任期的开始,客户端的读请求想要获取位于(commitIndex, lastLogIndex]之间的日志,那么这个时候可能读取的就是raft系统未提交的数据,这是不合法的,所以这个时候如果我规定在任期开始时,leader会在处理所有读请求之前优先写入一个"no-op"日志条目,主节点的lastLogIndex自增,然后将(nextIndex[i], lastLogIndex] 区间的日志发送给响应的从节点,这样当多数从节点的日志都接收了这个"no-op"日志项时,则leader节点也可以提交这个日志项,commitIndex更新为lastLogIndex,所以当接收到读取(commitIndex, lastLogIndex]日志的时候,获取的也是最新的数据。 相反如果主节点是旧leader,则各个从节点会拒绝接收日志并且将自己的任期号和日志相关的信息告诉这个旧leader,旧leader收到响应后更新任期号转变角色,提交"no-op"失败,可能是因为它已经不再是leader, 这个时候会拒绝客户端的读请求并且将自己获取到的leader信息返回给客户端,客户端刷新本地缓存后再将读请求发送给新leader
31 Raft 的优化,通常是 PreVote,ReadIndex,LeaseRead 这三个。问题来自 Raft。
31.1 预投票
31.2 什么是ReadIndex?
1 gpt的回答
2 raft论文中提到的原因以及详细的解决方案
因为1中提到的raft的日志如果只记录增删改而没有查询,则会导致查询到旧日志,这是因为收到查询请求的leader可能被新的leader取代,如果在旧leader查询期间,新leader收到了新的写请求,则这时就会产生读不一致(读到旧数据)。
为了解决这个问题,
raft论文中有提到read操作时采用两种措施来避免读到旧数据
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
翻译:Raft 需要两个额外的预防措施来在不使用日志的情况下保证这一点。首先,leader 必须拥有有关哪些条目已提交的最新信息。领导者完整性属性保证了领导者拥有所有已提交的条目,但在其任期开始时,它可能不知道这些是哪些。为了找出这些信息,它需要提交一个来自其任期的条目。Raft 通过在每个 leader 的任期开始时在日志中提交一个空的无操作条目来处理这个问题。其次,在处理只读请求之前,leader 必须检查是否已被罢免(如果已经选举出更近期的 leader,则其信息可能是过时的)。Raft 通过在响应只读请求之前,让 leader 与集群的大多数节点交换心跳消息来处理这个问题。
31.3 什么是lease read?
32 Raft 的选举机制说一下。问了 n 次。问题来自 Raft。
33 Raft 哪些 state 需要持久化,为什么。问题来自 Raft。
Q1 为什么要进行持久化
持久化是 RAFT 算法的关键特性之一,用于确保数据在节点发生故障或系统重启等情况下不会丢失。
Q2 RAFT 算法会持久化哪些东西
Q3 提交的日志条目就已经被持久化了,那么未提交的日志条目在系统崩溃时会丢失吗?
Q4 为什么要持久化一个节点的任期号?
问:这个图片中为什么节点A故障了后可能会不断以任期0产生I/O请求呢?
答:其实截图中的第二个方框也回答的很明白了,就是因为网络中rpc请求过多,网络拥堵,
此时新任leader的心跳可能无法到达故障后重连的节点A,所以A会经历好几波网络超时
时间,也就会以任期等于0的情况发起好几次leader选举的投票广播,这任期是一定比
其他节点都小的,所以是无效的投票请求,进一步加重了拥堵(其实即使持久化了也有可能
在网络拥堵情况下以任期号5发送很多网络请求,但是这些请求时合法,是有可能被其他节点
投票的,所以相比任期为0百分百不可能被投票的情况,这种不算浪费网络资源
),同时也浪费了大量的网络资源。
Q5 为什么要持久化votedFor字段?
你能举个因为没有持久化votedFor字段而导致同一个任期出现两个leader的例子吗?
34 Raft applyIndex 和 commitIndex 的关系以及区别,是否需要持久化,持久化有什么好处。问题来自 Raft。
34.1 是否需要持久化commitIndex和applyIndex?
34.2 commitIndex可以不持久化吗
34.3 如果不对commitIndex持久化,宕机重连后如何确认自己的commitIndex呢?
1 对于从节点来说:
首先我们需要知道从节点的commitIndex依赖于leader节点的commitIndex,重连后的从节点会重新加入网络,
收到主结点的日志复制请求后就会更新自己的commitIndex
2 对于leader节点,leader宕机后有可能成为从节点,此时它的commitIndex会被新leader更新,如果重连后它依然成为了
新leader,那么在任期开始时会先向自己的日志中插入一个no-op的条目,随后会向所有从节点发送一次AppendEntries rpc,
然后汇总从节点的响应,计算出各个节点的matchIndex后就相当于知道了 各个节点的日志复制进度,并且可以找出多数节点都复制成功的最大的那一个索引项,它就是commitIndex,然后下次appendEntriesRpc时就可以将这个index发给从节点,从节点收到后更新即可。
35 Raft 成员变更介绍一下,就是单步成员变更和联合成员变更这两个说一下。问题来自 TinyKV。
36 Percolator 边边角角的问题,比如 Percolator 解决了什么问题。相比于 2PC,3PC 有什么好处等等。这块问的频率很高,估计比较有搞头吧。问题来自 TinyKV。
37 Percolator 说完会问 2PC,3PC,它们有啥优缺点。问题来自 TinyKV。
38 raft在处理用户请求超时的时候,如何避免重试的请求被多次应用?
该问题实际上是6.824Lab3 part1中需要解决的问题,下面这个推文很好的描述了解决方案:
raft在处理用户请求超时的时候,如何避免重试的请求被多次应用
39 针对大流量的情况下,如何优化客户端的读请求?
线性一致性读
Raft 的 read 有多种实现:
- Raft log read:每个 read 都有一个对应的 Op log,和 write 一样,都会走一遍一致性协议的流程,会在此 Read Op log 被 Apply 的时候读,那么这个 read Op log 之前的 write Op log 肯定也被 applied 了,那么一定能够被读取到,读到的也一定是最新的
- ReadIndex:我们知道 Raft log read,会有 raft read log 的复制和提交的开销,所以出现了 ReadIndex,read 没有 Op log,但是需要额外的机制保证读到最新的,所以 read 发送给 Leader 的时候,1)它需要确认 read 返回的数据的那个点 ?必须返回最新 committed 的结果,但是一个节点刚当选 Leader 的时候并不知道最新的 committed index,这个时候需要提交一个 Noop log entry 来提交之前的 log entry,然后开始 Read;2)它需要确认当前的 Leader 是不是还是 Leader,因为可能因为网络分区,这个 Leader 已经被孤立了,所以 Leader 在返回 read 之前,先和 Replica-group 的其他成员发送 heartbeat 确定自己 Leader 的身份;通过上述两条保证读到最新被 committed 的数据
- Lease read:主要是通过 lease 机制维护 Leader 的状态,来减少了 ReadIndex 每次 read 发送 heartheat 的开销,详细参考 [3]
- Follower read:先去 Leader 查询最新的 committed index,然后拿着 committed Index 去 Follower read,从而保证能从 Follower 中读到最新的数据,当前 etcd 就实现了 Follower read
这里,面试的时候最好直接回答第四个优化点,因为可以大大减轻服务器的负担,第三点对分布式系统的时钟准确性要求很高,否则可能会出现不一致。
39.1 对于ReadIndex,为什么需要直到commitIndex而不是appliedId,如果一个leader刚刚选举成功,appliedId还没赶上commitIndex怎么办呢?
Raft协议在处理读请求时使用了一种称为"ReadIndex"的技术。这种技术的目的是在不增加日志复制开销的情况下,从领导者节点读取最新的已提交数据。这在大量读操作的场景中很有用,因为它可以减少读操作的延迟,并提高系统的吞吐量。
当领导者收到读请求时,它需要确保自己已经完成了所有已提交但未应用的日志条目。这是因为这些未应用的日志条目可能会改变被读取的数据。为了确保数据的一致性,领导者需要先应用这些日志条目,然后再处理读请求。在Raft协议中,这通过在处理读请求之前,将已提交的索引(commitIndex)推进到最新的位置来实现。
对于你的问题,如果一个领导者刚刚选举成功,那么它的已应用索引(appliedIndex)可能会落后于已提交索引(commitIndex)。在这种情况下,领导者需要先将所有已提交但未应用的日志条目应用到状态机,然后再处理读请求。这就保证了处理读请求时,领导者的状态机已经包含了所有已提交的写变更。
总的来说,为什么需要知道commitIndex而不是appliedIndex,是因为commitIndex代表的是已经被大多数节点确认的,被认为是安全的日志条目,而appliedIndex可能会落后于commitIndex。在处理读请求之前,确保所有已提交的日志条目都已应用,可以防止读取到旧的数据。
39.2 readIndex+lease read机制
虽然 ReadIndex Read 比第一种方法快很多,但我们发现还是有一次心跳包的网络开销,raft论文里提到了一种更激进的方式,也就是leader其实都存在一个’在位期’, 也就是说leader会向follower发送心跳包,当收到大多数节点的回复后即确认了leader身份,并记录下时间戳t1,如果读过raft论文我们会知道,follower会在Election Timeout超时后,才会认为leader挂掉,并发起一次新的选举,也就是说在下一次leader选举肯定发生在t1 + Election Timeout +/- 时钟偏移量之后,因此第二种方法的中,通过发送心跳包确认leader身份这一步就可以省略掉了,在Election Timeout内只需要确认一次即可,相对一种方式,不仅仅不需要写raft日志,连心跳包都省略掉了,可想而知,性能会大大提升,但这里存在一个潜在的问题是,服务器的时间可能会偏移,存在一定的误差,如果偏移过大的话,这种机制就不够安全。
39.3 ReadIndex + Lease Read中的租期什么时候更新,比如接受了写请求?发生了leader更换?还有其他的吗?
39.4 要准确地实现租约,需要解决时钟漂移等问题,这在分布式系统中是非常具有挑战性的。为什么时钟漂移等问题对准确实现租约很重要?(待解决)
39.2 对比readIndex,Follower Read的好处?
Follower Read优点是可以显著减轻领导者的负载,并提高读取性能。缺点是需要额外的通信延迟,因为每个读请求都需要先去询问领导者最新的commitIndex。
39.2.1 read Index对于每一个读请求也都会发送心跳给每一个从节点啊,理论上发送的rpc数都差不多,为什么Follower Read就可以减轻负载?
答:因为读操作涉及到读取内存数据,响应数据给各个客户端,其实从rpc数量上讲,所有参与follower read的从节点响应给客户端数据的rpc请求数等于leader节点的响应rpc数,另外leader节点还需要涉及到内存IO,这本身也是耗费时间的