跳到正文
Java 入门到面试指南
返回

01-分布式理论基础

阶段:分布式与高并发篇 | 建议时长:5 天 | 前置:05-中间件篇(Redis、ZooKeeper、RocketMQ)

学习目标

  • 讲清”单机 → 分布式”的动机:不是追新技术,而是单机在性能、容量、可用性上撞墙后的必然选择;
  • 能结合注册中心(ZooKeeper/Nacos)讲 CAP 定理与 CP/AP 取舍,说清 BASE 理论与强/最终一致性的区别 ⭐;
  • 讲清一致性哈希为什么能让节点扩缩容时只迁移少量数据(哈希环 + 虚拟节点)⭐;
  • 掌握分布式 ID 四种方案,吃透雪花算法 64 位结构与时钟回拨处理 ⭐;
  • 从实现、优缺点、选型三个维度对比数据库/Redis/ZooKeeper 三种分布式锁 ⭐⭐;
  • 理解 2PC、TCC、本地消息表、RocketMQ 事务消息、Seata AT 的原理与适用场景,会做选型 ⭐⭐;
  • 说出一次 RPC 调用的完整链路(动态代理 → 序列化 → 网络传输 → 反序列化 → 反射调用)。

核心知识点

1. 为什么需要分布式

单机架构的三堵墙,每一堵都对应一个”分布式化”动作:

单机瓶颈具体表现对应解法
性能墙单机 QPS/连接数有上限,CPU 与内存物理封顶横向加机器 + 负载均衡
容量墙单表几千万行后索引膨胀、单机磁盘放不下分库分表、数据分片
可用性墙单机故障 = 全站故障,发布即停服多副本 + 故障转移(Redis/MySQL 主从、服务多实例)

代价同样明确:数据一致性变难、运维复杂度上升、排查链路变长。所以原则是”先优化单机与架构,能不分布式就不分布式”,这与下一篇的高并发演进路径是同一条主线。

2. CAP 定理与取舍 ⭐

  • C(Consistency)一致性:所有节点在同一时刻读到相同的数据;
  • A(Availability)可用性:每个请求都能得到(非错误的)响应;
  • P(Partition tolerance)分区容错性:网络分区(节点间断连)发生时系统仍能工作。

三个关键结论:

  1. 分布式系统中网络分区必然发生,P 必选,所以真实选择是 CP 还是 AP;
  2. 分区发生时:选 CP 就拒绝部分请求保数据一致(分布式锁、选主场景);选 AP 就返回可能旧的数据保可用(服务发现场景);
  3. 分区恢复后通过同步、补偿达到最终一致。

注册中心的取舍(呼应 05-中间件篇):

注册中心倾向具体表现
ZooKeeperCP主节点宕机选举期间(可达 30~120 秒)服务不可用,宁停服不脏读
EurekaAP节点对等,任一节点可用即可完成注册发现,可能读到旧实例列表
NacosAP/CP 可切换临时实例走 Distro 协议(AP),持久实例走 Raft(CP),默认 AP

BASE 理论:选 AP 时的工程化落地方案 —— BA(Basically Available,基本可用:降级、限流)、S(Soft State,软状态:允许中间态,如”支付处理中”)、E(Eventually Consistent,最终一致)。

一致性级别:

  • 强一致:写完任意节点后,立即可读到最新值,代价高、性能差;
  • 最终一致:允许短暂不一致,经过一段时间同步后保证一致,互联网业务主流选择;
  • 两者之间还有”读己之写""单调读”等会话级保证,面试提一句即可。

3. 一致性哈希 ⭐

普通 Hash 取模的问题:hash(key) % N,N 从 3 变 4 时绝大多数 key 的落点都会变,缓存几乎全部失效——这就是缓存集群扩容的经典痛点。

哈希环方案:

  1. 把 2^32 的哈希空间想成一个环,节点(按 IP/主机名 hash)与数据(按 key hash)都映射到环上;
  2. 数据沿顺时针找到第一个节点存取;
  3. 增删节点时,只有”该节点逆时针方向到上一个节点之间”的数据需要迁移(约 1/N),其余全部不动。

数据倾斜与虚拟节点:节点少时在环上分布不均,可能大部分数据落到同一节点。给每个物理节点生成 100~200 个虚拟节点散布在环上,数据先落在虚拟节点再映射回物理节点,均匀性大幅提升。

应用:分布式缓存节点分布、负载均衡(Dubbo 的一致性哈希策略)、分库分表备选方案。注意:Redis Cluster 用的不是一致性哈希,而是 16384 个 hash slot,面试要能区分这两者。

4. 分布式 ID ⭐

分库分表后自增主键只能保证单表唯一,必须引入全局 ID。核心诉求:全局唯一、趋势递增(利于 B+ 树顺序插入)、信息安全和性能可用性。

方案原理优点缺点
UUID128 位随机数本地生成、无依赖、性能高无序导致 B+ 树页分裂、太长(36 字符)、无业务含义
数据库号段批量取一段号缓存在应用内存,用完再取趋势递增、DB 压力小(一次取 1000 个)依赖 DB、应用重启浪费号段、多机房扩展难
雪花算法64 位:时间戳 + 机器 ID + 序列号本地生成、趋势递增、单机每秒 400 万+依赖机器时钟(回拨问题)、workerId 需要分配
Redis 自增INCR + 初始值/步长简单、有序强依赖 Redis 可用性,需持久化防重启丢数据

雪花算法结构(必背):

| 1 bit 符号位 | 41 bit 时间戳(毫秒级,可用约 69 年) | 10 bit 机器 ID(可拆 5+5 机房+机器,共 1024 台) | 12 bit 序列号(同一毫秒内 0~4095) |

时钟回拨问题:机器时间被 NTP 校准往回调后,同一毫秒可能被”重复使用”,生成重复 ID。处理思路:小幅度回拨(如 ≤5ms)等待追平;回拨过大直接抛异常告警,或切换备用 workerId。动手实践第 1 题要求实现。

5. 分布式锁方案对比 ⭐⭐

单机用 synchronized/ReentrantLock,应用多实例部署后 JVM 锁失效,需要跨进程互斥——典型如防重复下单、定时任务防多机并发执行。

方案一:数据库

  • 唯一索引插入法:INSERT INTO lock_table(lock_key) VALUES(?),插入成功即获锁,删除记录即释放;
  • 悲观锁法:SELECT ... FOR UPDATE 排他锁。
  • 缺点:性能差(DB 是瓶颈)、无过期自动释放、宕机要自己处理死锁。仅适合低频场景或没有其他中间件时兜底。

方案二:Redis(主流)

  • 雏形:SET lockKey uniqueValue NX EX 30,一条命令原子完成”不存在才设置 + 过期时间”;
  • 三大坑:① 业务没执行完锁先过期 → Redisson 看门狗自动续期;② 误删他人锁 → value 存唯一标识,释放时 Lua 校验后再删;③ 不可重入 → Redisson 用 Hash 结构记录持有线程与重入次数;
  • Redisson 关键点:加锁/解锁都是 Lua 脚本保证原子性;不传 leaseTime 时默认锁 30 秒,后台线程每 10 秒(1/3)检查并续期,业务没执行完锁不会失效。
RLock lock = redissonClient.getLock("order:lock:" + userId);
try {
    // 不指定 leaseTime → 启用看门狗自动续期;第一个参数是最长等待时间
    if (lock.tryLock(3, TimeUnit.SECONDS)) {
        // 业务逻辑
    }
} finally {
    if (lock.isHeldByCurrentThread()) {   // 只释放自己持有的锁
        lock.unlock();
    }
}

方案三:ZooKeeper

  • 客户端在 /locks 下创建临时顺序节点;
  • 判断自己是否是序号最小的节点:是则获锁;否则只 watch 前一个节点的删除事件(避免羊群效应);
  • 释放 = 删除自己的节点;客户端宕机时临时节点自动消失,锁天然不会死锁;
  • 代价:写请求要走 ZAB 过半提交,性能不如 Redis;过半机制的容灾换算(3 台容 1、4 台也只容 1)见《06-ZooKeeper与Nacos》。

对比与选型:

维度数据库RedisZooKeeper
性能低高(内存操作)中
可靠性高(落库)中(主从切换瞬间可能丢锁)高(CP)
实现复杂度低中(Redisson 已解决)中
自动过期需自己实现过期时间 + 看门狗续期临时节点(会话断开即释放)
适用场景低频、无其他中间件高并发业务锁(秒杀、防重)强一致要求(选主、元数据)

一句话选型:业务高并发选 Redisson;强一致、不能出错选 ZK;没得选才用数据库。

Redlock 与它的争议(深挖加分项):Redis 官方提出 Redlock——向 N 个(通常 5 个)互相独立的 Redis 主节点依次加锁,过半数成功且总耗时小于锁有效期才算加锁成功,意在摆脱”单主从切换丢锁”的隐患。争议焦点(Kleppmann 的质疑):

  • 依赖时钟:Redlock 靠各节点本地时钟判断锁是否过期;若某节点时钟跳变(NTP 校时、虚拟机迁移),锁可能在业务未完成时就失效。
  • 进程暂停(GC/调度)无法被锁阻止:持锁进程暂停跨越了锁租期,醒来后仍会继续写共享资源——此时锁已被别人拿走,互斥被破坏。这不是 Redis 独有的问题,任何基于租期的锁都躲不掉,只能用 fencing token(每次获锁拿到单调递增的令牌,写资源时带上;存储端拒绝比已见令牌更小的写入)来兜底。
  • 工程结论:对正确性要求极高的场景,别把安全性全押在分布式锁上——用 fencing token 或让下游操作幂等(如”更新前先校验版本号”)才是根本防线。因此 Redlock 在实践中用于”提高可用性、降低丢锁概率”,而不是”保证绝对互斥”。

6. 分布式事务方案与选型 ⭐⭐

跨库/跨服务的写操作无法用本地事务保证。先想清楚:能不用就不用——能合并到单库就合并,能靠业务流程避开的就避开。

2PC(两阶段提交)/3PC:协调者先问所有参与者”能否提交”(Prepare),全部同意再统一提交(Commit)。

  • 问题:同步阻塞、协调者单点、第二阶段部分节点宕机会导致数据不一致;3PC 增加超时与 CanCommit 预询问缓解阻塞,但未根治。属于一致性协议层面的考点,工程上很少直接用。

TCC(Try-Confirm-Cancel):业务层的两阶段,每个参与方实现三个方法。

  • Try:预留资源(冻结库存/冻结金额);Confirm:确认扣减;Cancel:释放预留;
  • 三个经典问题:空回滚(Try 没到、Cancel 先到)、幂等(重试导致重复执行)、悬挂(Cancel 之后迟到的 Try 又落地);解决方式是事务状态表 + 幂等控制;
  • 特点:不依赖数据库回滚、性能好,但业务侵入极大(每个接口要写三套逻辑),适合资金类核心链路。

本地消息表:业务操作与”待发送消息”记录在同一个本地事务里落库,后台任务扫描消息表投递 MQ,投递成功标记完成,失败重试;消费方保证幂等。简单可靠,缺点是与业务耦合、消息表需要定时清理。

RocketMQ 事务消息:本地消息表的中间件化。

  1. 发送半消息(对消费者不可见)→ 2. 执行本地事务 → 3. 根据结果提交/回滚半消息;Broker 定时回查生产者本地事务状态兜底。解决”本地事务与发消息的原子性”。

Seata AT 模式原理 ⭐(国内面试高频):

  • 一阶段:代理数据源解析业务 SQL,生成前后镜像(before/after image)写入 undo_log,与业务数据在同一个本地事务提交,同时向 TC(事务协调器)注册分支;
  • 二阶段提交:异步批量删除 undo_log,几乎无性能损耗;
  • 二阶段回滚:根据 undo_log 校验数据未被别人改过,用前镜像生成反向 SQL 补偿;
  • 全局锁:保证一阶段已提交的本地数据,在全局事务回滚前不被其他全局事务修改(隔离性来源);
  • 优点:业务几乎零侵入(一个 @GlobalTransactional 注解);代价:全局锁带来并发性能损耗。

选型对比表:

方案一致性性能侵入性适用场景
2PC/3PC强差低(协议层)数据库层面,工程少用
TCC最终一致(较强)好极高(手写三接口)资金类核心链路
本地消息表最终一致好中中小系统的 MQ 场景
RocketMQ 事务消息最终一致好低依赖 RocketMQ 的异步场景
Seata AT最终一致(准实时)中(全局锁)低大多数微服务跨库场景

7. RPC 基础

把本地方法调用变成跨网络调用,中间要做五件事——一次 RPC 调用流程:

  1. 动态代理:调用方拿到的是接口的代理对象,方法调用被拦截,组装请求(接口名、方法名、参数、版本号);
  2. 序列化:把请求对象序列化成字节流(Kryo/Protobuf/Hessian 等;JSON 慢但可读);
  3. 网络传输:客户端通过 Netty(TCP)发送,服务端接收;
  4. 反序列化:服务端还原请求,根据接口名找到本地实现类;
  5. 反射调用:Method.invoke 执行真实方法,结果原路序列化返回。

注册中心(Nacos/ZK)负责服务地址的注册与发现,负载均衡在调用方(客户端)完成。

Dubbo 与 OpenFeign 一句话:都是远程调用方案——Dubbo 是自研 RPC 框架(默认 Dubbo 协议 + 注册中心,服务治理能力强);OpenFeign 本质是基于 HTTP 的声明式调用客户端(伪 RPC:HTTP + 动态代理),是 Spring Cloud 技术栈的默认选择。

动手实践

  1. 手写雪花算法(含时钟回拨抛异常)

    • 实现 64 位结构生成器 + 简单 workerId 分配(配置文件或数据库);
    • 时钟回拨处理:≤5ms 自旋等待追平;>5ms 抛 IllegalStateException 并打告警日志;
    • 验收:10 个线程各生成 10 万个 ID,去重后仍是 100 万(无重复);手动把系统时间回拨 10 秒,程序稳定抛异常而不是生成重复 ID。
  2. 封装 Redisson 分布式锁工具类

    • 提供 tryLock(key, waitTime, Supplier) 模板方法:加锁 → 执行业务 → finally 中释放;进阶用 AOP + 自定义 @DistributedLock 注解实现;
    • 验收:两个终端同时运行抢锁程序,同一时刻只有一个执行业务;把业务耗时设为 40 秒(超过默认 30 秒锁时长),观察看门狗续期期间锁不失效。
  3. 对比测试三种”锁”(可选)

    • 分别用数据库唯一索引、原生 SET NX EX、Redisson 跑同一段”查库存→扣减”逻辑;
    • 验收:用 JMeter 100 并发压测,观察前两种出现超卖或请求堆积,Redisson 版扣减结果精确;把现象与原因写成注释留在代码里。

高频面试题

Q:CAP 定理是什么?实践中怎么取舍?⭐

  • P 必选(网络分区无法避免),实际是在 CP 与 AP 之间做选择;
  • 涉及钱、锁、选主的场景选 CP:宁可拒绝服务,不能读到脏数据(ZooKeeper、etcd);
  • 服务发现、登录态、信息流场景选 AP:宁可返回旧数据,不能整体不可用(Eureka、Nacos 默认);
  • 分区恢复后靠同步与补偿达到最终一致,这正是 BASE 理论;
  • 能举注册中心的例子(ZK 选举期间不可用 vs Eureka 返回旧列表)是明显加分项。

Q:一致性哈希解决什么问题?虚拟节点的作用?⭐

  • 解决节点数变化时普通取模导致几乎全量数据迁移的问题;
  • 哈希环 + 顺时针寻址,增删节点只影响相邻区段的数据(约 1/N);
  • 节点少时数据倾斜,用虚拟节点(每个物理节点 100~200 个)打散到环上,让分布均匀;
  • 应用:分布式缓存、负载均衡策略;能对比出 Redis Cluster 用的是 hash slot 而非一致性哈希更好。

Q:讲讲雪花算法的结构?时钟回拨怎么处理?⭐

  • 64 位 = 1 符号位 + 41 位时间戳(约 69 年)+ 10 位机器 ID + 12 位毫秒内序列号(4096);
  • 时钟回拨会让同一毫秒被重复使用而生成重复 ID;
  • 处理:小回拨(毫秒级)等待追平;大回拨抛异常告警,或切换备用 workerId;
  • 缓解手段:NTP 用渐变同步(slew)而非阶跃、预留回拨保护位、生产用美团 Leaf 等成熟方案。

Q:Redis、ZooKeeper、数据库三种分布式锁怎么选?⭐

  • Redis:性能最高,Redisson 解决了续期、误删、重入三大坑;代价是主从切换瞬间可能丢锁,适合高并发业务锁;
  • ZK:临时顺序节点 + watch 前一个节点,会话断开自动释放、不会死锁,CP 更可靠,性能一般;
  • 数据库:实现最简单,但性能差、无自动过期、要自己处理宕机,只在低频场景兜底;
  • 结论:默认 Redisson;强一致选 ZK;数据库是最后的选择。

Q:分布式事务有哪些方案?怎么选型?⭐

  • 用三个维度评估:一致性强度、性能、业务侵入性;
  • 强一致:2PC(同步阻塞,工程少用)、TCC(侵入大但可控,适合资金链路,要处理空回滚/幂等/悬挂);
  • 最终一致:本地消息表(简单通用)、RocketMQ 事务消息(半消息 + 回查)、Seata AT(零侵入但有全局锁开销);
  • 选择优先级:能合并成单库本地事务 > 消息最终一致 > Seata AT > TCC,数据越重要用侵入性越强的方案;
  • 一定先反问业务场景再选型,体现”能不用就不用”的工程判断。

Q:Seata AT 模式的原理?

  • 一阶段:代理数据源解析 SQL,把前后镜像写入 undo_log,随本地事务一起提交,并向 TC 注册分支;
  • 二阶段:全局提交则异步删除 undo_log;全局回滚则校验后镜像无脏写,用前镜像生成反向 SQL 补偿;
  • 全局锁保证回滚窗口内数据不被其他全局事务修改;
  • 对比 TCC:AT 无业务侵入,但依赖数据库支持且并发受全局锁限制。

推荐资料

资料说明链接
pdai 架构知识体系CAP、一致性、分布式锁、事务的体系化索引https://pdai.tech/md/arch/arch-z-overview.html
JavaGuide RPC&Dubbo 入门RPC 原理与 Dubbo 架构详解https://javaguide.cn/distributed-system/rpc/rpc-intro.html
JavaGuide ZooKeeper 入门ZAB、Watcher、临时节点与 ZK 分布式锁https://javaguide.cn/distributed-system/distributed-process-coordination/zookeeper/zookeeper-intro.html
JavaGuide 消息队列入门事务消息与 MQ 基础概念https://javaguide.cn/high-performance/message-queue/message-queue.html
JavaGuide 高可用系统设计指南限流/降级/超时重试,衔接下一篇https://javaguide.cn/high-availability/high-availability-system-design.html
JavaGuide 后端面试通关计划冲刺期分布式部分的复习节奏https://javaguide.cn/interview-preparation/backend-interview-plan.html
pdai 面试知识体系分布式面试真题索引https://pdai.tech/md/interview/x-interview.html