分布式系统详解
分布式系统概述
什么是分布式系统
分布式系统是由多台通过网络通信的计算机组成的系统,对外表现为一个统一的整体。其核心目标是:
- 高性能:通过并行处理提升吞吐量
- 高可用:单点故障不影响整体服务
- 可扩展:通过增加节点线性提升能力
- 透明性:用户无需感知底层分布细节
分布式系统的挑战
| 挑战 | 描述 | 典型问题 |
|---|---|---|
| 网络不可靠 | 消息可能丢失、延迟、乱序 | 超时、重试、幂等 |
| 时钟不同步 | 各节点时钟存在偏差 | 事件排序、分布式ID |
| 节点故障 | 进程崩溃、网络分区 | 脑裂、数据不一致 |
| 并发控制 | 多节点同时修改共享资源 | 分布式锁、事务 |
分布式系统八大谬误
- 网络是可靠的
- 延迟为零
- 带宽是无限的
- 网络是安全的
- 拓扑不会变化
- 只有一个管理员
- 传输成本为零
- 网络是同构的
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将分布式一致性问题分解为三个子问题:
- Leader选举:如何选出领导者
- 日志复制:如何将日志同步到所有节点
- 安全性:如何保证状态机正确性
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通过以下机制保证安全性:
- 选举限制:只有日志最新的节点才能当选Leader
- Leader只追加:Leader从不覆盖或删除自己的日志
- 提交规则: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使用的原子广播协议,保证分布式事务的顺序一致性。
两种模式
- 崩溃恢复:Leader崩溃后选举新Leader并同步数据
- 消息广播:正常运行时的事务广播
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 / 事务消息
