Java深入解析篇三十六之分布式系统详解

分布式系统详解

分布式系统概述

什么是分布式系统

分布式系统是由多台通过网络通信的计算机组成的系统,对外表现为一个统一的整体。其核心目标是:

  • 高性能:通过并行处理提升吞吐量
  • 高可用:单点故障不影响整体服务
  • 可扩展:通过增加节点线性提升能力
  • 透明性:用户无需感知底层分布细节

分布式系统的挑战

挑战 描述 典型问题
网络不可靠 消息可能丢失、延迟、乱序 超时、重试、幂等
时钟不同步 各节点时钟存在偏差 事件排序、分布式ID
节点故障 进程崩溃、网络分区 脑裂、数据不一致
并发控制 多节点同时修改共享资源 分布式锁、事务

分布式系统八大谬误

  1. 网络是可靠的
  2. 延迟为零
  3. 带宽是无限的
  4. 网络是安全的
  5. 拓扑不会变化
  6. 只有一个管理员
  7. 传输成本为零
  8. 网络是同构的

CAP定理

定理内容

CAP定理由Eric Brewer在2000年提出,2002年被证明:在一个分布式系统中,以下三个特性最多只能同时满足两个:

  • C(Consistency)一致性:所有节点在同一时刻看到相同的数据
  • A(Availability)可用性:每个请求都能在合理时间内获得非错误响应
  • P(Partition Tolerance)分区容错性:网络分区发生时系统仍能继续运作

为什么P是必选项

在分布式环境中,网络分区是不可避免的(网络故障、机房断网等),因此P是必须保证的。实际选择是在CP和AP之间权衡:

CP系统(牺牲可用性)

java 复制代码
/**
 * CP系统示例:基于Zookeeper的强一致配置中心
 * 当网络分区时,少数派节点拒绝服务以保证一致性
 */
public class ZookeeperConfigCenter {
    private final CuratorFramework client;
    private static final String CONFIG_PATH = "/config";

    public ZookeeperConfigCenter(String connectString) {
        this.client = CuratorFrameworkFactory.builder()
                .connectString(connectString)
                .sessionTimeoutMs(5000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        client.start();
    }

    /**
     * 写入配置 - 强一致性保证
     * 需要多数节点确认才返回成功
     */
    public void setConfig(String key, String value) throws Exception {
        String path = CONFIG_PATH + "/" + key;
        Stat stat = client.checkExists().forPath(path);
        if (stat == null) {
            client.create().creatingParentsIfNeeded()
                    .withMode(CreateMode.PERSISTENT)
                    .forPath(path, value.getBytes(StandardCharsets.UTF_8));
        } else {
            client.setData().forPath(path, value.getBytes(StandardCharsets.UTF_8));
        }
    }

    /**
     * 读取配置 - 使用sync()确保读到最新数据
     */
    public String getConfig(String key) throws Exception {
        String path = CONFIG_PATH + "/" + key;
        // sync强制从Leader读取,保证线性一致性
        client.sync().forPath(path);
        byte[] data = client.getData().forPath(path);
        return new String(data, StandardCharsets.UTF_8);
    }
}

AP系统(牺牲一致性)

java 复制代码
/**
 * AP系统示例:基于Eureka的服务注册中心
 * 网络分区时各节点仍可读写,但数据可能暂时不一致
 */
public class EurekaServiceRegistry {
    private final Map<String, ServiceInstance> registry = new ConcurrentHashMap<>();
    private final long leaseExpirationMs = 90_000; // 90秒未续约则剔除

    /**
     * 服务注册 - 本地立即生效,异步同步到其他节点
     */
    public void register(ServiceInstance instance) {
        registry.put(instance.getInstanceId(), instance);
        // 异步复制到其他Eureka节点(最终一致)
        asyncReplicateToPeers(instance);
    }

    /**
     * 服务发现 - 即使数据不是最新也返回结果
     */
    public List<ServiceInstance> discover(String serviceName) {
        return registry.values().stream()
                .filter(i -> i.getServiceName().equals(serviceName))
                .filter(i -> !isExpired(i))
                .collect(Collectors.toList());
    }

    private boolean isExpired(ServiceInstance instance) {
        return System.currentTimeMillis() - instance.getLastRenewalTime() > leaseExpirationMs;
    }

    private void asyncReplicateToPeers(ServiceInstance instance) {
        CompletableFuture.runAsync(() -> {
            // 异步复制,失败不影响本地可用性
        });
    }
}

CAP权衡决策表

系统类型 选择 典型代表 适用场景
金融交易 CP Zookeeper, etcd 账户余额、转账
社交Feed AP Cassandra, DynamoDB 时间线、点赞数
配置中心 CP etcd, Consul 集群配置、服务发现
电商商品 AP Eureka, Nacos(AP) 商品列表、搜索
DNS AP 全球DNS系统 域名解析

BASE理论

理论背景

BASE理论是对CAP中AP方案的补充,由eBay架构师Dan Pritchett提出。它是对互联网大规模分布式系统实践的经验总结。

三大要素

1. 基本可用(Basically Available)

系统在出现故障时,保证核心功能可用,允许损失部分非核心功能:

java 复制代码
/**
 * 基本可用示例:电商降级策略
 */
public class DegradationService {
    private final CircuitBreaker circuitBreaker;

    /**
     * 商品详情页 - 核心功能保持可用
     */
    public ProductDetail getProductDetail(Long productId) {
        ProductDetail detail = new ProductDetail();
        // 核心信息:必须可用
        detail.setProduct(productService.getById(productId));
        detail.setPrice(priceService.getPrice(productId));

        // 非核心信息:允许降级
        try {
            detail.setRecommendations(recommendService.getRelated(productId));
        } catch (Exception e) {
            // 推荐服务不可用时返回默认值
            detail.setRecommendations(Collections.emptyList());
        }

        try {
            detail.setReviews(reviewService.getTopReviews(productId, 5));
        } catch (Exception e) {
            // 评论服务不可用时降级
            detail.setReviews(Collections.emptyList());
        }
        return detail;
    }
}

2. 软状态(Soft State)

允许系统存在中间状态,即数据在不同节点间存在暂时的不一致:

java 复制代码
/**
 * 软状态示例:订单状态流转中的中间态
 */
public class OrderStateMachine {
    public enum OrderState {
        CREATED,        // 已创建
        PAYING,         // 支付中(软状态:钱已扣但订单未确认)
        PAID,           // 已支付
        DELIVERING,     // 配送中
        COMPLETED       // 已完成
    }

    /**
     * 支付回调处理 - 存在软状态窗口
     * 用户已付款但订单状态可能暂时还是PAYING
     */
    public void handlePaymentCallback(String orderId, PaymentResult result) {
        if (result.isSuccess()) {
            // 更新订单状态(可能因网络延迟暂时不一致)
            orderRepository.updateStatus(orderId, OrderState.PAID);
            // 异步通知库存、物流等服务
            eventBus.publish(new OrderPaidEvent(orderId));
        }
    }
}

3. 最终一致性(Eventually Consistent)

系统保证在没有新的更新的情况下,数据最终会达到一致:

java 复制代码
/**
 * 最终一致性示例:基于消息队列的数据同步
 */
public class InventorySyncService {
    private final RocketMQTemplate rocketMQTemplate;

    /**
     * 扣减库存后异步同步到其他系统
     */
    @Transactional
    public void deductStock(Long skuId, int quantity) {
        // 1. 本地事务:扣减库存
        int affected = stockMapper.deduct(skuId, quantity);
        if (affected == 0) {
            throw new BusinessException("库存不足");
        }

        // 2. 发送消息,最终同步到搜索、缓存等系统
        StockChangeEvent event = new StockChangeEvent(skuId, quantity,
                System.currentTimeMillis());
        rocketMQTemplate.asyncSend("stock-change-topic", event, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("库存变更消息发送成功: {}", sendResult.getMsgId());
            }

            @Override
            public void onException(Throwable e) {
                // 发送失败进入本地消息表,定时重试
                localMessageTable.save(event);
            }
        });
    }
}

一致性模型

线性一致性(Linearizability)

线性一致性是最强的一致性模型:每个操作看起来像是在某个时间点瞬间完成,且所有节点看到的操作顺序一致。

java 复制代码
/**
 * 线性一致性验证:使用Jepsen风格检测
 * 每个操作都有调用时间和响应时间,线性一致性要求存在一个
 * 合法的全序排列,使得每个操作在其调用和响应时间之间"瞬间"完成
 */
public class LinearizabilityChecker {
    private final List<Operation> history = new CopyOnWriteArrayList<>();

    public record Operation(
            String key,
            String value,
            long invokeTime,
            long responseTime,
            OperationType type
    ) {}

    public enum OperationType { READ, WRITE }

    /**
     * 检查历史记录是否满足线性一致性
     * 使用穷举法(适用于小规模历史)
     */
    public boolean checkLinearizable(List<Operation> ops) {
        return tryPermutations(ops, new ArrayList<>(), new HashMap<>());
    }

    private boolean tryPermutations(List<Operation> remaining,
                                     List<Operation> ordered,
                                     Map<String, String> state) {
        if (remaining.isEmpty()) {
            return true;
        }
        for (int i = 0; i < remaining.size(); i++) {
            Operation op = remaining.get(i);
            // 检查是否可以在此位置线性化该操作
            if (canLinearize(op, state)) {
                Map<String, String> newState = new HashMap<>(state);
                if (op.type() == OperationType.WRITE) {
                    newState.put(op.key(), op.value());
                }
                List<Operation> newRemaining = new ArrayList<>(remaining);
                newRemaining.remove(i);
                if (tryPermutations(newRemaining, ordered, newState)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean canLinearize(Operation op, Map<String, String> state) {
        if (op.type() == OperationType.READ) {
            String expected = state.getOrDefault(op.key(), "null");
            return expected.equals(op.value());
        }
        return true; // 写操作总是可以线性化
    }
}

顺序一致性(Sequential Consistency)

所有节点看到的操作顺序相同,但不要求与实际时间对应:

java 复制代码
/**
 * 顺序一致性示例:所有客户端看到相同的操作顺序
 * 但不要求操作按真实时间排序
 */
public class SequentialConsistencyStore {
    private final AtomicLong sequenceGenerator = new AtomicLong(0);
    private final Map<String, VersionedValue> store = new ConcurrentHashMap<>();

    public record VersionedValue(String value, long sequence) {}

    public void write(String key, String value) {
        long seq = sequenceGenerator.incrementAndGet();
        store.put(key, new VersionedValue(value, seq));
    }

    /**
     * 读取时返回全局序列号,客户端可据此判断顺序
     */
    public VersionedValue read(String key) {
        return store.get(key);
    }
}

最终一致性(Eventual Consistency)

在没有新写入的情况下,所有副本最终收敛到相同状态:

java 复制代码
/**
 * 最终一致性示例:基于版本向量的冲突检测与合并
 */
public class EventualConsistencyStore {
    private final Map<String, VersionedData> localStore = new ConcurrentHashMap<>();

    public static class VersionedData {
        private String value;
        private Map<String, Integer> vectorClock; // 节点ID -> 版本号

        public VersionedData(String value, Map<String, Integer> vectorClock) {
            this.value = value;
            this.vectorClock = vectorClock;
        }
    }

    /**
     * 本地写入:递增本节点版本号
     */
    public void write(String key, String value, String nodeId) {
        localStore.compute(key, (k, existing) -> {
            Map<String, Integer> clock = existing != null
                    ? new HashMap<>(existing.vectorClock)
                    : new HashMap<>();
            clock.merge(nodeId, 1, Integer::sum);
            return new VersionedData(value, clock);
        });
    }

    /**
     * 接收远程同步数据:合并版本向量
     */
    public void merge(String key, VersionedData remote) {
        localStore.compute(key, (k, local) -> {
            if (local == null) return remote;
            int comparison = compareVectorClocks(local.vectorClock, remote.vectorClock);
            if (comparison >= 0) return local;   // 本地更新或相同
            if (comparison < 0) return remote;   // 远程更新
            // 并发冲突:需要应用层解决(如取最新时间戳)
            return resolveConflict(local, remote);
        });
    }

    private int compareVectorClocks(Map<String, Integer> a, Map<String, Integer> b) {
        boolean aGreater = false, bGreater = false;
        Set<String> allKeys = new HashSet<>(a.keySet());
        allKeys.addAll(b.keySet());
        for (String key : allKeys) {
            int va = a.getOrDefault(key, 0);
            int vb = b.getOrDefault(key, 0);
            if (va > vb) aGreater = true;
            if (vb > va) bGreater = true;
        }
        if (aGreater && !bGreater) return 1;
        if (bGreater && !aGreater) return -1;
        return 0; // 并发
    }

    private VersionedData resolveConflict(VersionedData local, VersionedData remote) {
        // Last-Write-Wins策略
        Map<String, Integer> merged = new HashMap<>(local.vectorClock);
        remote.vectorClock.forEach((k, v) -> merged.merge(k, v, Math::max));
        // 简单策略:取字典序较大的值(实际应用中可用CRDT)
        String winner = local.value.compareTo(remote.value) >= 0
                ? local.value : remote.value;
        return new VersionedData(winner, merged);
    }
}

一致性模型对比

模型 强度 性能 典型实现
线性一致性 最强 最低 etcd, Zookeeper(sync)
顺序一致性 Zookeeper(默认读)
因果一致性 COPS, MongoDB
最终一致性 最弱 最高 DynamoDB, Cassandra

Paxos算法

算法角色

  • Proposer(提案者):提出提案,希望提案被选定
  • Acceptor(接受者):对提案进行投票
  • Learner(学习者):学习被选定的提案

两阶段流程

java 复制代码
/**
 * Paxos算法简化实现
 * 展示Prepare-Promise和Accept-Accepted两阶段
 */
public class PaxosNode {
    private final int nodeId;
    private final List<PaxosNode> peers;

    // Acceptor状态
    private int promisedProposalNumber = 0;
    private int acceptedProposalNumber = 0;
    private String acceptedValue = null;

    public PaxosNode(int nodeId, List<PaxosNode> peers) {
        this.nodeId = nodeId;
        this.peers = peers;
    }

    /**
     * Proposer发起提案
     */
    public boolean propose(String value) {
        int proposalNumber = generateProposalNumber();

        // Phase 1: Prepare
        List<PromiseResponse> promises = new ArrayList<>();
        for (PaxosNode peer : peers) {
            PromiseResponse response = peer.handlePrepare(proposalNumber);
            if (response != null) {
                promises.add(response);
            }
        }

        // 需要多数派响应
        if (promises.size() <= peers.size() / 2) {
            return false; // 未获得多数派Promise
        }

        // 选择已接受的最大编号提案的值
        String finalValue = promises.stream()
                .filter(p -> p.acceptedValue != null)
                .max(Comparator.comparingInt(p -> p.acceptedProposalNumber))
                .map(p -> p.acceptedValue)
                .orElse(value);

        // Phase 2: Accept
        int acceptCount = 0;
        for (PaxosNode peer : peers) {
            if (peer.handleAccept(proposalNumber, finalValue)) {
                acceptCount++;
            }
        }

        return acceptCount > peers.size() / 2;
    }

    /**
     * Acceptor处理Prepare请求
     */
    public synchronized PromiseResponse handlePrepare(int proposalNumber) {
        if (proposalNumber > promisedProposalNumber) {
            promisedProposalNumber = proposalNumber;
            return new PromiseResponse(true, acceptedProposalNumber, acceptedValue);
        }
        return null; // 拒绝:已有更高编号的Promise
    }

    /**
     * Acceptor处理Accept请求
     */
    public synchronized boolean handleAccept(int proposalNumber, String value) {
        if (proposalNumber >= promisedProposalNumber) {
            promisedProposalNumber = proposalNumber;
            acceptedProposalNumber = proposalNumber;
            acceptedValue = value;
            return true;
        }
        return false;
    }

    private int generateProposalNumber() {
        // 提案编号 = 时间戳 * 节点数 + 节点ID,保证全局唯一且递增
        return (int) (System.currentTimeMillis() * 10 + nodeId);
    }

    public record PromiseResponse(boolean promised, int acceptedProposalNumber,
                                   String acceptedValue) {}
}

Multi-Paxos优化

Multi-Paxos通过选举Leader来避免每个提案都执行两阶段:

java 复制代码
/**
 * Multi-Paxos: Leader稳定后只需一阶段提交
 */
public class MultiPaxos {
    private volatile boolean isLeader = false;
    private int currentTerm = 0;
    private final Map<Integer, String> log = new ConcurrentHashMap<>();

    /**
     * Leader直接执行Accept阶段(跳过Prepare)
     */
    public boolean proposeAsLeader(int slot, String value) {
        if (!isLeader) {
            throw new IllegalStateException("Not the leader");
        }
        // 已在当前term完成Prepare,直接Accept
        int acceptCount = 0;
        for (PaxosNode peer : peers) {
            if (peer.handleAccept(currentTerm, value)) {
                acceptCount++;
            }
        }
        if (acceptCount > peers.size() / 2) {
            log.put(slot, value);
            return true;
        }
        return false;
    }
}

Raft算法

核心概念

Raft将分布式一致性问题分解为三个子问题:

  1. Leader选举:如何选出领导者
  2. 日志复制:如何将日志同步到所有节点
  3. 安全性:如何保证状态机正确性

Leader选举

java 复制代码
/**
 * Raft Leader选举实现
 */
public class RaftNode {
    public enum NodeState { FOLLOWER, CANDIDATE, LEADER }

    private volatile NodeState state = NodeState.FOLLOWER;
    private volatile int currentTerm = 0;
    private volatile int votedFor = -1;
    private final int nodeId;
    private final List<RaftPeer> peers;
    private final ScheduledExecutorService scheduler;
    private ScheduledFuture<?> electionTimer;

    // 日志相关
    private final List<LogEntry> log = new CopyOnWriteArrayList<>();
    private volatile int commitIndex = 0;
    private volatile int lastApplied = 0;

    // Leader状态
    private final Map<Integer, Integer> nextIndex = new ConcurrentHashMap<>();
    private final Map<Integer, Integer> matchIndex = new ConcurrentHashMap<>();

    private static final int ELECTION_TIMEOUT_MIN = 150;
    private static final int ELECTION_TIMEOUT_MAX = 300;

    public RaftNode(int nodeId, List<RaftPeer> peers) {
        this.nodeId = nodeId;
        this.peers = peers;
        this.scheduler = Executors.newScheduledThreadPool(2);
        resetElectionTimer();
    }

    /**
     * 选举超时,发起选举
     */
    private synchronized void startElection() {
        state = NodeState.CANDIDATE;
        currentTerm++;
        votedFor = nodeId;
        log.info("Node {} starting election for term {}", nodeId, currentTerm);

        final int electionTerm = currentTerm;
        AtomicInteger voteCount = new AtomicInteger(1); // 自己的一票

        for (RaftPeer peer : peers) {
            CompletableFuture.supplyAsync(() ->
                    peer.requestVote(electionTerm, nodeId,
                            getLastLogIndex(), getLastLogTerm())
            ).thenAccept(granted -> {
                if (granted && state == NodeState.CANDIDATE
                        && currentTerm == electionTerm) {
                    if (voteCount.incrementAndGet() > (peers.size() + 1) / 2) {
                        becomeLeader();
                    }
                }
            });
        }
        resetElectionTimer();
    }

    /**
     * 处理投票请求
     */
    public synchronized boolean handleRequestVote(int term, int candidateId,
                                                    int lastLogIdx, int lastLogTerm) {
        if (term < currentTerm) return false;

        if (term > currentTerm) {
            currentTerm = term;
            state = NodeState.FOLLOWER;
            votedFor = -1;
        }

        // 检查日志是否足够新
        boolean logUpToDate = lastLogTerm > getLastLogTerm()
                || (lastLogTerm == getLastLogTerm()
                    && lastLogIdx >= getLastLogIndex());

        if ((votedFor == -1 || votedFor == candidateId) && logUpToDate) {
            votedFor = candidateId;
            resetElectionTimer();
            return true;
        }
        return false;
    }

    /**
     * 成为Leader
     */
    private synchronized void becomeLeader() {
        state = NodeState.LEADER;
        log.info("Node {} became leader for term {}", nodeId, currentTerm);

        // 初始化nextIndex和matchIndex
        for (RaftPeer peer : peers) {
            nextIndex.put(peer.getId(), getLastLogIndex() + 1);
            matchIndex.put(peer.getId(), 0);
        }

        // 取消选举定时器,启动心跳
        if (electionTimer != null) electionTimer.cancel(false);
        scheduler.scheduleAtFixedRate(this::sendHeartbeats, 0, 50,
                TimeUnit.MILLISECONDS);
    }

    private void resetElectionTimer() {
        if (electionTimer != null) electionTimer.cancel(false);
        int timeout = ELECTION_TIMEOUT_MIN +
                ThreadLocalRandom.current().nextInt(
                        ELECTION_TIMEOUT_MAX - ELECTION_TIMEOUT_MIN);
        electionTimer = scheduler.schedule(this::startElection,
                timeout, TimeUnit.MILLISECONDS);
    }

    private int getLastLogIndex() { return log.size(); }
    private int getLastLogTerm() {
        return log.isEmpty() ? 0 : log.get(log.size() - 1).term();
    }
}

日志复制

java 复制代码
/**
 * Raft日志复制
 */
public class RaftLogReplication {
    private final RaftNode node;

    public record LogEntry(int term, String command) {}

    public record AppendEntriesRequest(
            int term, int leaderId, int prevLogIndex, int prevLogTerm,
            List<LogEntry> entries, int leaderCommit
    ) {}

    public record AppendEntriesResponse(int term, boolean success, int matchIndex) {}

    /**
     * Leader发送AppendEntries RPC
     */
    public void sendAppendEntries(RaftPeer peer) {
        int nextIdx = node.getNextIndex(peer.getId());
        int prevLogIndex = nextIdx - 1;
        int prevLogTerm = node.getLogTerm(prevLogIndex);

        List<LogEntry> entries = node.getLogEntries(nextIdx);

        AppendEntriesRequest request = new AppendEntriesRequest(
                node.getCurrentTerm(), node.getNodeId(),
                prevLogIndex, prevLogTerm, entries, node.getCommitIndex()
        );

        AppendEntriesResponse response = peer.appendEntries(request);

        if (response.success()) {
            node.setNextIndex(peer.getId(), nextIdx + entries.size());
            node.setMatchIndex(peer.getId(), response.matchIndex());
            advanceCommitIndex();
        } else {
            // 日志不匹配,回退重试
            node.decrementNextIndex(peer.getId());
        }
    }

    /**
     * Follower处理AppendEntries
     */
    public AppendEntriesResponse handleAppendEntries(AppendEntriesRequest req) {
        if (req.term() < node.getCurrentTerm()) {
            return new AppendEntriesResponse(node.getCurrentTerm(), false, 0);
        }

        node.resetElectionTimer();
        node.setState(RaftNode.NodeState.FOLLOWER);

        // 检查prevLog是否匹配
        if (req.prevLogIndex() > 0) {
            if (req.prevLogIndex() > node.getLastLogIndex()
                    || node.getLogTerm(req.prevLogIndex()) != req.prevLogTerm()) {
                return new AppendEntriesResponse(node.getCurrentTerm(), false, 0);
            }
        }

        // 追加/覆盖日志
        for (int i = 0; i < req.entries().size(); i++) {
            int index = req.prevLogIndex() + 1 + i;
            LogEntry entry = req.entries().get(i);
            if (index <= node.getLastLogIndex()) {
                if (node.getLogTerm(index) != entry.term()) {
                    node.truncateLog(index); // 冲突,截断
                } else {
                    continue; // 已存在且一致
                }
            }
            node.appendLog(entry);
        }

        // 更新commitIndex
        if (req.leaderCommit() > node.getCommitIndex()) {
            node.setCommitIndex(Math.min(req.leaderCommit(),
                    node.getLastLogIndex()));
            node.applyCommittedEntries();
        }

        return new AppendEntriesResponse(node.getCurrentTerm(), true,
                node.getLastLogIndex());
    }

    /**
     * 推进commitIndex:多数派已复制的日志可以提交
     */
    private void advanceCommitIndex() {
        for (int n = node.getLastLogIndex(); n > node.getCommitIndex(); n--) {
            if (node.getLogTerm(n) == node.getCurrentTerm()) {
                int replicatedCount = 1; // Leader自己
                for (RaftPeer peer : node.getPeers()) {
                    if (node.getMatchIndex(peer.getId()) >= n) {
                        replicatedCount++;
                    }
                }
                if (replicatedCount > (node.getPeers().size() + 1) / 2) {
                    node.setCommitIndex(n);
                    node.applyCommittedEntries();
                    break;
                }
            }
        }
    }
}

安全性保证

Raft通过以下机制保证安全性:

  1. 选举限制:只有日志最新的节点才能当选Leader
  2. Leader只追加:Leader从不覆盖或删除自己的日志
  3. 提交规则:Leader只能提交当前term的日志
java 复制代码
/**
 * Raft安全性:集群成员变更(联合共识)
 */
public class RaftMembershipChange {
    private final RaftNode node;

    /**
     * 安全的成员变更:使用联合共识(Joint Consensus)
     * 避免直接变更导致两个不相交的多数派
     */
    public void addMember(RaftPeer newMember) {
        // 1. 创建联合配置 C_old + C_new
        ClusterConfig jointConfig = new ClusterConfig(
                node.getCurrentConfig(),
                node.getCurrentConfig().withMember(newMember)
        );

        // 2. 将联合配置作为日志条目提交
        LogEntry configEntry = new LogEntry(
                node.getCurrentTerm(),
                "config:" + jointConfig.serialize()
        );
        node.proposeLog(configEntry);

        // 3. 联合配置提交后,再提交新配置 C_new
        LogEntry newConfigEntry = new LogEntry(
                node.getCurrentTerm(),
                "config:" + jointConfig.getNewConfig().serialize()
        );
        node.proposeLog(newConfigEntry);
    }
}

ZAB协议(Zookeeper)

协议概述

ZAB(Zookeeper Atomic Broadcast)是Zookeeper使用的原子广播协议,保证分布式事务的顺序一致性。

两种模式

  1. 崩溃恢复:Leader崩溃后选举新Leader并同步数据
  2. 消息广播:正常运行时的事务广播
java 复制代码
/**
 * ZAB协议简化实现:消息广播模式
 */
public class ZabProtocol {
    public enum ZabState { LOOKING, FOLLOWING, LEADING, OBSERVING }

    private volatile ZabState state = ZabState.LOOKING;
    private long zxid = 0; // 事务ID: 高32位=epoch, 低32位=counter
    private final int serverId;

    /**
     * Leader广播事务提案
     */
    public void broadcastProposal(Transaction txn) {
        long proposalZxid = generateZxid();
        Proposal proposal = new Proposal(proposalZxid, txn);

        // 向所有Follower发送PROPOSE
        int ackCount = 1; // Leader自己
        for (FollowerHandler follower : followers) {
            follower.sendProposal(proposal);
        }

        // 等待多数派ACK
        // 收到ACK后发送COMMIT
    }

    /**
     * Follower处理提案
     */
    public void handleProposal(Proposal proposal) {
        // 检查zxid连续性
        if (proposal.zxid != this.zxid + 1) {
            // 需要与Leader同步
            syncWithLeader();
            return;
        }

        // 写入本地事务日志
        writeToTransactionLog(proposal);
        this.zxid = proposal.zxid;

        // 发送ACK给Leader
        sendAckToLeader(proposal.zxid);
    }

    /**
     * Leader选举(FastLeaderElection)
     * 优先选择zxid最大的节点,其次选择serverId最大的
     */
    public Vote electLeader(List<Vote> votes) {
        return votes.stream()
                .max(Comparator
                        .comparingLong(Vote::getZxid)
                        .thenComparingInt(Vote::getServerId))
                .orElseThrow();
    }

    private long generateZxid() {
        long epoch = zxid >> 32;
        long counter = (zxid & 0xFFFFFFFFL) + 1;
        return (epoch << 32) | counter;
    }

    public record Proposal(long zxid, Transaction transaction) {}
    public record Vote(int serverId, long zxid) {}
}

ZAB与Raft对比

特性 ZAB Raft
设计目标 主备复制 状态机复制
Leader选举 基于zxid(数据最新) 基于日志最新
日志提交 多数ACK后广播COMMIT 多数复制后推进commitIndex
成员变更 需要重启集群(旧版) 联合共识
应用场景 Zookeeper专用 通用(etcd, TiKV)

分布式ID生成

UUID方案

java 复制代码
/**
 * UUID方案:简单但无序
 */
public class UuidGenerator {
    /**
     * 标准UUID v4(随机)
     */
    public String generateV4() {
        return UUID.randomUUID().toString();
    }

    /**
     * 有序UUID(基于时间,类似UUIDv7)
     * 解决UUID无序导致B+Tree索引性能差的问题
     */
    public String generateOrdered() {
        long timestamp = System.currentTimeMillis();
        byte[] uuidBytes = new byte[16];

        // 前48位:毫秒时间戳
        uuidBytes[0] = (byte) (timestamp >> 40);
        uuidBytes[1] = (byte) (timestamp >> 32);
        uuidBytes[2] = (byte) (timestamp >> 24);
        uuidBytes[3] = (byte) (timestamp >> 16);
        uuidBytes[4] = (byte) (timestamp >> 8);
        uuidBytes[5] = (byte) timestamp;

        // 后80位:随机数
        ThreadLocalRandom.current().nextBytes(
                Arrays.copyOfRange(uuidBytes, 6, 16));

        // 设置版本号和变体
        uuidBytes[6] = (byte) ((uuidBytes[6] & 0x0F) | 0x70); // version 7
        uuidBytes[8] = (byte) ((uuidBytes[8] & 0x3F) | 0x80); // variant

        return bytesToUuid(uuidBytes);
    }

    private String bytesToUuid(byte[] bytes) {
        StringBuilder sb = new StringBuilder(36);
        for (int i = 0; i < 16; i++) {
            if (i == 4 || i == 6 || i == 8 || i == 10) sb.append('-');
            sb.append(String.format("%02x", bytes[i]));
        }
        return sb.toString();
    }
}

Snowflake算法

java 复制代码
/**
 * Twitter Snowflake算法实现
 * 结构: 0 | 41位时间戳 | 10位机器ID | 12位序列号
 * - 1位符号位(固定0)
 * - 41位时间戳:可用约69年
 * - 10位机器ID:最多1024个节点(可拆分为5位数据中心+5位机器)
 * - 12位序列号:每毫秒每节点可生成4096个ID
 */
public class SnowflakeIdGenerator {
    private static final long EPOCH = 1704067200000L; // 2024-01-01 00:00:00

    private static final long WORKER_ID_BITS = 5L;
    private static final long DATACENTER_ID_BITS = 5L;
    private static final long SEQUENCE_BITS = 12L;

    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);       // 31
    private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS); // 31
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);          // 4095

    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;                 // 12
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; // 17
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS
            + DATACENTER_ID_BITS; // 22

    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException("Worker ID超出范围");
        }
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException("Datacenter ID超出范围");
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();

        // 时钟回拨处理
        if (timestamp < lastTimestamp) {
            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                // 回拨5ms以内:等待
                try {
                    wait(offset << 1);
                    timestamp = System.currentTimeMillis();
                    if (timestamp < lastTimestamp) {
                        throw new RuntimeException("时钟回拨,拒绝生成ID");
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException(e);
                }
            } else {
                throw new RuntimeException(
                        "时钟回拨超过5ms,拒绝生成ID,回拨: " + offset + "ms");
            }
        }

        if (timestamp == lastTimestamp) {
            // 同一毫秒内,序列号递增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0) {
                // 序列号溢出,等待下一毫秒
                timestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - EPOCH) << TIMESTAMP_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }

    private long waitNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * 解析Snowflake ID
     */
    public static IdInfo parseId(long id) {
        long timestamp = (id >> TIMESTAMP_SHIFT) + EPOCH;
        long dcId = (id >> DATACENTER_ID_SHIFT) & MAX_DATACENTER_ID;
        long wkId = (id >> WORKER_ID_SHIFT) & MAX_WORKER_ID;
        long seq = id & MAX_SEQUENCE;
        return new IdInfo(timestamp, dcId, wkId, seq);
    }

    public record IdInfo(long timestamp, long datacenterId,
                          long workerId, long sequence) {}
}

号段模式(Leaf-Segment)

java 复制代码
/**
 * 美团Leaf号段模式:双Buffer优化
 * 从数据库批量获取ID段,避免每次生成都访问DB
 */
public class LeafSegmentIdGenerator {
    private final IdSegmentDao segmentDao;
    private final String bizTag;
    private final int step;       // 每次获取的步长
    private final int minStep;    // 最小步长

    private volatile IdSegment current;
    private volatile IdSegment next;
    private volatile boolean isLoadingNext = false;
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public LeafSegmentIdGenerator(IdSegmentDao segmentDao, String bizTag, int step) {
        this.segmentDao = segmentDao;
        this.bizTag = bizTag;
        this.step = step;
        this.minStep = step;
        // 初始化加载第一个号段
        this.current = loadSegment();
    }

    public long nextId() {
        lock.readLock().lock();
        try {
            long id = current.nextId();

            // 当前号段使用超过10%,异步加载下一个号段
            if (!isLoadingNext && current.usedPercent() > 0.1) {
                isLoadingNext = true;
                CompletableFuture.runAsync(() -> {
                    try {
                        next = loadSegment();
                    } finally {
                        isLoadingNext = false;
                    }
                });
            }

            // 当前号段用完,切换到下一个
            if (id == -1) {
                lock.readLock().unlock();
                lock.writeLock().lock();
                try {
                    if (current.isExhausted()) {
                        if (next != null) {
                            current = next;
                            next = null;
                        } else {
                            current = loadSegment(); // 同步加载
                        }
                    }
                    id = current.nextId();
                } finally {
                    lock.writeLock().unlock();
                    lock.readLock().lock();
                }
            }
            return id;
        } finally {
            lock.readLock().unlock();
        }
    }

    private IdSegment loadSegment() {
        // UPDATE id_segment SET max_id = max_id + step WHERE biz_tag = ?
        // 返回新的 [max_id - step, max_id) 区间
        return segmentDao.updateAndGetSegment(bizTag, step);
    }

    public static class IdSegment {
        private final AtomicLong currentId;
        private final long maxId;
        private final long startId;

        public IdSegment(long startId, long maxId) {
            this.startId = startId;
            this.currentId = new AtomicLong(startId);
            this.maxId = maxId;
        }

        public long nextId() {
            long id = currentId.getAndIncrement();
            return id < maxId ? id : -1;
        }

        public boolean isExhausted() {
            return currentId.get() >= maxId;
        }

        public double usedPercent() {
            return (double) (currentId.get() - startId) / (maxId - startId);
        }
    }
}

分布式锁

Redis分布式锁(Redisson实现)

java 复制代码
/**
 * 基于Redisson的分布式锁
 * 特性:可重入、自动续期(看门狗)、公平锁、读写锁
 */
public class RedisDistributedLock {
    private final RedissonClient redisson;

    public RedisDistributedLock(RedissonClient redisson) {
        this.redisson = redisson;
    }

    /**
     * 基本用法:可重入锁 + 看门狗自动续期
     */
    public void executeWithLock(String lockKey, Runnable task) {
        RLock lock = redisson.getLock(lockKey);
        try {
            // 等待10秒获取锁,获取后自动续期(默认30秒,每10秒续一次)
            boolean acquired = lock.tryLock(10, TimeUnit.SECONDS);
            if (!acquired) {
                throw new BusinessException("获取锁超时");
            }
            task.run();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("获取锁被中断", e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 公平锁:按请求顺序获取
     */
    public void executeWithFairLock(String lockKey, Runnable task) {
        RLock fairLock = redisson.getFairLock(lockKey);
        try {
            fairLock.lock();
            task.run();
        } finally {
            if (fairLock.isHeldByCurrentThread()) {
                fairLock.unlock();
            }
        }
    }

    /**
     * 联锁(MultiLock):同时锁定多个资源
     */
    public void transferWithLock(String fromAccount, String toAccount, Runnable task) {
        RLock lock1 = redisson.getLock("account:" + fromAccount);
        RLock lock2 = redisson.getLock("account:" + toAccount);
        RLock multiLock = redisson.getMultiLock(lock1, lock2);
        try {
            multiLock.lock(30, TimeUnit.SECONDS);
            task.run();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            multiLock.unlock();
        }
    }

    /**
     * 读写锁
     */
    public String readWithRWLock(String key) {
        RReadWriteLock rwLock = redisson.getReadWriteLock("rwlock:" + key);
        RLock readLock = rwLock.readLock();
        try {
            readLock.lock();
            return redisson.getBucket("data:" + key).get().toString();
        } finally {
            readLock.unlock();
        }
    }

    public void writeWithRWLock(String key, String value) {
        RReadWriteLock rwLock = redisson.getReadWriteLock("rwlock:" + key);
        RLock writeLock = rwLock.writeLock();
        try {
            writeLock.lock();
            redisson.getBucket("data:" + key).set(value);
        } finally {
            writeLock.unlock();
        }
    }
}

Redis分布式锁底层原理(Lua脚本)

java 复制代码
/**
 * Redis分布式锁的Lua脚本实现原理
 */
public class RedisLockPrinciple {

    /**
     * 加锁Lua脚本(Redisson简化版)
     */
    public static final String LOCK_SCRIPT = """
            -- KEYS[1]: 锁的key
            -- ARGV[1]: 锁的过期时间(ms)
            -- ARGV[2]: 客户端标识(UUID:threadId)
            if (redis.call('exists', KEYS[1]) == 0) then
                redis.call('hincrby', KEYS[1], ARGV[2], 1);
                redis.call('pexpire', KEYS[1], ARGV[1]);
                return nil;
            end;
            if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
                redis.call('hincrby', KEYS[1], ARGV[2], 1);
                redis.call('pexpire', KEYS[1], ARGV[1]);
                return nil;
            end;
            return redis.call('pttl', KEYS[1]);
            """;

    /**
     * 解锁Lua脚本
     */
    public static final String UNLOCK_SCRIPT = """
            -- KEYS[1]: 锁的key
            -- KEYS[2]: 续期频道
            -- ARGV[1]: 解锁消息
            -- ARGV[2]: 锁的过期时间
            -- ARGV[3]: 客户端标识
            if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
                return nil;
            end;
            local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
            if (counter > 0) then
                redis.call('pexpire', KEYS[1], ARGV[2]);
                return 0;
            else
                redis.call('del', KEYS[1]);
                redis.call('publish', KEYS[2], ARGV[1]);
                return 1;
            end;
            """;

    /**
     * 看门狗续期Lua脚本
     */
    public static final String RENEW_SCRIPT = """
            if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
                redis.call('pexpire', KEYS[1], ARGV[1]);
                return 1;
            end;
            return 0;
            """;
}

Zookeeper分布式锁

java 复制代码
/**
 * 基于Zookeeper临时顺序节点的分布式锁
 */
public class ZookeeperDistributedLock {
    private final CuratorFramework client;
    private static final String LOCK_ROOT = "/distributed-locks";

    public ZookeeperDistributedLock(CuratorFramework client) {
        this.client = client;
    }

    /**
     * 使用Curator的InterProcessMutex(推荐)
     */
    public void executeWithMutex(String lockName, Runnable task) throws Exception {
        InterProcessMutex mutex = new InterProcessMutex(client,
                LOCK_ROOT + "/" + lockName);
        try {
            if (mutex.acquire(10, TimeUnit.SECONDS)) {
                task.run();
            } else {
                throw new BusinessException("获取ZK锁超时");
            }
        } finally {
            if (mutex.isAcquiredInThisProcess()) {
                mutex.release();
            }
        }
    }

    /**
     * 手动实现原理:临时顺序节点 + Watcher
     */
    public boolean tryLock(String lockPath, long timeoutMs) throws Exception {
        // 1. 创建临时顺序节点
        String nodePath = client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
                .forPath(lockPath + "/lock-");

        // 2. 获取所有子节点并排序
        List<String> children = client.getChildren().forPath(lockPath);
        Collections.sort(children);

        String currentNode = nodePath.substring(nodePath.lastIndexOf('/') + 1);
        int currentIndex = children.indexOf(currentNode);

        // 3. 如果是最小节点,获取锁成功
        if (currentIndex == 0) {
            return true;
        }

        // 4. 监听前一个节点的删除事件
        String prevNode = children.get(currentIndex - 1);
        CountDownLatch latch = new CountDownLatch(1);

        Stat stat = client.checkExists()
                .usingWatcher((Watcher) event -> {
                    if (event.getType() == Watcher.Event.EventType.NodeDeleted) {
                        latch.countDown();
                    }
                })
                .forPath(lockPath + "/" + prevNode);

        if (stat == null) {
            return true; // 前一个节点已删除
        }

        // 5. 等待前一个节点删除
        return latch.await(timeoutMs, TimeUnit.MILLISECONDS);
    }
}

分布式锁对比

特性 Redis(Redisson) Zookeeper etcd
性能 高(内存操作) 中(磁盘+共识)
可靠性 中(主从切换可能丢锁) 高(ZAB协议) 高(Raft协议)
实现复杂度 低(框架封装好)
锁释放 过期时间/看门狗 会话断开自动释放 租约过期
可重入 支持 支持 需自行实现
适用场景 高并发、容忍极小概率丢锁 强一致要求 云原生环境

分布式事务理论

2PC(两阶段提交)

java 复制代码
/**
 * 两阶段提交协议实现
 */
public class TwoPhaseCommit {
    private final TransactionCoordinator coordinator;
    private final List<TransactionParticipant> participants;

    /**
     * 阶段一:Prepare(询问所有参与者是否可以提交)
     */
    public boolean prepare(String transactionId, Object transactionData) {
        List<Boolean> votes = new ArrayList<>();
        for (TransactionParticipant participant : participants) {
            try {
                boolean canCommit = participant.prepare(transactionId, transactionData);
                votes.add(canCommit);
            } catch (Exception e) {
                votes.add(false);
            }
        }
        return votes.stream().allMatch(v -> v);
    }

    /**
     * 阶段二:Commit/Rollback
     */
    public void commit(String transactionId) {
        boolean allPrepared = prepare(transactionId, null);
        if (allPrepared) {
            for (TransactionParticipant p : participants) {
                p.commit(transactionId);
            }
        } else {
            for (TransactionParticipant p : participants) {
                p.rollback(transactionId);
            }
        }
    }
}

/**
 * 参与者实现示例:数据库操作
 */
public class DatabaseParticipant implements TransactionParticipant {
    private final DataSource dataSource;
    private final Map<String, Connection> txConnections = new ConcurrentHashMap<>();

    @Override
    public boolean prepare(String txId, Object data) {
        try {
            Connection conn = dataSource.getConnection();
            conn.setAutoCommit(false);
            // 执行SQL但不提交
            executeSql(conn, data);
            txConnections.put(txId, conn);
            return true;
        } catch (SQLException e) {
            return false;
        }
    }

    @Override
    public void commit(String txId) {
        Connection conn = txConnections.remove(txId);
        try {
            conn.commit();
            conn.close();
        } catch (SQLException e) {
            throw new RuntimeException("提交失败", e);
        }
    }

    @Override
    public void rollback(String txId) {
        Connection conn = txConnections.remove(txId);
        try {
            conn.rollback();
            conn.close();
        } catch (SQLException e) {
            log.error("回滚失败", e);
        }
    }
}

TCC模式

java 复制代码
/**
 * TCC分布式事务:以电商下单为例
 * Try: 预留资源
 * Confirm: 确认提交
 * Cancel: 取消释放
 */
public interface TccAction {
    boolean prepare(BusinessActionContext context);
    boolean commit(BusinessActionContext context);
    boolean rollback(BusinessActionContext context);
}

/**
 * 库存服务TCC实现
 */
@LocalTCC
public interface InventoryTccService {

    @TwoPhaseBusinessAction(name = "deductStock",
            commitMethod = "confirm", rollbackMethod = "cancel")
    boolean tryDeduct(@BusinessActionContextParameter(paramName = "skuId") Long skuId,
                      @BusinessActionContextParameter(paramName = "quantity") int quantity);

    boolean confirm(BusinessActionContext context);

    boolean cancel(BusinessActionContext context);
}

@Service
public class InventoryTccServiceImpl implements InventoryTccService {
    private final StockMapper stockMapper;

    /**
     * Try阶段:冻结库存(不真正扣减)
     */
    @Override
    @Transactional
    public boolean tryDeduct(Long skuId, int quantity) {
        // available_stock -= quantity, frozen_stock += quantity
        int affected = stockMapper.freeze(skuId, quantity);
        if (affected == 0) {
            throw new BusinessException("库存不足,Try失败");
        }
        return true;
    }

    /**
     * Confirm阶段:真正扣减冻结库存
     */
    @Override
    @Transactional
    public boolean confirm(BusinessActionContext context) {
        Long skuId = Long.valueOf(context.getActionContext("skuId").toString());
        int quantity = Integer.parseInt(context.getActionContext("quantity").toString());
        // frozen_stock -= quantity(库存真正减少)
        stockMapper.confirmDeduct(skuId, quantity);
        return true;
    }

    /**
     * Cancel阶段:释放冻结库存
     * 需处理:幂等、空回滚、悬挂
     */
    @Override
    @Transactional
    public boolean cancel(BusinessActionContext context) {
        Long skuId = Long.valueOf(context.getActionContext("skuId").toString());
        int quantity = Integer.parseInt(context.getActionContext("quantity").toString());

        // 幂等检查:是否已经回滚过
        if (isAlreadyCancelled(context.getXid())) {
            return true;
        }

        // 空回滚检查:Try是否执行过
        if (!isTryExecuted(context.getXid())) {
            markCancelled(context.getXid()); // 标记已回滚,防止悬挂
            return true;
        }

        // frozen_stock -= quantity, available_stock += quantity
        stockMapper.unfreeze(skuId, quantity);
        markCancelled(context.getXid());
        return true;
    }
}

/**
 * 支付服务TCC实现
 */
@Service
public class PaymentTccServiceImpl implements PaymentTccService {
    private final AccountMapper accountMapper;

    @Override
    @Transactional
    public boolean tryPay(Long userId, BigDecimal amount) {
        // 冻结金额:balance -= amount, frozen_amount += amount
        int affected = accountMapper.freezeAmount(userId, amount);
        return affected > 0;
    }

    @Override
    @Transactional
    public boolean confirm(BusinessActionContext context) {
        // frozen_amount -= amount(真正扣款)
        Long userId = (Long) context.getActionContext("userId");
        BigDecimal amount = (BigDecimal) context.getActionContext("amount");
        accountMapper.confirmPay(userId, amount);
        return true;
    }

    @Override
    @Transactional
    public boolean cancel(BusinessActionContext context) {
        // frozen_amount -= amount, balance += amount(解冻)
        Long userId = (Long) context.getActionContext("userId");
        BigDecimal amount = (BigDecimal) context.getActionContext("amount");
        accountMapper.unfreezeAmount(userId, amount);
        return true;
    }
}

Saga模式

java 复制代码
/**
 * Saga编排式(Orchestration):以订单流程为例
 * 每个步骤有对应的补偿操作
 */
public class OrderSagaOrchestrator {
    private final SagaStepRegistry stepRegistry;

    /**
     * 定义订单Saga流程
     */
    public SagaDefinition defineOrderSaga() {
        return SagaDefinition.builder()
                .step("createOrder")
                    .action(ctx -> orderService.create(ctx.get("order")))
                    .compensation(ctx -> orderService.cancel(ctx.get("orderId")))
                .step("deductInventory")
                    .action(ctx -> inventoryService.deduct(
                            ctx.get("skuId"), ctx.get("quantity")))
                    .compensation(ctx -> inventoryService.restore(
                            ctx.get("skuId"), ctx.get("quantity")))
                .step("processPayment")
                    .action(ctx -> paymentService.charge(
                            ctx.get("userId"), ctx.get("amount")))
                    .compensation(ctx -> paymentService.refund(
                            ctx.get("userId"), ctx.get("amount")))
                .step("shipOrder")
                    .action(ctx -> shippingService.createShipment(ctx.get("orderId")))
                    .compensation(ctx -> shippingService.cancelShipment(ctx.get("orderId")))
                .build();
    }

    /**
     * 执行Saga:正向执行,失败则逆序补偿
     */
    public SagaResult execute(SagaDefinition saga, SagaContext context) {
        List<String> completedSteps = new ArrayList<>();

        for (SagaStep step : saga.getSteps()) {
            try {
                step.getAction().accept(context);
                completedSteps.add(step.getName());
                log.info("Saga步骤执行成功: {}", step.getName());
            } catch (Exception e) {
                log.error("Saga步骤执行失败: {}, 开始补偿", step.getName(), e);
                // 逆序执行补偿
                compensate(saga, completedSteps, context);
                return SagaResult.failed(step.getName(), e);
            }
        }
        return SagaResult.success();
    }

    private void compensate(SagaDefinition saga, List<String> completedSteps,
                             SagaContext context) {
        Collections.reverse(completedSteps);
        for (String stepName : completedSteps) {
            try {
                SagaStep step = saga.getStep(stepName);
                step.getCompensation().accept(context);
                log.info("补偿执行成功: {}", stepName);
            } catch (Exception e) {
                // 补偿失败:记录日志,人工介入或重试
                log.error("补偿执行失败: {}, 需要人工介入", stepName, e);
                compensationRetryQueue.add(stepName, context);
            }
        }
    }
}

分布式事务方案对比

方案 一致性 性能 复杂度 适用场景
2PC 强一致 低(阻塞) 数据库层面(XA)
3PC 强一致 理论方案,实践少
TCC 强一致 高(三接口) 资金、库存
Saga 最终一致 长事务、跨服务
事务消息 最终一致 异步解耦场景

一致性哈希

原理与实现

java 复制代码
/**
 * 一致性哈希算法实现(带虚拟节点)
 * 解决数据倾斜问题,节点增减时只影响相邻节点的数据
 */
public class ConsistentHash<T> {
    private final int virtualNodeCount;
    private final TreeMap<Integer, T> ring = new TreeMap<>();
    private final HashFunction hashFunction;

    public ConsistentHash(int virtualNodeCount, Collection<T> nodes) {
        this.virtualNodeCount = virtualNodeCount;
        this.hashFunction = Hashing.murmur3_128();
        nodes.forEach(this::addNode);
    }

    /**
     * 添加节点:在哈希环上放置虚拟节点
     */
    public void addNode(T node) {
        for (int i = 0; i < virtualNodeCount; i++) {
            int hash = hash(node.toString() + "#VN" + i);
            ring.put(hash, node);
        }
    }

    /**
     * 移除节点:移除所有虚拟节点
     */
    public void removeNode(T node) {
        for (int i = 0; i < virtualNodeCount; i++) {
            int hash = hash(node.toString() + "#VN" + i);
            ring.remove(hash);
        }
    }

    /**
     * 获取key对应的节点:顺时针找到第一个虚拟节点
     */
    public T getNode(String key) {
        if (ring.isEmpty()) {
            throw new IllegalStateException("哈希环为空");
        }
        int hash = hash(key);
        // 顺时针查找
        Map.Entry<Integer, T> entry = ring.ceilingEntry(hash);
        if (entry == null) {
            entry = ring.firstEntry(); // 环形:回到起点
        }
        return entry.getValue();
    }

    private int hash(String key) {
        return hashFunction.hashString(key, StandardCharsets.UTF_8).asInt();
    }

    /**
     * 使用示例:分布式缓存路由
     */
    public static void main(String[] args) {
        List<String> cacheNodes = List.of(
                "redis-node-1:6379",
                "redis-node-2:6379",
                "redis-node-3:6379"
        );

        ConsistentHash<String> hashRing = new ConsistentHash<>(150, cacheNodes);

        // 路由请求到对应节点
        String targetNode = hashRing.getNode("user:10001:profile");
        System.out.println("路由到: " + targetNode);

        // 节点扩容:只有约1/N的数据需要迁移
        hashRing.addNode("redis-node-4:6379");
    }
}

分布式限流

令牌桶算法

java 复制代码
/**
 * 令牌桶限流器:允许突发流量
 * 以恒定速率生成令牌,请求需要获取令牌才能通过
 */
public class TokenBucketRateLimiter {
    private final long capacity;        // 桶容量
    private final double refillRate;    // 每秒填充令牌数
    private double currentTokens;
    private long lastRefillTimestamp;
    private final ReentrantLock lock = new ReentrantLock();

    public TokenBucketRateLimiter(long capacity, double refillRate) {
        this.capacity = capacity;
        this.refillRate = refillRate;
        this.currentTokens = capacity;
        this.lastRefillTimestamp = System.nanoTime();
    }

    public boolean tryAcquire() {
        return tryAcquire(1);
    }

    public boolean tryAcquire(int permits) {
        lock.lock();
        try {
            refill();
            if (currentTokens >= permits) {
                currentTokens -= permits;
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    private void refill() {
        long now = System.nanoTime();
        double elapsed = (now - lastRefillTimestamp) / 1_000_000_000.0;
        double tokensToAdd = elapsed * refillRate;
        currentTokens = Math.min(capacity, currentTokens + tokensToAdd);
        lastRefillTimestamp = now;
    }
}

分布式限流(Redis + Lua)

java 复制代码
/**
 * 基于Redis的分布式滑动窗口限流
 */
public class DistributedRateLimiter {
    private final StringRedisTemplate redisTemplate;

    public DistributedRateLimiter(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 滑动窗口限流Lua脚本
     */
    private static final String SLIDING_WINDOW_SCRIPT = """
            -- KEYS[1]: 限流key
            -- ARGV[1]: 窗口大小(ms)
            -- ARGV[2]: 最大请求数
            -- ARGV[3]: 当前时间戳(ms)
            -- ARGV[4]: 唯一请求ID
            local key = KEYS[1]
            local window = tonumber(ARGV[1])
            local limit = tonumber(ARGV[2])
            local now = tonumber(ARGV[3])
            local requestId = ARGV[4]

            -- 移除窗口外的记录
            redis.call('ZREMRANGEBYSCORE', key, 0, now - window)

            -- 当前窗口内的请求数
            local count = redis.call('ZCARD', key)

            if count < limit then
                -- 未超限,添加当前请求
                redis.call('ZADD', key, now, requestId)
                redis.call('PEXPIRE', key, window)
                return 1  -- 允许
            else
                return 0  -- 拒绝
            end
            """;

    /**
     * 判断请求是否被限流
     */
    public boolean isAllowed(String resource, int maxRequests, long windowMs) {
        String key = "rate_limit:" + resource;
        String requestId = UUID.randomUUID().toString();
        long now = System.currentTimeMillis();

        Long result = redisTemplate.execute(
                new DefaultRedisScript<>(SLIDING_WINDOW_SCRIPT, Long.class),
                List.of(key),
                String.valueOf(windowMs),
                String.valueOf(maxRequests),
                String.valueOf(now),
                requestId
        );
        return result != null && result == 1L;
    }

    /**
     * 令牌桶分布式限流Lua脚本
     */
    private static final String TOKEN_BUCKET_SCRIPT = """
            -- KEYS[1]: 令牌桶key
            -- ARGV[1]: 桶容量
            -- ARGV[2]: 每秒填充速率
            -- ARGV[3]: 当前时间戳(秒)
            -- ARGV[4]: 请求令牌数
            local key = KEYS[1]
            local capacity = tonumber(ARGV[1])
            local rate = tonumber(ARGV[2])
            local now = tonumber(ARGV[3])
            local requested = tonumber(ARGV[4])

            local bucket = redis.call('HMGET', key, 'tokens', 'last_time')
            local tokens = tonumber(bucket[1])
            local lastTime = tonumber(bucket[2])

            if tokens == nil then
                tokens = capacity
                lastTime = now
            end

            -- 计算应填充的令牌
            local elapsed = math.max(0, now - lastTime)
            tokens = math.min(capacity, tokens + elapsed * rate)

            local allowed = 0
            if tokens >= requested then
                tokens = tokens - requested
                allowed = 1
            end

            redis.call('HMSET', key, 'tokens', tokens, 'last_time', now)
            redis.call('EXPIRE', key, math.ceil(capacity / rate) + 1)
            return allowed
            """;
}

分布式链路追踪

核心概念

  • Trace:一次完整的请求链路,由唯一TraceId标识
  • Span:链路中的一个操作单元(如一次RPC调用)
  • SpanContext:跨进程传播的上下文(TraceId + SpanId)
java 复制代码
/**
 * 分布式链路追踪:基于OpenTelemetry的集成
 */
public class DistributedTracing {

    /**
     * 手动创建Span(适用于自定义埋点)
     */
    private final Tracer tracer = GlobalOpenTelemetry.getTracer("order-service");

    public OrderResult processOrder(OrderRequest request) {
        Span span = tracer.spanBuilder("processOrder")
                .setSpanKind(SpanKind.SERVER)
                .setAttribute("order.id", request.getOrderId())
                .setAttribute("order.amount", request.getAmount().doubleValue())
                .startSpan();

        try (Scope scope = span.makeCurrent()) {
            // 业务逻辑
            OrderResult result = doProcess(request);
            span.setStatus(StatusCode.OK);
            return result;
        } catch (Exception e) {
            span.setStatus(StatusCode.ERROR, e.getMessage());
            span.recordException(e);
            throw e;
        } finally {
            span.end();
        }
    }

    /**
     * 跨服务传播TraceContext(通过HTTP Header)
     */
    public void callDownstream(String url) {
        Span span = tracer.spanBuilder("callDownstream")
                .setSpanKind(SpanKind.CLIENT)
                .startSpan();

        try (Scope scope = span.makeCurrent()) {
            HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                    .uri(URI.create(url));

            // 注入TraceContext到HTTP Header
            GlobalOpenTelemetry.getPropagators().getTextMapPropagator()
                    .inject(Context.current(), requestBuilder,
                            (builder, key, value) -> {
                                if (builder != null) builder.header(key, value);
                            });

            HttpClient.newHttpClient().send(requestBuilder.build(),
                    HttpResponse.BodyHandlers.ofString());
            span.setStatus(StatusCode.OK);
        } catch (Exception e) {
            span.setStatus(StatusCode.ERROR);
            span.recordException(e);
        } finally {
            span.end();
        }
    }
}

/**
 * Spring Cloud Sleuth / Micrometer Tracing 自动集成
 */
@Configuration
public class TracingConfig {

    @Bean
    public Sampler sampler() {
        // 采样率:生产环境建议0.1(10%)
        return Sampler.traceIdRatioBased(0.1);
    }

    @Bean
    public SpanExporter spanExporter() {
        // 导出到Jaeger
        return OtlpGrpcSpanExporter.builder()
                .setEndpoint("http://jaeger-collector:4317")
                .build();
    }
}

拜占庭将军问题

问题描述

拜占庭将军问题由Leslie Lamport在1982年提出:在存在恶意节点(可能发送错误信息)的分布式系统中,如何让忠诚节点达成一致。

核心结论

  • 如果有 f 个叛徒节点,至少需要 3f + 1 个节点才能达成共识
  • 即:N >= 3f + 1(N为总节点数,f为最大容错数)
java 复制代码
/**
 * 拜占庭容错简化演示:口头消息算法(OM算法)
 * OM(0): 无递归,直接采用Commander的值
 * OM(m): 递归m层,通过多数投票决定
 */
public class ByzantineAgreement {
    private final int totalNodes;
    private final int maxFaulty;

    public ByzantineAgreement(int totalNodes) {
        this.totalNodes = totalNodes;
        this.maxFaulty = (totalNodes - 1) / 3; // 最大容错数
    }

    /**
     * OM(m)算法: lieutenant节点收到的消息处理
     * @param m 递归深度
     * @param receivedValues 从其他节点收到的值
     * @return 最终决定的值
     */
    public String decide(int m, List<String> receivedValues) {
        if (m == 0) {
            // 基础情况:直接采用收到的值(或默认值)
            return receivedValues.isEmpty() ? "RETREAT" : receivedValues.get(0);
        }

        // 对每个收到的值,递归执行OM(m-1)
        List<String> decidedValues = new ArrayList<>();
        for (String value : receivedValues) {
            // 将value发送给其他所有节点,收集他们的反馈
            List<String> subValues = collectSubValues(value, m - 1);
            decidedValues.add(decide(m - 1, subValues));
        }

        // 多数投票
        return majorityVote(decidedValues);
    }

    private String majorityVote(List<String> values) {
        Map<String, Long> counts = values.stream()
                .collect(Collectors.groupingBy(v -> v, Collectors.counting()));
        return counts.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .map(Map.Entry::getKey)
                .orElse("RETREAT");
    }

    /**
     * 验证系统是否能容忍给定的故障节点数
     */
    public boolean canTolerate(int faultyNodes) {
        return totalNodes >= 3 * faultyNodes + 1;
    }

    private List<String> collectSubValues(String value, int depth) {
        // 模拟向其他节点广播并收集响应
        return List.of(value); // 简化
    }
}

区块链中的应用

java 复制代码
/**
 * 实用拜占庭容错(PBFT)简化流程
 * 应用于联盟链(如Hyperledger Fabric)
 */
public class PBFTConsensus {
    public enum Phase { PRE_PREPARE, PREPARE, COMMIT }

    private final int totalNodes;
    private final int faultyThreshold;

    public PBFTConsensus(int totalNodes) {
        this.totalNodes = totalNodes;
        this.faultyThreshold = (totalNodes - 1) / 3;
    }

    /**
     * PBFT三阶段提交
     * 1. Pre-Prepare: Primary广播提案
     * 2. Prepare: 节点广播Prepare消息,收集2f+1个
     * 3. Commit: 节点广播Commit消息,收集2f+1个后执行
     */
    public boolean consensus(Request request) {
        // Phase 1: Pre-Prepare
        PrePrepareMessage proposal = createProposal(request);
        broadcast(proposal);

        // Phase 2: Prepare - 需要2f+1个Prepare消息
        int prepareCount = collectMessages(Phase.PREPARE, proposal.digest());
        if (prepareCount < 2 * faultyThreshold + 1) {
            return false;
        }

        // Phase 3: Commit - 需要2f+1个Commit消息
        broadcast(new CommitMessage(proposal.digest()));
        int commitCount = collectMessages(Phase.COMMIT, proposal.digest());
        if (commitCount < 2 * faultyThreshold + 1) {
            return false;
        }

        // 执行请求
        execute(request);
        return true;
    }

    private PrePrepareMessage createProposal(Request request) {
        return new PrePrepareMessage(
                currentView,
                sequenceNumber++,
                hash(request)
        );
    }

    private int collectMessages(Phase phase, String digest) {
        // 收集并验证消息(检查签名、序号等)
        return 0; // 简化
    }

    private void broadcast(Object message) { /* 广播到所有节点 */ }
    private void execute(Request request) { /* 执行请求 */ }
    private String hash(Request request) { return ""; }

    private int currentView = 0;
    private int sequenceNumber = 0;

    record PrePrepareMessage(int view, int seq, String digest) {}
    record CommitMessage(String digest) {}
    record Request(String data) {}
}

最佳实践

幂等性设计

java 复制代码
/**
 * 通用幂等性保障方案
 */
public class IdempotencyService {
    private final StringRedisTemplate redisTemplate;
    private static final long IDEMPOTENT_KEY_TTL = 24; // 小时

    /**
     * 基于唯一请求ID的幂等检查
     */
    public boolean checkAndMark(String requestId) {
        String key = "idempotent:" + requestId;
        Boolean success = redisTemplate.opsForValue()
                .setIfAbsent(key, "1", IDEMPOTENT_KEY_TTL, TimeUnit.HOURS);
        return Boolean.TRUE.equals(success);
    }

    /**
     * 基于数据库唯一索引的幂等(适用于事务场景)
     */
    @Transactional
    public void idempotentInsert(String bizId, Object data) {
        try {
            // 利用唯一索引防止重复插入
            idempotentRecordMapper.insert(new IdempotentRecord(bizId, "PROCESSING"));
        } catch (DuplicateKeyException e) {
            // 已存在,说明是重复请求
            IdempotentRecord record = idempotentRecordMapper.selectByBizId(bizId);
            if ("SUCCESS".equals(record.getStatus())) {
                return; // 已成功处理,直接返回
            }
            throw new BusinessException("请求正在处理中,请勿重复提交");
        }

        try {
            // 执行业务逻辑
            doBusiness(data);
            idempotentRecordMapper.updateStatus(bizId, "SUCCESS");
        } catch (Exception e) {
            idempotentRecordMapper.updateStatus(bizId, "FAILED");
            throw e;
        }
    }
}

超时与重试策略

java 复制代码
/**
 * 指数退避重试 + 超时控制
 */
public class RetryableService {
    private final int maxRetries;
    private final long baseDelayMs;
    private final long maxDelayMs;

    public RetryableService(int maxRetries, long baseDelayMs, long maxDelayMs) {
        this.maxRetries = maxRetries;
        this.baseDelayMs = baseDelayMs;
        this.maxDelayMs = maxDelayMs;
    }

    /**
     * 带重试的远程调用
     */
    public <T> T executeWithRetry(Supplier<T> action, Predicate<Exception> retryable) {
        Exception lastException = null;
        for (int attempt = 0; attempt <= maxRetries; attempt++) {
            try {
                return action.get();
            } catch (Exception e) {
                lastException = e;
                if (attempt == maxRetries || !retryable.test(e)) {
                    break;
                }
                long delay = calculateDelay(attempt);
                log.warn("第{}次重试,等待{}ms", attempt + 1, delay, e);
                sleep(delay);
            }
        }
        throw new RuntimeException("重试" + maxRetries + "次后仍失败", lastException);
    }

    /**
     * 指数退避 + 抖动
     */
    private long calculateDelay(int attempt) {
        long exponentialDelay = baseDelayMs * (1L << attempt);
        long cappedDelay = Math.min(exponentialDelay, maxDelayMs);
        // 添加随机抖动,避免惊群效应
        long jitter = ThreadLocalRandom.current().nextLong(0, cappedDelay / 2);
        return cappedDelay + jitter;
    }

    private void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

分布式系统设计原则总结

原则 说明 实践
设计失败 假设一切都会失败 超时、重试、降级、熔断
幂等设计 重复执行结果不变 唯一ID、状态机、去重表
最终一致 接受短暂不一致 消息队列、事件驱动
无状态服务 状态外置到存储层 Redis、数据库存储会话
水平扩展 无共享、分片 一致性哈希、分库分表
可观测性 监控、日志、追踪 Metrics + Logging + Tracing
面向失败编程 防御性编程 空值检查、异常处理、兜底

技术选型决策树

复制代码
需要强一致性?
├── 是 → 使用CP系统
│   ├── 配置/协调 → Zookeeper / etcd
│   ├── 分布式事务 → Seata(TCC) / 2PC
│   └── 分布式锁 → Zookeeper / etcd
└── 否 → 使用AP系统(最终一致)
    ├── 服务发现 → Eureka / Nacos(AP模式)
    ├── 缓存 → Redis Cluster
    ├── 消息 → Kafka / RocketMQ
    └── 分布式事务 → Saga / 事务消息
相关推荐
KaKa_大王3 小时前
关于秒杀项目的一些理解
java·学习
ZJU_统一阿萨姆3 小时前
【推理优化进阶】调度器的数学内核:排队论、SLO 与在线决策
开发语言·人工智能·语言模型·系统架构·vllm
tomla3 小时前
使用Java VisualVM观察过期对象引用现象
java
M1A13 小时前
Spring Boot YAML 配置读取完全指南:从基础到微服务
java
古法安卓3 小时前
Android-SELinux 策略调试实战:从 AVC 日志到策略修复
android·java·android studio
todoitbo3 小时前
飞算JavaAI的多租户权限隔离实测
java·springboot·ai编程·java开发·飞算javaai·java代码生成
星空3 小时前
Springboot复习
java·spring boot·spring
SomeB1oody3 小时前
【RustyML入门】5.2. 分类指标
开发语言·后端·机器学习·rust·教程
Dicky-_-zhang3 小时前
大模型部署架构:从推理引擎到弹性扩缩容的工程实践
java·jvm