使用C++ 20协程实现Raft共识算法

本文介绍了在C++ 20中不使用额外库实现Raft Server共识模块的方法。涵盖Raft算法概述、服务器开发说明及自定义网络库描述。利用C++ 20协程,展示其在复杂编程中的应用,还探讨构建共识模块的挑战与解决方案,并给出开源项目供参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文描述了如何在不使用任何额外库的情况下在c++ 20中实现Raft Server共识模块。文章分为三个主要部分:

  1. Raft算法的全面概述
  2. 关于Raft服务器开发的详细说明
  3. 对基于协程的自定义网络库的描述

该实现利用了C++ 20的强大功能,特别是协同程序,为构建分布式系统的关键组件提供了一种有效而现代的方法。本文会不仅展示了C++ 20协程在复杂编程环境中的实际应用和优点,而且还深入探讨了从头开始构建共识模块(如Raft Server)时遇到的挑战和解决方案。可以参考开源项目:miniraft-cpp 和 coroio 进一步探索和实际应用。

简介

在深入研究Raft算法的复杂性之前,让我们考虑一个现实世界的例子。我们的目标是开发一个网络键值存储(K/V)系统。在C++中,这可以通过使用unordered_map<string, string>轻松实现。然而,在实际应用程序中,对容错存储系统的需求增加了复杂性。一种看似简单的方法可能需要部署三台(或更多)机器,每台机器托管该服务的一个副本。用户可能期望管理数据复制和一致性。然而,这种方法可能导致不可预测的行为。例如,可以使用特定的键更新数据,然后稍后检索旧版本。

用户真正想要的是一个分布式系统,可能分布在多台机器上,运行起来像单主机系统一样流畅。为了满足这一需求,共识模块通常被放置在K/V存储(或任何类似的服务,以下称为“状态机”)的前面。此配置确保与状态机的所有用户交互都通过共识模块路由,而不是直接访问。考虑到这个上下文,现在让我们看看如何实现这样一个共识模块,以Raft算法为例。 

Raft概述

在Raft算法中,有奇数个参与者称为peers。每个peers都有自己的记录日志。有一个同侪领袖,其他人都是追随者。用户将所有请求(读和写)直接发送给leader。当接收到更改状态机的写请求时,leader首先将其记录下来,然后再将其转发给follower,后者也将其记录下来。一旦大多数peers成功响应,leader就认为该条目已提交,将其应用于状态机,并通知用户其成功。

Term是Raft中的一个关键概念,它只会不断发展。当系统发生变化时,例如领导层的变化,Term也会发生变化。Raft中的日志具有特定的结构,每个条目由Term和Payload组成。这个词指的是写最初条目的领导者。Payload表示要对状态机进行的更改。Raft保证具有相同索引和Term的两个条目是相同的。Raft日志不只是附加的,可以被截断。例如,在下面的场景中,leader S1在崩溃之前复制了两个条目。S2率先开始复制条目,S1的log与S2和S3的log不同。因此,S1日志中的最后一个条目将被删除并替换为一个新条目。

Two entries with the same index and term are identical

Raft RPC API

让我们再看一下一下Raft RPC。值得注意的是,Raft API非常简单,只有两个调用。我们将从查看领导人选举API开始。重要的是要注意,Raft确保每学期只能有一个领导者。如果选举失败,也可能出现没有领导人的任期。为了确保只发生一次选举,对peers将其投票保存在名为VotedFor的持久变量中。选举RPC称为RequestVote,它有三个参数:Term、LastLogIndex和LastLogTerm。响应包含Term和votegranting。值得注意的是,每个请求都包含Term,在Raft中,peer只有在它们的Terms兼容的情况下才能有效地通信。

当一个peer发起选举时,它向其他peer发送RequestVote请求并收集他们的投票。如果大多数人的回答都是积极的,这个伙计就晋升为领导者。

现在让我们看一下AppendEntries请求。它接受参数Term、PrevLogIndex、PrevLogTerm和Entries,响应包含Term和Success。如果请求中的Entries字段为空,则充当Heartbeat。

当接收到AppendEntries请求时,follower会检查PrevLogIndex中的Term。如果匹配到PrevLogTerm, follower会在其日志中添加以PrevLogIndex + 1开头的条目(如果存在,则删除在PrevLogIndex之后的条目):

Flow of AppendEntries request being received

如果条件不匹配,则follower返回Success=false。在这种情况下,leader会重新发送请求,并将PrevLogIndex降低1。

当peer接收到RequestVote请求时,它将其LastTerm和LastLogIndex对与最近的日志条目进行比较。如果这对小于或等于请求者的,peer返回votegranting =true。

Raft中的状态转换

Raft的状态转换是这样的。每个peer从Follower状态开始。如果Follower在设定的超时时间内没有收到AppendEntries,则扩展其Term并移动到Candidate状态,从而触发选举。如果赢得选举,对等体可以从Candidate状态移动到Leader状态,如果收到AppendEntries请求,则返回到Follower状态。如果在超时时间内没有转换为Follower或Leader, Candidate也可以恢复为Candidate。如果处于任何状态的peer接收到具有大于其当前状态的Term的RPC请求,则它将移动到Follower状态。

安全提交

现在让我们考虑一个示例,该示例演示了Raft并不像看起来那么简单。我从Diego Ongaro的论文中选取了这个例子。S1在第2项中处于领先地位,它在崩溃之前复制了两个条目。在此之后,S5在第三学期领先,增加了一个条目,然后崩溃了。接下来,S2接管了Term 4的领导权,复制了Term 2的条目,为Term 4添加了自己的条目,然后崩溃了。这导致两种可能的结果:S5重新获得领导地位并截断Term 2中的条目,或者S1重新获得领导地位并提交Term 2中的条目。Term 2中的条目只有在被新leader的后续条目覆盖后才会被安全提交。

How the Raft algorithm operates in a dynamic and often unpredictable set of circumstances

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值