【主流分布式算法总结】

文章目录

分布式常见的问题

分布式场景下困扰我们的3个核心问题(CAP):一致性、可用性、分区容错性。

1、一致性(Consistency):无论服务如何拆分,所有实例节点同一时间看到是相同的数据

2、可用性(Availability):不管是否成功,确保每一个请求都能接收到响应

3、分区容错性(Partition Tolerance):系统任意分区后,在网络故障时,仍能操作

而我们的业务中,一般一致性、可用性、分区容错性,三个满足两个,一般算法就是以CA和AP两个选择性,进行选择。

常见的分布式算法

Raft算法

概念

RAFT(Replicated State Machine Approach)算法是一种一致性分布式复制协议,用于在分布式系统中维护一组服务节点的一致状态。它是一种领导者选举算法,适用于解决分布式系统中的数据复制和一致性问题。

1.领导者选举:RAFT将系统中的节点分为三种角色:领导者(leader)、跟随者(follower)和候选者(candidate)。在任何给定的时刻,系统中都只有一个领导者。节点之间通过选举机制选出领导者,领导者负责处理客户端请求,并在系统中推动状态变更。
跟随者(Follower)

Fllower是所有节点的初始状态,内部都会有一个随机超时时间。这个超时时间,规定了在倒计时结束后仍然收不到Leader的心跳,Follower就会转变为Candidate。
候选者(candidate)

Follower在转变为Candidate后,超时时间重置,倒计时结束时就会向其他节点提名自己的实,拉取选票。

如果能获得半数以上(1/2以上,包含自己投给自己的)的选票,则当选为Leader,这个过程就叫做Leader选举。

所以节点最好是单数,避免极端情况下出现一个集群选举出两个Leader的脑裂问题。
领导者(leader)

Raft集群通过Leader与客户端进行交互,Leader不断处理写请求与发送心跳给Follower,Follower在收到Leader的心跳后,其超时时间会重置,即重新开始倒计时。

正常工作期间只有 Leader 和 Follower,且Leader至多只能有一个。

2.任期(Term):系统中的时间被分为一个个任期。每个任期都有一个唯一的标识符,领导者选举是基于这个任期进行的。当一个候选者成为领导者时,它会增加当前任期的编号,并在该任期内保持领导者身份。

3.日志(Log):RAFT将系统状态的变化表示为一系列日志条目。每个日志条目包含一个命令和任期号。这些日志条目按顺序附加到每个节点的日志中,并通过领导者复制到其他节点。

4.选举过程:当没有领导者时,节点会进入选举过程。在选举过程中,节点变为候选者状态,并向其他节点发送选举请求。候选者获得多数票后成为领导者。为了避免竞争和分裂投票,选举中使用随机化的超时机制。

5.日志复制:领导者负责将新的日志条目复制到其他节点。一旦大多数节点确认了日志条目,领导者就可以提交日志,并通知其他节点将其应用到状态机中。

6.安全性和一致性:RAFT确保在系统中只有一个领导者,并且所有节点都按相同的顺序应用相同的日志条目,从而确保了系统的一致性和安全性。

Raft的实现

java 复制代码
public class Node {
    private int nodeId;
    private RaftNode raftNode;

    public Node(int nodeId, RaftNode raftNode) {
        this.nodeId = nodeId;
        this.raftNode = raftNode;
    }

    public int getNodeId() {
        return nodeId;
    }

    public RaftNode getRaftNode() {
        return raftNode;
    }
}
java 复制代码
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class RaftNode implements Runnable {
    private int nodeId;
    private List<Node> cluster;
    private String state;
    private int currentTerm;
    private Integer votedFor;
    private List<String> log;
    private int commitIndex;
    private int lastApplied;
    private Map<Integer, Integer> nextIndex;
    private Map<Integer, Integer> matchIndex;
    private Integer leaderId;
    private final Lock lock;

    public RaftNode(int nodeId, List<Node> cluster) {
        this.nodeId = nodeId;
        this.cluster = cluster;
        this.state = "Follower";
        this.currentTerm = 0;
        this.votedFor = null;
        this.log = new ArrayList<>();
        this.commitIndex = 0;
        this.lastApplied = 0;
        this.nextIndex = new ConcurrentHashMap<>();
        this.matchIndex = new ConcurrentHashMap<>();
        this.leaderId = null;
        this.lock = new ReentrantLock();
    }

    public void start() {
        new Thread(this).start();
    }

    @Override
    public void run() {
        while (true) {
            switch (state) {
                case "Follower":
                    runFollower();
                    break;
                case "Candidate":
                    runCandidate();
                    break;
                case "Leader":
                    runLeader();
                    break;
            }
        }
    }

    private void runFollower() {
        long timeout = (long) (150 + Math.random() * 150);
        long lastHeartbeat = System.currentTimeMillis();
        while ("Follower".equals(state)) {
            if (System.currentTimeMillis() - lastHeartbeat >= timeout) {
                state = "Candidate";
                return;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void runCandidate() {
        currentTerm++;
        votedFor = nodeId;
        int votesReceived = 1;
        for (Node peer : cluster) {
            if (peer.getNodeId() != nodeId && sendRequestVote(peer.getRaftNode())) {
                votesReceived++;
            }
        }

        if (votesReceived > cluster.size() / 2) {
            state = "Leader";
            leaderId = nodeId;
            for (Node peer : cluster) {
                if (peer.getNodeId() != nodeId) {
                    nextIndex.put(peer.getNodeId(), log.size());
                    matchIndex.put(peer.getNodeId(), 0);
                }
            }
        } else {
            state = "Follower";
        }
    }

    private void runLeader() {
        while ("Leader".equals(state)) {
            sendHeartbeats();
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private boolean sendRequestVote(RaftNode peer) {
        peer.lock.lock();
        try {
            if (peer.currentTerm <= currentTerm && (peer.votedFor == null || peer.votedFor == nodeId)) {
                peer.votedFor = nodeId;
                return true;
            }
        } finally {
            peer.lock.unlock();
        }
        return false;
    }

    private void sendHeartbeats() {
        for (Node peer : cluster) {
            if (peer.getNodeId() != nodeId) {
                sendAppendEntries(peer.getRaftNode());
            }
        }
    }

    private void sendAppendEntries(RaftNode peer) {
        peer.lock.lock();
        try {
            if (peer.currentTerm > currentTerm) {
                state = "Follower";
            }
        } finally {
            peer.lock.unlock();
        }
    }
}
java 复制代码
import java.util.*;

public class RaftCluster {
    public static void main(String[] args) {
        List<Node> cluster = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            RaftNode raftNode = new RaftNode(i, cluster);
            Node node = new Node(i, raftNode);
            cluster.add(node);
            raftNode.start();
        }
    }
}

ZAB算法

ZAB(ZooKeeper Atomic Broadcast)算法是分布式系统中用来实现一致性的重要协议。它是Apache ZooKeeper分布式协调服务的核心组件,确保集群中的所有节点在数据上的一致性。以下是对ZAB算法的详细解释。

  1. 目标
    ZAB的主要目标:
    顺序一致性:确保所有事务在所有ZooKeeper服务器上以相同的顺序被应用。
    原子广播:确保每个事务被可靠地传递到集群中的所有节点。
    容错性:在部分节点故障的情况下,仍能保证系统的正确性和可用性。
  2. 工作模式
    ZAB算法有两种主要的工作模式:
    广播模式(Broadcast mode):用于正常操作期间处理客户端请求。
    恢复模式(Recovery mode):用于集群启动或领导者故障后,选举新领导者并同步状态。
  3. 广播模式
    在广播模式下,ZAB的工作流程如下:
    领导者选举:集群启动或领导者故障后,会进行领导者选举。新领导者负责处理客户端请求并广播事务。
    事务处理:
    客户端请求被发送到领导者。
    领导者生成提案(Proposal),将提案广播给所有追随者(Follower)。
    追随者接收提案并将其写入事务日志,然后发送确认(ACK)给领导者。
    当领导者收到大多数追随者的确认后,认为提案已提交(Commit),并将提交信息广播给所有追随者。
    每个节点应用提交的事务到其状态机。
  4. 恢复模式
    在恢复模式下,ZAB的工作流程如下:
    数据同步:确保新领导者和追随者之间的数据一致性。追随者将与领导者进行数据同步,获取最新的事务日志。
    领导者选举:如果没有现任领导者,集群会通过投票机制选举出新的领导者。
    状态同步:新领导者与追随者同步状态,以便进入广播模式继续处理客户端请求。
  5. 容错处理
    ZAB通过以下机制实现容错:
    复制日志:每个事务都被写入多个节点的事务日志,确保在部分节点故障时仍然可以恢复数据。
    多数派确认:事务在大多数节点确认后才被提交,保证系统在多数节点可用的情况下仍然一致。
    持久化存储:事务日志被持久化存储在磁盘上,防止数据因节点重启或崩溃而丢失。

Paxos算法

Paxos是一种广泛使用的分布式一致性算法,由计算机科学家Leslie Lamport提出。它用于在分布式系统中达成一致性,即使某些节点发生故障也能保证系统的一致性。Paxos算法的核心思想是在分布式环境中,通过一系列的消息传递和投票机制,确保多个节点对某个值达成共识。

Paxos算法的基本概念

Paxos算法涉及三个主要角色:

提议者(Proposer):提出提案并争取达成共识。

接受者(Acceptor):对提议者提出的提案进行投票。

学习者(Learner):一旦共识达成,学习最终达成的值。

Paxos算法的基本过程

Paxos算法通常分为两个主要阶段:准备阶段(Prepare Phase)和接受阶段(Accept Phase)。

  1. 准备阶段(Prepare Phase)
    提议者选择一个提案编号n并向大多数接受者发送Prepare(n)请求。
    接受者收到Prepare(n)请求后,如果n大于它已经响应过的所有Prepare请求的编号,则接受该请求,并承诺不再接受编号小于n的提案。同时,接受者会向提议者回复它已经接受的提案中编号最大的提案。
  2. 接受阶段(Accept Phase)
    提议者在收到大多数接受者对Prepare(n)请求的响应后,可以确定一个提案值。如果接受者返回了已经接受的提案,提议者会选取编号最大的那个提案值;否则,可以选择任意值。
    提议者将提案(n, value)发送给大多数接受者,请求他们接受该提案。
    接受者收到提案后,如果提案编号n不小于它已经响应过的所有Prepare请求的编号,则接受该提案,并向提议者回复确认消息。
    达成共识
    当提议者收到大多数接受者对Accept(n, value)请求的确认时,认为提案已经被接受,达成共识。
    学习者通过接受者的消息获知已经达成共识的值。
    Paxos算法的特点
    容错性:Paxos算法能够容忍少量的节点故障,只要大多数节点正常工作,系统就能达成一致性。
    一致性:Paxos算法保证所有参与节点最终会达成一致的决策。
    复杂性:Paxos算法的实现和理解相对复杂,特别是在处理边界情况和优化性能时。
    Paxos的变种
    Paxos算法有许多变种,用于在不同的应用场景中优化性能和简化实现,例如:
    Multi-Paxos:扩展Paxos算法以支持多个提案的连续达成共识,常用于实现分布式日志。
    Fast Paxos:通过减少消息传递的轮数来加速达成共识。
    Cheap Paxos:优化资源使用,降低实现成本。
    应用场景
    Paxos算法广泛应用于需要分布式一致性的系统中,例如分布式数据库、分布式文件系统和分布式协调服务等。
相关推荐
禁默11 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood18 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Jasmine_llq18 分钟前
《 火星人 》
算法·青少年编程·c#
Code哈哈笑20 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528723 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶24 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
闻缺陷则喜何志丹29 分钟前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
zfoo-framework32 分钟前
【jenkins插件】
java