欢迎光临
我们一直在努力

图解Raft算法:大数据分布式系统一致性协议入门教程(超详细)

图解Raft算法:大数据分布式系统一致性协议入门教程(超详细)

关键词:Raft算法;分布式一致性;leader选举;日志复制;安全性;Mermaid图解;入门教程 摘要: 分布式系统中,多台机器要保持数据一致就像“一群人一起做同一件事”——得有个指挥的(leader)、得把任务说清楚(日志)、得保证大家都做完(安全性)。Raft算法把这些问题拆成3个简单模块,用“选班长”“传纸条”“查作业”的生活场景类比,让复杂的一致性问题变得像小学生过家家一样好懂。本文用超详细的图解、代码模拟和生活比喻,帮你彻底搞懂Raft的核心逻辑,甚至能自己写个简单的Raft集群!

一、背景介绍:为什么需要Raft?

1.1 问题场景:分布式系统的“混乱之源”

假设你有一个大数据系统,用3台服务器(节点)存用户的订单数据。如果没有一致性协议:

  • 节点故障:比如服务器A宕机,服务器B和C的数据可能不一样,用户查订单时会得到错误结果;
  • 网络延迟:服务器A给B发了“新增订单”的命令,但B没收到,导致A和B的数据不一致;
  • 脑裂:如果网络分裂成两部分,每部分都选了自己的“ leader ”,会导致数据彻底混乱。

Raft的作用:就像给分布式系统找了个“仲裁者”,保证不管发生什么故障,所有节点的数据始终一致(比如所有服务器的订单数据都一样)。

1.2 预期读者 & 文档结构

  • 预期读者:刚接触分布式系统的程序员、想搞懂一致性协议的学生、对“Raft为什么能解决问题”好奇的人(不需要懂Paxos);
  • 文档结构:用“生活场景→核心概念→图解原理→代码模拟→实战应用”的顺序,从“感性认识”到“理性实现”逐步推进;
  • 术语表(提前记下来,后面会反复用):
    • 节点(Node):分布式系统中的每台机器(类比“班级里的每个同学”);
    • 集群(Cluster):多台节点组成的系统(类比“一个班级”);
    • 一致性(Consistency):所有节点的数据完全相同(类比“所有同学的作业都一样”);
    • leader:集群的“指挥者”(类比“班长”),负责分配任务、协调一致;
    • follower:集群的“执行者”(类比“学生”),服从leader的指挥;
    • candidate:正在竞选leader的节点(类比“想当班长的学生”);
    • 日志(Log):节点存储的“任务清单”(类比“作业纸条”),每个条目是客户端的命令(比如“新增订单”);
    • 任期(Term):集群的“学期”(类比“班级的学期”),每个任期只能有一个leader,任期号递增。

二、核心概念:用“班级过家家”理解Raft的3大模块

2.1 故事引入:班级里的“一致性问题”

想象一个小学班级,老师布置了一个任务:所有人一起画一幅画,要求每个人的画完全一样。怎么保证?

  • 第一步:选班长(leader选举):得有个人负责传达老师的命令,不然大家各自为政;
  • 第二步:传纸条(日志复制):班长把老师的命令写在纸条上,发给每个同学,大家都要把纸条贴在自己的笔记本上;
  • 第三步:查作业(安全性):班长要确认每个同学都收到了纸条,并且画对了,才能告诉老师“完成了”。

Raft算法的核心逻辑,其实就是这三个步骤的“分布式版”!

2.2 核心概念1:leader选举——怎么选“班长”?

(1)生活比喻:班级选班长

每个同学(follower)都有一个“耐心计时器”(比如10秒):如果超过10秒没收到班长的消息(比如班长没说话),就会举手说“我要当班长!”(变成candidate)。

  • 当candidate时,会给每个同学发一张“投票请求”纸条(上面写着“我是 candidate,选我当班长吧!”);
  • 同学收到后,如果还没给别人投票(voted_for为空),就会回一张“同意”纸条;
  • 如果candidate收到超过一半同学的同意(比如3个同学需要2票,5个需要3票),就会成为新班长(leader);
  • 新班长上任后,会每隔几秒给大家发一张“我是班长”的纸条(heartbeat,心跳包),告诉大家“我还在,不用选新班长”。
(2)Raft中的leader选举逻辑
  • 角色转换:
    • follower:默认状态,等待leader的心跳;
    • candidate:follower超时(没收到心跳),发起选举;
    • leader:candidate获得多数票,成为leader。
  • 关键规则:
    • 每个任期(term)只能有一个leader;
    • 投票遵循“先到先得”+“任期号大的优先”(比如上学期的班长不能和这学期的候选人抢);
    • 多数派(n/2+1)才能当选(比如3节点集群需要2票,5节点需要3票),防止“分裂”(比如两个candidate各得1票,没人当选)。
(3)Mermaid图解:leader选举流程

#mermaid-svg-lRPH7yvQ9Ulb33GL{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lRPH7yvQ9Ulb33GL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lRPH7yvQ9Ulb33GL .error-icon{fill:#552222;}#mermaid-svg-lRPH7yvQ9Ulb33GL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lRPH7yvQ9Ulb33GL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lRPH7yvQ9Ulb33GL .marker.cross{stroke:#333333;}#mermaid-svg-lRPH7yvQ9Ulb33GL svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lRPH7yvQ9Ulb33GL p{margin:0;}#mermaid-svg-lRPH7yvQ9Ulb33GL .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-lRPH7yvQ9Ulb33GL .cluster-label text{fill:#333;}#mermaid-svg-lRPH7yvQ9Ulb33GL .cluster-label span{color:#333;}#mermaid-svg-lRPH7yvQ9Ulb33GL .cluster-label span p{background-color:transparent;}#mermaid-svg-lRPH7yvQ9Ulb33GL .label text,#mermaid-svg-lRPH7yvQ9Ulb33GL span{fill:#333;color:#333;}#mermaid-svg-lRPH7yvQ9Ulb33GL .node rect,#mermaid-svg-lRPH7yvQ9Ulb33GL .node circle,#mermaid-svg-lRPH7yvQ9Ulb33GL .node ellipse,#mermaid-svg-lRPH7yvQ9Ulb33GL .node polygon,#mermaid-svg-lRPH7yvQ9Ulb33GL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lRPH7yvQ9Ulb33GL .rough-node .label text,#mermaid-svg-lRPH7yvQ9Ulb33GL .node .label text,#mermaid-svg-lRPH7yvQ9Ulb33GL .image-shape .label,#mermaid-svg-lRPH7yvQ9Ulb33GL .icon-shape .label{text-anchor:middle;}#mermaid-svg-lRPH7yvQ9Ulb33GL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lRPH7yvQ9Ulb33GL .rough-node .label,#mermaid-svg-lRPH7yvQ9Ulb33GL .node .label,#mermaid-svg-lRPH7yvQ9Ulb33GL .image-shape .label,#mermaid-svg-lRPH7yvQ9Ulb33GL .icon-shape .label{text-align:center;}#mermaid-svg-lRPH7yvQ9Ulb33GL .node.clickable{cursor:pointer;}#mermaid-svg-lRPH7yvQ9Ulb33GL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lRPH7yvQ9Ulb33GL .arrowheadPath{fill:#333333;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lRPH7yvQ9Ulb33GL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lRPH7yvQ9Ulb33GL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lRPH7yvQ9Ulb33GL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lRPH7yvQ9Ulb33GL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lRPH7yvQ9Ulb33GL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lRPH7yvQ9Ulb33GL .cluster text{fill:#333;}#mermaid-svg-lRPH7yvQ9Ulb33GL .cluster span{color:#333;}#mermaid-svg-lRPH7yvQ9Ulb33GL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lRPH7yvQ9Ulb33GL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lRPH7yvQ9Ulb33GL rect.text{fill:none;stroke-width:0;}#mermaid-svg-lRPH7yvQ9Ulb33GL .icon-shape,#mermaid-svg-lRPH7yvQ9Ulb33GL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lRPH7yvQ9Ulb33GL .icon-shape p,#mermaid-svg-lRPH7yvQ9Ulb33GL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lRPH7yvQ9Ulb33GL .icon-shape rect,#mermaid-svg-lRPH7yvQ9Ulb33GL .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lRPH7yvQ9Ulb33GL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lRPH7yvQ9Ulb33GL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lRPH7yvQ9Ulb33GL :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

超时(没收到心跳)

向所有节点发送RequestVote

收到多数票(≥n/2+1)

定期发送Heartbeat

没收到多数票

回到Follower,等待下一次超时

变成Candidate

等待回复

变成Leader

2.3 核心概念2:日志复制——怎么“传纸条”?

(1)生活比喻:班长传任务纸条

老师给班长说“画一朵红太阳”(客户端请求),班长要做3件事:

  • 把“画红太阳”写在自己的笔记本上(追加日志);
  • 把这张纸条发给每个同学(发送AppendEntries请求);
  • 等每个同学回复“我收到了,贴在笔记本上了”(确认复制成功);
  • 告诉大家“可以画了”(提交日志,执行命令)。
  • (2)Raft中的日志复制逻辑
    • 日志结构:每个日志条目包含3个信息:
      • term:产生这个条目的任期(比如“第3学期的任务”);
      • index:条目在日志中的位置(比如“第5页”);
      • command:要执行的命令(比如“INSERT user: Alice”)。
    • 复制流程(见下图):
    • Leader接收客户端命令,追加到自己的日志;
    • Leader向所有Follower发送AppendEntries请求(包含日志条目);
    • Follower收到后,检查日志的一致性(比如“我的第5页是不是和Leader的第5页一样?”),如果一致就追加;
    • Follower回复“成功”,Leader统计回复数量,超过多数就“提交”(标记为已执行);
    • Leader回复客户端“成功”,并通知Follower提交日志。
    (3)Mermaid图解:日志复制流程

    Follower2节点

    Follower1节点

    Leader节点

    客户端

    Follower2节点

    Follower1节点

    Leader节点

    客户端

    #mermaid-svg-IBSxfg9pYGW3MPU6{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IBSxfg9pYGW3MPU6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IBSxfg9pYGW3MPU6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IBSxfg9pYGW3MPU6 .error-icon{fill:#552222;}#mermaid-svg-IBSxfg9pYGW3MPU6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IBSxfg9pYGW3MPU6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IBSxfg9pYGW3MPU6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IBSxfg9pYGW3MPU6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IBSxfg9pYGW3MPU6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IBSxfg9pYGW3MPU6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IBSxfg9pYGW3MPU6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IBSxfg9pYGW3MPU6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IBSxfg9pYGW3MPU6 .marker.cross{stroke:#333333;}#mermaid-svg-IBSxfg9pYGW3MPU6 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IBSxfg9pYGW3MPU6 p{margin:0;}#mermaid-svg-IBSxfg9pYGW3MPU6 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IBSxfg9pYGW3MPU6 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-IBSxfg9pYGW3MPU6 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IBSxfg9pYGW3MPU6 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-IBSxfg9pYGW3MPU6 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-IBSxfg9pYGW3MPU6 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-IBSxfg9pYGW3MPU6 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-IBSxfg9pYGW3MPU6 .sequenceNumber{fill:white;}#mermaid-svg-IBSxfg9pYGW3MPU6 #sequencenumber{fill:#333;}#mermaid-svg-IBSxfg9pYGW3MPU6 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-IBSxfg9pYGW3MPU6 .messageText{fill:#333;stroke:none;}#mermaid-svg-IBSxfg9pYGW3MPU6 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IBSxfg9pYGW3MPU6 .labelText,#mermaid-svg-IBSxfg9pYGW3MPU6 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-IBSxfg9pYGW3MPU6 .loopText,#mermaid-svg-IBSxfg9pYGW3MPU6 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-IBSxfg9pYGW3MPU6 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IBSxfg9pYGW3MPU6 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-IBSxfg9pYGW3MPU6 .noteText,#mermaid-svg-IBSxfg9pYGW3MPU6 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-IBSxfg9pYGW3MPU6 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IBSxfg9pYGW3MPU6 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IBSxfg9pYGW3MPU6 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IBSxfg9pYGW3MPU6 .actorPopupMenu{position:absolute;}#mermaid-svg-IBSxfg9pYGW3MPU6 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-IBSxfg9pYGW3MPU6 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IBSxfg9pYGW3MPU6 .actor-man circle,#mermaid-svg-IBSxfg9pYGW3MPU6 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-IBSxfg9pYGW3MPU6 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    发送命令:画红太阳

    追加日志(index=5, term=3, command=画红太阳)

    发送AppendEntries(index=5, term=3, command=画红太阳)

    发送AppendEntries(index=5, term=3, command=画红太阳)

    回复:成功(已追加)

    回复:成功(已追加)

    统计多数(2/3),提交日志

    回复:成功

    通知提交(index=5)

    通知提交(index=5)

    执行命令(画红太阳)

    执行命令(画红太阳)

    2.4 核心概念3:安全性——怎么“查作业”?

    (1)生活比喻:班长查作业

    如果有个同学没收到纸条(比如请假了),或者纸条被改了(比如有人恶作剧),班长必须保证:

    • 所有同学的笔记本上的纸条都和自己的一样(日志一致性);
    • 没完成的任务不能算“完成”(未提交的日志不能执行);
    • 新班长必须继承旧班长的所有任务(比如新班长上任后,要把旧班长没传完的纸条继续传)。
    (2)Raft中的安全性规则
    • 规则1:Leader必须包含所有已提交的日志条目:如果一个日志条目在某个任期被提交,那么所有更高任期的Leader必须包含这个条目(比如上学期的“画红太阳”任务,这学期的班长必须继续传);
    • 规则2:Follower只能追加Leader的日志:如果Follower的日志和Leader不一致(比如Follower有“画月亮”,而Leader没有),Follower必须删除自己的日志,换成Leader的(比如同学发现自己的纸条和班长的不一样,要撕掉重贴);
    • 规则3:未提交的日志不能执行:只有当Leader确认多数Follower都复制了日志,才能提交(比如班长没收到多数同学的回复,不能说“完成了”)。

    2.5 核心概念关系:像“班级分工”一样配合

    Raft的3个模块不是孤立的,而是像“班级做任务”一样环环相扣:

    • leader选举是前提:没有班长,就没人传纸条;
    • 日志复制是核心:传纸条是完成任务的关键;
    • 安全性是保障:查作业是防止出乱子的底线。

    用公式总结:

    分布式一致性 = 选对leader(避免混乱) + 传对日志(任务明确) + 查对作业(保证结果)

    三、核心原理:用代码模拟Raft的“班级游戏”

    3.1 开发环境搭建

    我们用Python模拟一个简单的Raft集群(3个节点),需要:

    • Python 3.8+(支持异步编程);
    • asyncio库(模拟节点间的网络通信);
    • logging库(打印日志,看节点状态变化)。

    3.2 源代码实现:节点类(Node)

    我们用一个Node类模拟每个节点的状态和行为,核心属性包括:

    • state:节点状态(follower/candidate/leader);
    • current_term:当前任期;
    • voted_for:本次任期投票给了谁;
    • log:日志列表(每个元素是{term: int, command: str});
    • leader_id:当前leader的ID。

    核心方法包括:

    • start():启动节点(异步循环,处理超时和消息);
    • request_vote():处理投票请求(选班长);
    • append_entries():处理日志复制请求(传纸条);
    • heartbeat():leader发送心跳(保持地位)。

    以下是简化的代码示例(完整代码见附录):

    import asyncio
    import logging
    from enum import Enum

    # 节点状态枚举
    class State(Enum):
    FOLLOWER = "follower"
    CANDIDATE = "candidate"
    LEADER = "leader"

    class Node:
    def __init__(self, node_id: int, peers: list):
    self.node_id = node_id # 节点ID
    self.peers = peers # 其他节点的ID列表
    self.state = State.FOLLOWER # 初始状态是follower
    self.current_term = 0 # 初始任期为0
    self.voted_for = None # 本次任期没投票给任何人
    self.log = [] # 日志列表,初始为空
    self.leader_id = None # 当前leader的ID
    self.election_timeout = 5 # 选举超时时间(秒),模拟用小值

    async def start(self):
    """启动节点,处理超时和消息"""
    while True:
    if self.state == State.FOLLOWER:
    await self.follower_loop()
    elif self.state == State.CANDIDATE:
    await self.candidate_loop()
    elif self.state == State.LEADER:
    await self.leader_loop()

    async def follower_loop(self):
    """follower的循环:等待心跳,超时则发起选举"""
    logging.info(f"节点{self.node_id}:当前是follower,任期{self.current_term}")
    try:
    # 等待心跳(模拟:用sleep模拟超时)
    await asyncio.sleep(self.election_timeout)
    # 超时,变成candidate
    self.state = State.CANDIDATE
    self.current_term += 1 # 任期加1
    self.voted_for = self.node_id # 给自己投票
    logging.info(f"节点{self.node_id}:超时,变成candidate,任期{self.current_term}")
    except asyncio.CancelledError:
    # 收到心跳,取消超时
    pass

    async def candidate_loop(self):
    """candidate的循环:发送投票请求,等待结果"""
    # 向所有 peers 发送 RequestVote 请求
    votes = 1 # 自己已经投了一票
    for peer_id in self.peers:
    # 模拟发送请求(实际用网络通信,这里用print代替)
    logging.info(f"节点{self.node_id}:向节点{peer_id}发送RequestVote(任期{self.current_term})")
    # 假设 peer 回复同意(实际需要处理回复)
    votes += 1
    if votes >= 2: # 3节点集群,多数是2票
    self.state = State.LEADER
    self.leader_id = self.node_id
    logging.info(f"节点{self.node_id}:获得多数票({votes}),成为leader,任期{self.current_term}")
    return
    # 没获得多数票,回到follower
    self.state = State.FOLLOWER
    logging.info(f"节点{self.node_id}:没获得多数票,回到follower")

    async def leader_loop(self):
    """leader的循环:发送心跳,处理客户端请求"""
    logging.info(f"节点{self.node_id}:当前是leader,任期{self.current_term}")
    while self.state == State.LEADER:
    # 发送心跳(每1秒一次)
    for peer_id in self.peers:
    logging.info(f"节点{self.node_id}:向节点{peer_id}发送Heartbeat(任期{self.current_term})")
    await asyncio.sleep(1)

    3.3 代码测试:启动3个节点

    我们启动3个节点(ID分别为1、2、3),看它们如何选举leader:

    import asyncio
    import logging

    from node import Node

    async def main():
    # 初始化3个节点,每个节点的peers是另外两个节点的ID
    node1 = Node(node_id=1, peers=[2, 3])
    node2 = Node(node_id=2, peers=[1, 3])
    node3 = Node(node_id=3, peers=[1, 2])

    # 启动所有节点
    tasks = [node1.start(), node2.start(), node3.start()]
    await asyncio.gather(*tasks)

    if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(asctime)s – %(message)s")
    asyncio.run(main())

    3.4 运行结果:看节点状态变化

    运行代码后,你会看到类似以下的日志:

    2024-05-20 10:00:00,000 – 节点1:当前是follower,任期0
    2024-05-20 10:00:00,000 – 节点2:当前是follower,任期0
    2024-05-20 10:00:00,000 – 节点3:当前是follower,任期0
    2024-05-20 10:00:05,000 – 节点1:超时,变成candidate,任期1
    2024-05-20 10:00:05,000 – 节点1:向节点2发送RequestVote(任期1)
    2024-05-20 10:00:05,000 – 节点1:向节点3发送RequestVote(任期1)
    2024-05-20 10:00:05,000 – 节点1:获得多数票(3),成为leader,任期1
    2024-05-20 10:00:05,000 – 节点1:当前是leader,任期1
    2024-05-20 10:00:06,000 – 节点1:向节点2发送Heartbeat(任期1)
    2024-05-20 10:00:06,000 – 节点1:向节点3发送Heartbeat(任期1)

    这说明:

    • 初始时所有节点都是follower;
    • 节点1超时,变成candidate,发送投票请求;
    • 节点2和3(模拟)同意投票,节点1获得多数票,成为leader;
    • leader每隔1秒发送心跳,保持地位。

    四、数学模型:为什么“多数派”能保证一致性?

    4.1 多数派的计算

    Raft中,多数派的定义是:

    多数

    =

    n

    2

    +

    1

    \\text{多数} = \\left\\lfloor \\frac{n}{2} \\right\\rfloor + 1

    多数=2n+1 其中

    n

    n

    n是集群节点数量。比如:

    • n

      =

      3

      n=3

      n=3时,多数是2;

    • n

      =

      5

      n=5

      n=5时,多数是3;

    • n

      =

      7

      n=7

      n=7时,多数是4。

    4.2 为什么多数派能防止“脑裂”?

    假设集群分裂成两个部分(比如网络故障),比如3个节点分裂成[1,2]和[3]:

    • [1,2]部分的多数是2,能选一个leader;
    • [3]部分的多数是2,但只有1个节点,无法选leader;
    • 当网络恢复后,[3]节点会发现[1,2]的leader任期号更大,就会变成follower,从而保证集群一致。

    用公式证明:两个不同的多数派集合必有交集。比如

    n

    =

    5

    n=5

    n=5,两个多数派集合(各3个节点)至少有1个共同节点,所以不会出现两个leader同时存在的情况。

    五、实际应用场景:Raft在大数据中的作用

    Raft不是“玩具算法”,而是大数据系统的“基石”,比如:

    5.1 分布式数据库:TiDB

    TiDB是一个分布式关系型数据库,用Raft保证多个存储节点(TiKV)的数据一致。比如用户执行INSERT命令,TiDB的leader会把命令复制到所有TiKV节点,确认多数节点收到后,才会返回“成功”。

    5.2 集群管理:Kubernetes

    Kubernetes的etcd组件(存储集群状态)用Raft保证多个etcd节点的数据一致。比如当你创建一个Pod,etcd的leader会把这个状态复制到所有节点,防止集群状态混乱。

    5.3 大数据框架:Spark

    Spark的集群管理器(Standalone模式)用Raft保证多个Master节点的数据一致。比如当一个Master节点宕机,另一个Master节点会接管,继续管理集群。

    六、未来趋势与挑战

    6.1 未来趋势

    • Multi-Raft:将一个大集群分成多个小集群(分片),每个分片选一个leader,提高性能(比如TiDB用Multi-Raft管理多个数据分片);
    • Raft与区块链结合:区块链的共识机制(比如比特币的PoW)效率低,Raft的高吞吐量(每秒处理 thousands 次请求)可能成为联盟链的首选;
    • 性能优化:比如减少选举超时时间(比如从秒级降到毫秒级)、优化日志复制的网络传输(比如批量发送日志)。

    6.2 挑战

    • 高延迟网络:跨数据中心的集群(比如北京和上海的节点),网络延迟高,会导致leader选举和日志复制变慢;
    • 节点故障:如果超过一半节点故障(比如5个节点坏了3个),集群无法工作;
    • 日志膨胀:长期运行的集群,日志会越来越大,需要用“快照”(Snapshot)压缩日志(比如把旧日志合并成一个文件)。

    七、总结:Raft的“小学生法则”

    通过本文的学习,你应该掌握了Raft的核心逻辑,用“小学生法则”总结:

  • 选班长要公平:超时的同学可以举手,得多数票才能当;
  • 传纸条要认真:班长要把任务写清楚,每个同学都要贴在笔记本上;
  • 查作业要严格:班长要确认每个同学的纸条都和自己的一样,没完成的不能算完成。
  • 八、思考题:动动小脑筋

  • 如果集群有5个节点,最多能容忍几个节点故障?(答案:2个,因为多数是3,5-2=3);
  • 如果leader在发送日志时宕机,新leader会怎么做?(答案:新leader会继承旧leader的日志,继续复制);
  • 如果你是一个程序员,想实现一个简单的分布式缓存,怎么用Raft保证缓存一致?(提示:缓存的“set”命令作为日志条目,复制到所有节点)。
  • 附录:常见问题与解答

    Q1:Raft和Paxos有什么区别?

    A:Paxos是“学术派”,把一致性问题做成了一个复杂的数学模型,难学难实现;Raft是“工程派”,把问题拆成3个简单模块,易学易实现(比如Raft的代码量是Paxos的1/3)。

    Q2:Raft的性能怎么样?

    A:Raft的性能取决于网络延迟和节点数量。比如5个节点的集群,每秒能处理10000+次请求(比Paxos快),满足大多数大数据场景的需求。

    Q3:怎么学习Raft的进阶知识?

    A:推荐阅读:

    • 《In Search of an Understandable Consensus Algorithm》(Raft官方论文);
    • 《数据密集型应用系统设计》(第9章,详细讲Raft);
    • Raft可视化工具:Raft Scope(可以模拟Raft的运行过程)。

    扩展阅读

    • 论文:《In Search of an Understandable Consensus Algorithm》(Raft作者论文);
    • 书籍:《数据密集型应用系统设计》(第9章,分布式一致性);
    • 视频:《Raft算法讲解》(Bilibili,up主“程序员小灰”);
    • 代码:Raft官方实现(Go语言)。

    结语:Raft算法的魅力在于“把复杂的问题拆成简单的模块”,用生活中的场景类比,让分布式一致性问题变得“触手可及”。希望本文能帮你彻底搞懂Raft,甚至能自己写个简单的Raft集群——毕竟,“选班长”的游戏,谁不会呢? 😊

    赞(0)
    未经允许不得转载:171主机测评 » 图解Raft算法:大数据分布式系统一致性协议入门教程(超详细)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址