Day 013 — 分布式 + 消息队列 + 微服务

📅 2026/7/29 4:47:42 👁️ 阅读次数 📝 编程学习
Day 013 — 分布式 + 消息队列 + 微服务

📅2026-07-27 |🏷️Java · 后端方向 |⏱️建议 5h |🎯后端面试的终极考验——分布式系统设计能力

📌 今日知识地图

分布式 + MQ + 微服务 面试全景 │ ├── 模块一:分布式理论 │ ├── CAP 理论 & BASE 理论 │ ├── 一致性协议(Paxos / Raft) │ └── 分布式 ID(雪花算法 / 号段模式) │ ├── 模块二:分布式锁 & 分布式事务 │ ├── Redis 分布式锁 vs ZooKeeper 分布式锁 │ ├── 分布式事务:2PC → TCC → Seata-AT → MQ 最终一致性 │ └── 本地消息表 & 事务消息 │ ├── 模块三:消息队列 │ ├── RabbitMQ:Exchange / 死信 / 延迟队列 / 可靠性 │ ├── Kafka:高吞吐 / 分区副本 ISR / 幂等 / 事务 │ └── RabbitMQ vs Kafka 选型 │ ├── 模块四:微服务 │ ├── Nacos(注册中心 + 配置中心) │ ├── Gateway 网关 + Sentinel 熔断限流 │ ├── 链路追踪(SkyWalking)+ 灰度发布 │ └── Docker + K8s 基础 │ └── 面试题精选(10 道 + 公司标签)

模块一:分布式理论

1.1 CAP 理论

CAP = 分布式系统最多只能同时满足其中两个: Consistency(一致性):所有节点同一时刻看到相同数据 Availability(可用性):每个请求都能收到非错误的响应(但不保证数据最新) Partition Tolerance(分区容错):节点间网络故障时系统仍能工作 因为网络分区(P)在分布式系统中不可避免 → 必须在 C 和 A 之间取舍 CP(保一致性,牺牲可用性): 网络分区时,不可用的分区拒绝服务 例:ZooKeeper(Leader 宕机时暂停服务直到选举完成)、银行转账 AP(保可用性,牺牲强一致性): 网络分区时,所有分区都能继续服务(但数据可能不一致) 例:Eureka、DNS 最终一致性(Eventual Consistency)是 AP 的典型实践

1.2 BASE 理论

BASE = Basically Available(基本可用)+ Soft State(软状态)+ Eventually Consistent(最终一致性) Basically Available:系统出现故障时允许损失部分可用性 → 响应时间变慢 / 非关键功能降级 Soft State:系统中的数据允许存在中间状态 → 数据副本之间暂时不一致是可以接受的 Eventually Consistent:经过一段时间后,所有数据副本最终达到一致 → 不要求实时一致,但保证最终一致 BASE 是 CAP 中 AP 的延伸:牺牲强一致性,换取更高的可用性。

1.3 Raft 一致性协议

Raft 解决什么问题? → 多个节点对某个值达成一致(谁的数据是对的?) Raft 三个角色: Leader :处理所有客户端请求,发送日志给 Follower(一个任期只有一个) Follower :被动接收 Leader 的日志,不主动发起请求 Candidate:竞选 Leader 期间的临时角色 Raft 两个核心机制: ① Leader 选举: 心跳超时 → Follower 变 Candidate → 任期+1 → 投自己一票 → 请求其他节点投票 → 获得多数票 → 成为 Leader → 发送心跳维持统治 → 如果两个 Candidate 票数相同 → 随机超时后重新选举 ② 日志复制: 客户端请求 → Leader 追加日志 → 并发给所有 Follower → 多数确认 → 提交 → Leader 应用到状态机 → 返回结果给客户端 → Leader 在后续心跳中通知 Follower 提交

1.4 分布式 ID

雪花算法(Snowflake): 1bit(符号位,不用) | 41bit(毫秒时间戳,69年) | 10bit(机器ID,1024台) | 12bit(序列号,每毫秒4096个) 优点:高性能、趋势递增(对数据库索引友好)、不依赖外部服务 缺点:依赖机器时钟 → 时钟回拨会出问题 时钟回拨解决: → 等时钟追上 → 用"历史最大时间戳"兜底,时钟回拨期间用备用机器ID → 美团 Leaf:号段模式 + 雪花算法双模式 号段模式(美团 Leaf-Segment): 从数据库批量取一段 ID(如 1~1000)→ 缓存到本地 → 用完再取下一段 优点:无时钟依赖,ID 严格递增 缺点:依赖数据库

模块二:分布式锁 & 分布式事务

2.1 Redis 锁 vs ZooKeeper 锁

维度RedisZooKeeper
原理SET NX EX + Lua临时顺序节点 + Watch
实现争抢式(CAS 抢锁)排队式(节点最小序号获得锁)
释放主动删除(看门狗续期)连接断开自动删除(临时节点)
一致性AP(主从异步,可能丢锁)CP(ZAB 协议,强一致)
性能极高(内存操作)中(需要 ZAB 协议同步)
适用性能敏感、允许极低概率的锁丢失一致性要求高(如金融场景)
// ZooKeeper 分布式锁原理:// ① 所有线程在 /lock 下创建临时顺序节点(/lock/seq-0001, /lock/seq-0002, ...)// ② 序号最小的节点获得锁// ③ 其他节点 Watch 前一个节点 → 前一个节点释放 → 收到通知 → 成为最小 → 获得锁// ④ 连接断开 → 临时节点自动删除 → 下一个节点自动获得锁// 优点:公平锁(排队),连接断开自动释放,强一致性// 缺点:性能比 Redis 低,需维护 ZK 集群

2.2 分布式事务

分布式事务的核心困难: 一个业务操作涉及多个数据库/服务 如何保证跨库/跨服务的 ACID? 四种方案: ┌─────────────────────────────────────────────────────────┐ │ ① 2PC(Two-Phase Commit)— XA 协议 │ │ │ │ 协调者 → 所有参与者:Prepare(预提交,锁定资源) │ │ 协调者 → 所有参与者:Commit / Rollback │ │ │ │ 缺点:同步阻塞、协调者单点、数据不一致风险(Commit 阶段崩了)│ │ 适用:传统数据库(MySQL XA)、JTA │ ├─────────────────────────────────────────────────────────┤ │ ② TCC(Try-Confirm-Cancel) │ │ │ │ Try :预留资源(冻结库存、预扣余额) │ │ Confirm :提交(真正扣减) │ │ Cancel :释放(退回冻结的资源) │ │ │ │ 优点:不阻塞、业务层实现、灵活 │ │ 缺点:代码侵入强(每个接口都要写三套逻辑)、需幂等 │ │ 适用:金融、电商核心链路 │ ├─────────────────────────────────────────────────────────┤ │ ③ Seata-AT(自动补偿) │ │ │ │ 基于 undo_log 自动生成回滚 SQL → 无业务侵入 │ │ 两阶段:① 业务 SQL + 记录 undo_log │ │ ② 成功 → 删 undo_log / 失败 → 用 undo_log 回滚 │ │ │ │ 优点:对业务无侵入、使用简单 │ │ 缺点:性能损耗(多一次 undo_log 写入) │ │ 适用:不希望改业务代码的场景 │ ├─────────────────────────────────────────────────────────┤ │ ④ MQ 最终一致性(最常用!) │ │ │ │ 本地事务 + MQ 消息 = 最终一致 │ │ 例:下单 → 扣库存(本地事务 + 发消息) │ │ → 创建订单(消费消息) │ │ │ │ 核心:本地事务和发消息必须是原子的(事务消息/本地消息表) │ │ 优点:高可用、高性能、解耦 │ │ 缺点:数据不是实时一致的 │ │ 适用:对一致性要求不极端的场景(绝大多数互联网业务) │ └─────────────────────────────────────────────────────────┘

2.3 本地消息表 & 事务消息

// ═══════════════════════════════════════// 本地消息表(最经典的最终一致性方案)// ═══════════════════════════════════════// 思路:在同一个数据库中创建一张"消息表"// 业务操作和消息写入在同一个本地事务中 → 保证原子性@TransactionalpublicvoidcreateOrder(Orderorder){// ① 业务操作orderMapper.insert(order);// ② 写消息表(在同一个事务中!)Messagemsg=newMessage();msg.setTopic("ORDER_CREATED");msg.setBody(JSON.toJSONString(order));msg.setStatus("PENDING");messageMapper.insert(msg);// ③ 事务提交后 → 定时任务扫 PENDING 消息 → 发送到 MQ// ④ 消费者处理成功后 → 更新消息状态为 SENT// ⑤ 定时任务重试:PENDING 超过 N 分钟 → 重新发送}// ═══════════════════════════════════════// RocketMQ 事务消息// ═══════════════════════════════════════// ① 发送 Half 消息(半消息,消费者不可见)// ② 执行本地事务// ③ 本地事务成功 → Commit → 消费者可见// 本地事务失败 → Rollback → 消息删除// ④ 如果生产者挂了(没 Commit 也没 Rollback)// → Broker 定期回调生产者检查本地事务状态(checkListener)

模块三:消息队列

3.1 RabbitMQ

核心概念: Producer → Exchange(交换机) → [Binding] → Queue(队列) → Consumer Exchange 四种类型: Direct :Routing Key 完全匹配 → 精确路由 Topic :Routing Key 通配符匹配(*匹配一个词, #匹配零或多个词) Fanout :广播到所有绑定的队列(忽略 Routing Key) Headers :根据 Header 匹配(很少用) 消息可靠性三件套: ① 生产端确认(Publisher Confirm): 生产者 → Broker:消息收到了吗? Broker → 生产者:收到了(ack)/ 没收到(nack)→ 重发 ② 消费端确认(Consumer ACK): 消费者处理完 → 手动 ACK → Broker 删除消息 没处理完(连接断开/异常)→ 未 ACK → Broker 重新投递 ③ 持久化: Queue 持久化 + Message 持久化(delivery_mode=2) → Broker 重启消息也不丢 死信队列(DLX - Dead Letter Exchange): 消息变成死信的条件: → 被消费者拒绝(reject/nack)且 requeue=false → 消息过期(TTL) → 队列满了 死信队列 = "异常消息收容所" → 人工处理 / 定时任务重新投递 延迟队列: TTL + DLX 的经典组合: → 消息发到 A 队列(设置了 TTL,没有消费者) → 过期后变成死信 → 投递到 B 队列 → B 队列的消费者在延迟后收到消息 场景:订单 30 分钟未支付取消

3.2 Kafka

Kafka 为什么吞吐量这么高? ① 顺序写磁盘(Sequential Write): 追加写 → 磁盘顺序 I/O 接近内存随机 I/O 速度 (比随机写快 100 倍以上) ② Page Cache(页缓存): 数据先写到 OS 的 Page Cache → 由 OS 决定何时刷盘 读数据优先从 Page Cache 读 → 命中率高 → 不走磁盘 ③ 零拷贝(Zero Copy): sendfile() 系统调用 → 数据从 Page Cache 直接到网卡 → 不经过用户态 → 减少拷贝和上下文切换 ④ 分区并行(Partition): Topic 分成多个 Partition → 每个 Partition 独立读写 → 多个 Consumer 并行消费 → 水平扩展 ⑤ 批量处理(Batching): 生产者攒一批消息一起发 → 减少网络开销 消费者一次拉一批 → 减少拉取次数
Kafka 核心概念: Topic → 消息的逻辑分类 Partition → 每个 Topic 分为多个 Partition(物理分片) Partition 内消息严格有序,全局无序 Replica → 每个 Partition 有 N 个副本(1 Leader + N-1 Follower) ISR → In-Sync Replicas,与 Leader 保持同步的副本集合 如果 Follower 落后太多 → 从 ISR 中移除 Offset → 每条消息在 Partition 内的唯一位置编号 消息可靠性保证: 生产者 acks: acks=0 → 不等待确认(最快,可能丢消息) acks=1 → Leader 确认即可(默认) acks=all → 所有 ISR 确认(最安全,推荐) 消费者 Offset 提交: 自动提交 → 可能丢消息(消息拉取后自动提交,还没处理完) 手动提交 → 处理完再提交 → 至少一次语义 幂等性(Idempotent): 生产者:enable.idempotence=true → 自动去重(Producer ID + Sequence Number) 消费者:业务层实现幂等(唯一键 / 版本号 / Redis 去重) 事务(Transactional): 生产者:initTransactions → beginTransaction → send → commitTransaction 支持跨 Partition 的原子写入

3.3 RabbitMQ vs Kafka

维度RabbitMQKafka
定位消息代理(AMQP 协议)分布式流平台
吞吐万级/秒百万级/秒
延迟微秒级毫秒级
消息回溯不支持(消费完就删)支持(按 Offset 重放)
顺序全局顺序(单队列)Partition 内有序
持久化消息持久化 + 队列持久化全部落盘(默认持久化)
推拉Push(推送)Pull(拉取,长轮询)
路由丰富(Exchange/Binding)简单(Topic 直接发)
运维轻量重(依赖 ZooKeeper/KRaft)
适用业务消息、RPC、延迟消息日志、大数据、流处理、事件溯源

模块四:微服务

4.1 微服务体系全景

┌─────────────────────────────────────────────────────────┐ │ 微服务体系架构 │ │ │ │ 外部请求 → Gateway(网关) → Service A → Service B │ │ │ │ │ │ │ Nacos(注册发现) Nacos(配置中心) │ │ │ │ │ │ │ Sentinel(熔断限流) │ SkyWalking(链路追踪)│ │ │ │ │ │ │ └───────────────┴───────────┘ │ │ │ │ │ 消息队列(MQ) │ │ │ │ │ Docker + K8s 部署 │ └─────────────────────────────────────────────────────────┘

4.2 核心组件速查

Nacos(注册中心 + 配置中心): 注册中心:服务启动 → 注册到 Nacos → 定时心跳(5s) 服务调用 → 从 Nacos 获取服务列表 → 负载均衡 → 调用 服务下线 → Nacos 剔除 → 通知订阅者更新列表 配置中心:配置修改 → Nacos 推送 → 应用实时刷新(@RefreshScope) Gateway 网关: 路由(Route):根据路径/Header 转发到对应服务 过滤(Filter):鉴权、限流、日志、跨域 断言(Predicate):匹配请求条件 → Spring Cloud Gateway 底层:Netty + WebFlux(非阻塞) Sentinel(熔断限流降级): 限流:QPS 超过阈值 → 排队/拒绝(滑动窗口算法) 熔断:错误率超过阈值 → 快速失败 → 一段时间后探测恢复 降级:系统负载高 → 返回降级响应(默认值/静态页面) 三种效果:快速失败 / Warm Up(预热)/ 匀速排队 SkyWalking(链路追踪): 每个请求生成 TraceID → 在服务间传递 → 记录每个 Span(一次服务调用)的时间和状态 → 可视化拓扑图 + 调用链路 + 耗时分析 灰度发布: 流量染色:Header 中打标签(如 version=v2) → Gateway 根据标签路由到新版本服务 → 先放 10% 流量到 V2 → 观察 → 逐步扩大到 100%

4.3 Docker + K8s 基础

Docker: Dockerfile → Image(镜像)→ Container(容器) 关键命令: docker build -t app:v1 . docker run -d -p 8080:8080 --name myapp app:v1 docker-compose up -d (多容器编排) 多阶段构建: FROM maven AS build → 编译 → FROM openjdk AS runtime → 只复制 JAR K8s(Kubernetes): Pod :最小部署单位(一个或多个容器共享网络和存储) Deployment :管理 Pod 的副本数、滚动更新、回滚 Service :给 Pod 提供稳定 IP 和 DNS(Pod IP 会变,Service IP 不变) Ingress :外部流量入口,HTTP 路由规则 ConfigMap :非敏感的配置数据 Secret :敏感数据(密码、Token) HPA :Horizontal Pod Autoscaler → 根据 CPU/内存自动扩缩 Pod CI/CD 流程: 代码提交 → Jenkins/GitLab CI → docker build + push → kubectl apply → K8s 滚动更新 → 健康检查 → 流量切换

面试题精选(10 道)

Q1. CAP 理论?为什么不能同时满足?(阿里/腾讯/字节 高频)

标准回答

CAP = 一致性(所有节点同时看同一数据)+ 可用性(每个请求都能正常响应)+ 分区容错(网络故障时系统继续工作)。分布式系统中 P 不可避免(网络可能出问题),必须在 C 和 A 之间取舍。选 CP(如 ZK):Leader 宕机时暂停服务保证一致性。选 AP(如 Eureka):网络分区时所有节点能服务但数据可能不一致。实际系统不是二选一,而是不同程度的取舍(如金融侧重 CP,社交侧重 AP)。

Q2. 分布式事务怎么实现?各自的优缺点?(阿里/美团 高频)

标准回答

四种方案:① 2PC/XA——同步阻塞、协调者单点,传统数据库支持;② TCC——Try-Confirm-Cancel 三段式,业务层实现,灵活但代码侵入强;③ Seata-AT——自动生成 undo_log,无业务侵入但有性能损耗;④ MQ 最终一致性(最常用)——本地事务+消息表/事务消息保证原子发送,高性能、解耦,但非实时一致。

选型:对一致性要求极高(金融)→ TCC;不想改代码 → Seata;大多数场景 → MQ 最终一致性。

Q3. Kafka 为什么高吞吐?(字节/腾讯/快手 高频)

标准回答

五个原因:① 顺序写磁盘(追加写,接近内存速度);② Page Cache(数据先写页缓存,OS 决定刷盘);③ 零拷贝(sendfile,数据从 Page Cache 直发网卡,不经过用户态);④ 分区并行(多 Partition 独立读写,水平扩展);⑤ 批量处理(生产者攒批发送,消费者一次拉取多条,减少网络开销)。

Q4. RabbitMQ 怎么保证消息不丢失?(字节/美团)

标准回答

三端保障:① 生产端——Publisher Confirm(Broker 确认收到,失败重发)+ 持久化(Queue + Message 都持久化);② Broker 端——持久化到磁盘 + 镜像队列(Mirror Queue)防止节点故障;③ 消费端——手动 ACK(处理完确认,未 ACK 重投)+ 死信队列兜底(异常消息不丢失,人工处理)。三端全链路保障,"发-存-收"各环节都有确认机制。

Q5. 缓存和数据库一致性怎么保证?(字节/阿里 超高频)

标准回答

无法保证绝对一致(CAP),只能保证最终一致。最常用方案:Cache-Aside Pattern(旁路缓存)——先更新 DB,再删除缓存(不是更新缓存!)。更新缓存会有并发写问题,删除缓存+延迟双删更安全。更严格场景用:① 订阅 MySQL Binlog(Canal)→ 异步更新/删除缓存;② 分布式事务(MQ 最终一致性);③ 写时直接写 DB,读时永远查 DB + 缓存空结果。

Q6. 分布式 ID 的雪花算法原理?时钟回拨怎么解决?(美团/字节)

标准回答

结构:1bit(不用) + 41bit(毫秒时间戳) + 10bit(机器ID) + 12bit(序列号)。性能极高、趋势递增利于 MySQL 索引。时钟回拨解决:① 短时间回拨(<5ms)→ 自旋等待时钟追上;② 长时间回拨 → 用备用机器 ID 生成;③ 记录"历史最大时间戳",回拨期间使用未来时间戳(但可能产生不连续的 ID)。美团 Leaf 结合号段模式(从数据库批量取 ID 段)作为兜底。

Q7. 服务雪崩怎么解决?(字节/阿里)

标准回答

三层防护:① 限流(Sentinel/Guava RateLimiter,超过阈值直接拒绝或排队,保护自己不被冲垮);② 熔断(错误率或慢调用超过阈值 → 打开熔断器 → 快速失败 → 一段时间后探测 → 半开 → 恢复正常 → 关闭);③ 降级(系统负载高时返回默认值或静态页面,保证核心功能可用)。三层配合 = 防(限流)+ 断(熔断)+ 保底(降级)。

Q8. Nacos 注册中心的原理?和 Eureka 有什么区别?(阿里)

标准回答

Nacos = 注册中心 + 配置中心。注册中心:服务启动时向 Nacos 注册(IP+Port+元数据),定时心跳(5s),Nacos 检测 15s 无心跳标记不健康,30s 剔除。订阅者(客户端)定时拉取服务列表 + Nacos Push 更新通知。

vs Eureka:Eureka 是 AP(自我保护模式,宁可保留过期实例也不剔除),Nacos 支持 CP 和 AP 切换。Eureka 只做注册发现,Nacos 还做配置中心。Eureka 2.0 已闭源,Nacos 是当前主流。

Q9. Gateway 网关的作用?(腾讯/字节)

标准回答

统一入口:路由转发(URL→微服务)、鉴权(认证+授权)、限流、日志、跨域处理、负载均衡。Spring Cloud Gateway 基于 Netty+WebFlux(非阻塞异步),性能比 Zuul 1.x(阻塞)好很多。与 Nginx 的区别:Nginx 在服务最外层(流量入口),Gateway 在微服务层做业务路由。

Q10. K8s 的 Deployment 和 Service 的区别?(字节/阿里)

标准回答

Deployment 管理 Pod(副本数、滚动更新、回滚、启停),声明式控制(定义期望状态,Controller 调节到期望)。Service 给 Pod 提供稳定的网络访问(Pod IP 会变,Service 有稳定的 ClusterIP 和 DNS 名),通过 Label Selector 关联 Pod,默认轮询负载均衡。一句话:Deployment 管"运行",Service 管"访问"。


📊 今日知识图谱

分布式 + MQ + 微服务 DAY 13 │ ├── 分布式理论 │ ├── CAP:C(一致) vs A(可用),P 不可避免 │ ├── BASE:基本可用+软状态+最终一致(AP的延伸) │ ├── Raft:Leader选举(随机超时)+日志复制(多数确认) │ └── 雪花算法:时间戳+机器ID+序列号,时钟回拨→等待/备用 │ ├── 分布式锁 & 事务 │ ├── Redis锁(SETNX+Lua+看门狗,AP) vs ZK锁(临时顺序节点+Watch,CP) │ ├── 事务:2PC(同步阻塞) → TCC(Try/Confirm/Cancel) → Seata(undo_log) → MQ最终一致 │ └── 本地消息表(同事务写消息)+RocketMQ事务消息(Half→Commit/Rollback) │ ├── 消息队列 │ ├── RabbitMQ:Direct/Topic/Fanout + 生产确认/消费ACK/持久化 + DLX+TTL延迟 │ ├── Kafka:顺序写+PageCache+零拷贝+分区并行+批量=高吞吐 │ │ └── acks=all+手动提交+幂等+事务 保证可靠性 │ └── 选型:RabbitMQ(业务消息,低延迟) vs Kafka(大数据,高吞吐,可回溯) │ └── 微服务 ├── Nacos(注册中心+配置中心) + Gateway(路由/过滤/鉴权) ├── Sentinel(限流/熔断/降级) + SkyWalking(TraceID/链路追踪) ├── 灰度发布:Header染色 → 小比例路由V2 → 观察 → 扩大 └── K8s:Deployment(副本/更新) + Service(稳定IP) + HPA(自动扩缩)

🔜 明日预告

Day 14 — 场景题 + 算法冲刺 + 项目深挖(Java 方向收官!)

  • 场景题 4 大经典:秒杀系统 / 高可用支付 / 数据迁移 / 扫码登录
  • LeetCode Hot 100 必刷 30 题(Java 版)
  • 项目深挖:STAR 法则 + Java 项目常见追问
  • Java 方向 7 天知识图谱总复习

💡速通心法:分布式面试的核心是"trade-off 思维"——没有完美方案,只有合适的取舍。CAP 选 C 还是 A?分布式事务选强一致还是最终一致?Redis 锁 vs ZK 锁选性能还是一致性?每次选型都能讲清楚"因为什么场景所以选什么方案,牺牲了什么换来了什么",面试官就会觉得你真的懂分布式。