分布式系统核心原理
分布式系统核心原理
分布式系统是大厂后端面试的必考领域,CAP/BASE 理论、分布式事务、服务注册、分布式锁、限流熔断——字节/阿里/腾讯 P6+ 必备知识全覆盖。
一、CAP 理论 + BASE 理论
CAP 三大属性
CAP 定理由 Eric Brewer 于 2000 年提出,核心结论:分布式系统最多只能同时满足以下三点中的两点:
- C(Consistency,一致性):所有节点同一时刻读到相同的数据
- A(Availability,可用性):每个请求都能收到非错误响应(不保证最新)
- P(Partition Tolerance,分区容错性):网络分区时系统仍能运行
由于网络故障在分布式系统中不可避免,P 必须满足,实际只在 CP 和 AP 之间选择。
Zookeeper(CP) vs Eureka(AP) 对比
| 对比维度 | Zookeeper(CP) | Eureka(AP) |
|---|---|---|
| 一致性协议 | ZAB(类 Raft),强一致 | P2P 对等同步,最终一致 |
| 可用性 | Leader 选举期间(约 200ms~30s)拒绝写请求 | 即使部分节点故障仍提供服务列表 |
| 数据准确性 | 高,数据强一致 | 可能返回过期数据(已下线服务) |
| 适用场景 | 配置中心、分布式协调 | 服务注册与发现(可用性优先) |
| 自我保护机制 | 无 | 有(15 分钟内心跳丢失 > 85% 则停止剔除) |
选型结论:服务注册发现通常选 Eureka/Nacos(AP),因为返回稍旧的列表 + 客户端重试容错比整体不可用代价更小。配置管理、分布式锁选 Zookeeper/etcd(CP),数据准确性是首要要求。
BASE 理论
BASE 是对 CAP 中 AP 路线的延伸,互联网系统大规模实践的总结:
- BA(Basically Available,基本可用):故障时允许响应时间延长或降级(如双 11 关闭部分非核心功能)
- S(Soft State,软状态):允许副本之间存在短暂的不一致窗口
- E(Eventually Consistent,最终一致性):经过一段时间同步后,所有副本最终达到一致
BASE 不是"随时不一致",而是有时间上界的收敛保证,配合幂等消费、对账补偿等机制使用。
二、分布式事务方案
2PC / 3PC
2PC(两阶段提交) 分准备阶段和提交阶段,由协调者统一指挥所有参与者。
缺点:
1. 同步阻塞:所有参与者在 Prepare→Commit 期间持有资源锁,性能差
2. 单点故障:协调者宕机导致参与者永久阻塞
3. 脑裂:网络分区时部分节点 Commit,部分节点未收到,数据不一致3PC 在 2PC 基础上增加 CanCommit 阶段并引入超时机制,降低了阻塞概率,但仍存在脑裂风险,工程实践中很少直接使用。
TCC 模式
TCC 将事务拆为三个接口,由业务代码自行控制:
- Try:预留资源(冻结库存 / 冻结余额)
- Confirm:正式扣减(确认消耗预留资源)
- Cancel:回滚预留(解冻之前冻结的资源)
// TCC 账户服务示例(Seata TCC 注解)
@LocalTCC
public interface AccountTccService {
@TwoPhaseBusinessAction(name = "deductBalance",
commitMethod = "confirm",
rollbackMethod = "cancel")
boolean tryDeduct(BusinessActionContext ctx,
@BusinessActionContextParameter("userId") String userId,
@BusinessActionContextParameter("amount") BigDecimal amount);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
@Service
public class AccountTccServiceImpl implements AccountTccService {
@Override
public boolean tryDeduct(BusinessActionContext ctx, String userId, BigDecimal amount) {
// Try:冻结余额,balance -= amount,frozen_balance += amount
accountMapper.freeze(userId, amount);
return true;
}
@Override
public boolean confirm(BusinessActionContext ctx) {
// Confirm:将冻结余额正式扣除,frozen_balance -= amount
String userId = ctx.getActionContext("userId").toString();
BigDecimal amount = new BigDecimal(ctx.getActionContext("amount").toString());
accountMapper.deductFrozen(userId, amount);
return true;
}
@Override
public boolean cancel(BusinessActionContext ctx) {
// Cancel:解冻余额(需幂等处理,可能被重复调用)
String userId = ctx.getActionContext("userId").toString();
BigDecimal amount = new BigDecimal(ctx.getActionContext("amount").toString());
// 空回滚保护:若 Try 未执行则跳过
if (!accountMapper.hasFrozenRecord(userId, amount)) {
return true;
}
accountMapper.unfreeze(userId, amount);
return true;
}
}TCC 三大注意事项:
- 空回滚:Try 未执行(网络超时/未到达),Cancel 被触发,需判断记录不存在直接返回成功
- 幂等性:Confirm/Cancel 可能被重复调用,用唯一事务 ID(xid)去重
- 悬挂:Cancel 先于 Try 到达,Try 到达后需拒绝执行(检查事务已取消标记)
Seata AT / TCC 模式对比
Seata 是阿里开源的分布式事务框架,AT 模式对业务代码无侵入:
| 模式 | 原理 | 业务侵入 | 性能 | 适用场景 |
|---|---|---|---|---|
| AT 模式 | 自动生成 undo_log,二阶段自动回滚 | 无侵入(仅需 @GlobalTransactional) | 中等 | 关系型数据库,快速接入 |
| TCC 模式 | 手动实现 Try/Confirm/Cancel | 高侵入 | 高 | 性能要求高,跨异构资源 |
| Saga 模式 | 正向服务链 + 补偿服务链 | 中等 | 高 | 长流程业务,服务不可改造 |
// Seata AT 模式:仅加一个注解,其余业务代码无需修改
@GlobalTransactional(timeoutMills = 30000, name = "create-order-tx")
public void createOrder(OrderDTO order) {
orderMapper.insert(order);
// 远程调用——Seata 自动生成 undo_log,失败时自动补偿回滚
inventoryFeignClient.deduct(order.getSkuId(), order.getCount());
accountFeignClient.deduct(order.getUserId(), order.getAmount());
}下图展示了 Seata AT 模式中 TM、RM、TC 三角色的完整交互流程,包括提交和回滚两条分支:
Saga 模式
Saga 将长事务拆为一系列本地事务,每步成功则继续,任意步骤失败则依次触发前序步骤的补偿操作:
正向链:T1(创建订单)→ T2(扣库存)→ T3(扣款)→ T4(发货通知)
补偿链:T4 失败 → C3(退款)→ C2(还库存)→ C1(取消订单)适合流程长、不可能长时间持锁的场景,如电商下单流程、旅行预订(机票 + 酒店 + 租车)。
三、服务注册与发现:Nacos vs Eureka vs Consul
| 对比维度 | Nacos | Eureka | Consul |
|---|---|---|---|
| CAP | AP(可切换 CP) | AP | CP |
| 健康检查 | 客户端心跳 + 服务端主动探测 | 客户端心跳 | Agent 主动检测(TCP/HTTP/gRPC) |
| 配置中心 | 内置(推送通知) | 无(需 Spring Cloud Config) | 内置 K/V 存储 |
| 服务分组 | 命名空间 + 分组(多维隔离) | 无 | 无 |
| 多数据中心 | 支持 | 不支持 | 原生支持 |
| 维护状态 | 活跃(阿里主导) | 已停止维护 | 活跃(HashiCorp) |
| 生态 | Spring Cloud Alibaba 一等公民 | Spring Cloud Netflix | HashiCorp 生态 |
国内主流选型:Spring Cloud Alibaba 项目首选 Nacos,注册 + 配置 + 服务发现一体化,社区活跃,中文文档完善。
四、分布式锁:Redis Redisson vs Zookeeper
Redis Redisson 实现
Redisson 核心优势是看门狗自动续期(默认锁过期 30s,每 10s 自动续期):
@Service
public class OrderService {
@Autowired
private RedissonClient redissonClient;
public void createOrder(String userId) {
RLock lock = redissonClient.getLock("order:lock:" + userId);
try {
// waitTime=3s:等待获取锁的最长时间
// leaseTime=-1:启用看门狗自动续期(推荐)
boolean acquired = lock.tryLock(3, -1, TimeUnit.SECONDS);
if (!acquired) {
throw new BusinessException("操作过于频繁,请稍后重试");
}
doCreateOrder(userId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}Zookeeper 实现原理
基于临时顺序节点实现,Session 断开节点自动删除,天然防死锁:
1. 在 /lock/ 下创建临时顺序节点,如 /lock/node-0000000003
2. 获取所有子节点排序,若自己是最小节点 → 加锁成功
3. 否则,监听前一个节点的删除事件(只监听前一个,避免羊群效应)
4. 收到删除通知后重新判断,若成为最小节点则加锁成功方案对比
| 维度 | Redis(Redisson) | Zookeeper(Curator) |
|---|---|---|
| 性能 | 极高(内存操作,< 1ms) | 中等(ZAB 协议,5~20ms) |
| 可靠性 | 主从切换时有短暂锁丢失风险 | 强一致,无丢锁风险 |
| 防死锁 | 过期时间 + 看门狗续期 | Session 断开节点自动删除 |
| 公平性 | 非公平(可用 FairLock) | 天然公平(顺序节点排队) |
| 适用场景 | 高并发(秒杀/防重复提交) | 强一致性(金融/配置变更) |
五、分布式限流:Sentinel 滑动窗口 + 令牌桶
Sentinel 滑动窗口
Sentinel 默认使用滑动时间窗口统计 QPS:将 1 秒分为若干小格(默认 500ms 一格),滑动计算窗口内请求总数,避免固定窗口边界处的流量突刺。
@SentinelResource(value = "queryOrder",
blockHandler = "queryOrderBlock",
fallback = "queryOrderFallback")
public OrderVO queryOrder(String orderId) {
return orderMapper.selectById(orderId);
}
public OrderVO queryOrderBlock(String orderId, BlockException e) {
// 限流/熔断触发时的兜底
return OrderVO.empty("系统繁忙");
}
// 动态流控规则(通常通过 Nacos 配置中心推送)
FlowRule rule = new FlowRule("queryOrder");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1000); // QPS 上限 1000
FlowRuleManager.loadRules(Collections.singletonList(rule));令牌桶 vs 漏桶
| 算法 | 原理 | 突发流量 | 适用场景 |
|---|---|---|---|
| 令牌桶 | 固定速率生产令牌,请求消耗令牌,桶满丢弃 | 允许(消耗积累令牌) | API 接口限流(Guava RateLimiter) |
| 漏桶 | 以固定速率处理请求,超出直接丢弃 | 不允许 | 网络流量整形 |
// Guava RateLimiter(令牌桶实现)
RateLimiter rateLimiter = RateLimiter.create(100.0); // 每秒 100 个令牌
public String handleRequest() {
// 最多等待 100ms,获取不到令牌则限流
if (!rateLimiter.tryAcquire(1, 100, TimeUnit.MILLISECONDS)) {
return "系统繁忙,请稍后重试";
}
return doBusiness();
}六、负载均衡算法
| 算法 | 原理 | 适用场景 |
|---|---|---|
| 轮询(Round Robin) | 依次分配,平均分发 | 服务器性能相近 |
| 权重轮询 | 按权重比例分发 | 服务器性能差异明显 |
| 最少连接 | 选当前连接数最少的节点 | 长连接、请求耗时差异大 |
| 一致性 Hash | Hash(请求特征) 映射到虚拟节点环 | 有状态服务、缓存集群 |
| 随机 | 随机选取节点 | 简单场景 |
| IP Hash | Hash(客户端 IP) 固定路由 | 会话保持(Session 亲和) |
根据业务特征选择合适的负载均衡算法,下图给出了决策路径:
一致性 Hash 特别适合分布式缓存:增减节点时只影响相邻节点的数据,最小化缓存失效范围。引入虚拟节点(每个物理节点映射多个虚拟节点)解决数据倾斜问题。
七、熔断降级:Sentinel vs Resilience4j
| 对比维度 | Sentinel | Resilience4j |
|---|---|---|
| 来源 | 阿里开源 | Netflix Hystrix 继任者 |
| 生态 | Spring Cloud Alibaba 原生支持 | Spring Cloud Circuit Breaker 标准接口 |
| 熔断策略 | 慢调用比例 / 异常比例 / 异常数 | 错误率 / 慢调用率 |
| 内置限流 | 支持(QPS / 并发线程数) | 不内置(需配合其他组件) |
| 控制台 | Sentinel Dashboard(实时监控) | 无内置控制台 |
| 动态规则推送 | 支持 Nacos/ZK 动态推送 | 代码 / 配置文件 |
| 隔离方式 | 信号量隔离 | 信号量 + 线程池隔离 |
熔断器三状态:Closed(正常)→ Open(熔断,直接降级)→ Half-Open(探测恢复)。
八、高频面试题
Q1:CAP 中为什么 P 是必须保证的?Zookeeper 为什么是 CP?(阿里、字节高频)
P(分区容错性)在分布式系统中几乎必须满足,因为网络故障不可避免(机房断电、网线故障均会导致分区)。不选 P 意味着假设网络永不故障,这在真实分布式系统中是不现实的。
Zookeeper 选择 CP:基于 ZAB 协议,写入需要 Leader 接收后同步给多数派 Follower 才返回成功。当发生 Leader 选举时(约 200ms~30s),ZK 拒绝所有写请求,牺牲可用性来保证数据强一致性。这也是为什么高可用场景的服务注册中心更推荐使用 Eureka 或 Nacos(AP 模式)。
Q2:Seata AT 模式原理是什么?和 TCC 有什么区别?(字节、美团高频)
Seata AT 模式原理:在 SQL 执行前后自动生成 before-image 和 after-image,写入 undo_log 表。若需要回滚,用 undo_log 的 before-image 生成反向 SQL 执行补偿。业务代码只需加 @GlobalTransactional,无需修改业务逻辑(无侵入)。
区别对比:
- AT 无业务侵入,底层仍基于数据库行锁,性能中等,适合快速接入关系型数据库场景
- TCC 需要手动实现 Try/Confirm/Cancel,侵入高,但无数据库锁,性能更好,适合高并发或跨异构资源(Redis、RPC)
Q3:分布式锁主从切换时 Redis 锁会丢失吗?如何解决?(字节、美团)
会丢失。时序如下:
- 客户端 A 在 Master 加锁成功
- Master 宕机,锁数据未同步到 Slave
- Slave 晋升为新 Master
- 客户端 B 在新 Master 加锁成功 → 两个客户端同时持有锁,临界区安全被破坏
解决方案:
- RedLock 算法:向 5 个独立 Redis 节点同时加锁,超过半数(3 个)成功才算获取锁,任一节点宕机不影响整体
- Zookeeper:ZAB 强一致协议保证,不存在上述问题(但性能更低)
- 业务幂等兜底:即使偶发并发进入临界区,通过业务幂等(唯一索引/状态机)保证结果正确
生产建议:大多数业务场景 Redisson 单集群已足够,加上业务幂等即可。极致强一致要求(金融转账)选 Zookeeper。
Q4:Nacos 和 Eureka 有什么区别?为什么国内更推荐 Nacos?(阿里系必考)
核心区别:
- 功能:Nacos 内置配置中心(支持动态推送),Eureka 纯服务注册
- 一致性:Nacos 默认 AP,可通过接口切换为 CP;Eureka 只有 AP
- 健康检查:Nacos 支持主动检测(TCP/HTTP),Eureka 只依赖客户端心跳
- 维护状态:Eureka 2.x 已停止开源维护;Nacos 社区活跃,版本持续更新
- 隔离机制:Nacos 支持命名空间 + 分组多维度隔离(适合多环境/多租户)
国内推荐 Nacos:功能全面、阿里背书、与 Spring Cloud Alibaba 深度集成、中文文档完善、社区活跃。
Q5:服务雪崩是怎么发生的?如何预防?(阿里、滴滴)
服务雪崩:A 调 B,B 调 C,C 响应慢 → B 的线程池被慢请求占满 → B 无法响应 A → A 也开始积压 → 整条链路崩溃。
预防组合手段(缺一不可):
- 限流(Sentinel):拦截超量请求,保护下游
- 超时设置(Feign/Ribbon timeout):快速失败,不无限等待
- 熔断(Sentinel/Resilience4j):错误率超阈值自动熔断,拒绝请求直接降级
- 降级(fallback):熔断/限流时返回默认值或缓存数据
- 隔离(线程池/信号量):不同服务独立隔离,防止相互影响
Sentinel 熔断器三状态:Closed(正常统计)→ Open(熔断,直接走降级)→ Half-Open(探测,放行少量请求检测恢复)。
Q6:什么是一致性 Hash?为什么要引入虚拟节点?(腾讯、快手)
一致性 Hash:将 Hash 值空间组织成 0~2^32-1 的环,每个服务节点也映射到环上某个位置。请求根据 Hash(key) 落在环上,顺时针找到第一个节点即为负责节点。
优势:增减节点时,只有相邻节点受影响,最小化数据迁移/缓存失效范围(普通 Hash 取模方式增减节点会导致几乎所有数据重新映射)。
虚拟节点:如果物理节点较少,节点在环上分布不均匀,会导致数据倾斜(某些节点承担大量请求)。将每个物理节点映射为多个虚拟节点(如 150 个),分散在环的不同位置,使数据分布更均匀。
Q7:TCC 的空回滚、幂等、悬挂问题怎么解决?(美团、京东高频)
空回滚:Try 因网络超时未执行,Cancel 先被触发。 解决:Cancel 中检查业务记录是否存在,不存在则直接返回成功(不做实际回滚)。
幂等性:Confirm/Cancel 可能因网络重试被调用多次。 解决:用全局事务 ID(xid + branchId)作为唯一键,建去重表(执行成功后插入,重复调用检测到已插入则直接返回成功)。
悬挂:Cancel 先于 Try 到达并执行完成,之后 Try 到达。 解决:Cancel 执行时插入一条"已取消"标记记录;Try 执行前检查此标记,若存在则拒绝执行(返回失败)。
三个问题的根本解法:引入事务状态表,记录每个事务分支的状态(初始化/已 Try/已 Confirm/已 Cancel),所有操作前先查状态,确保幂等和顺序正确性。
Q8:Saga 模式和 TCC 的区别?各适合什么场景?(京东、美团)
核心区别:
- 资源预留:TCC 有 Try 预留阶段,事务提交前资源被冻结,隔离性好;Saga 直接执行正向操作,无预留,其他服务可读到中间状态(隔离性弱)
- 适用流程:TCC 适合短事务、高并发,如支付扣款;Saga 适合长流程(步骤多、每步耗时长),如旅行预订(订机票→订酒店→订租车,每步可能需要秒级或分钟级)
- 性能:Saga 无全局锁,并发更高;TCC 的 Try 阶段会冻结资源,有一定锁开销
- 补偿复杂度:Saga 的补偿逻辑(Cancel)必须是幂等的,且无法在中途停止补偿链
选型:金融/电商的短事务用 TCC;跨系统长流程(含外部系统调用)用 Saga。
想要更多大厂真题和详细解析?
加入知识星球,获取:字节/阿里/腾讯/美团最新分布式系统面试真题(含详细答案)、Seata 源码深度解析与生产排坑经验、微服务架构设计题系统讲解、个人简历指导 + 模拟面试服务。
