中间件 — 小白讲解 + 面试题精解
中间件 — 小白讲解 + 面试题精解
定位:假设你只会用 @Autowired 注入一个 Template 发消息,所有概念从零讲起,配代码示例 + 面试题。 覆盖:RocketMQ → Kafka → RabbitMQ → ElasticSearch → Redisson 分布式锁 五大模块。 用法:先读讲解理解概念 → 跑代码验证 → 再看面试题自测。 特色:每个知识点都结合简历真实项目案例,面试时能直接用。
目录
- 第一章:RocketMQ(简历项目四核心)
- 第二章:Kafka(对比学习,简历项目一)
- 第三章:RabbitMQ(了解即可)
- 第四章:ElasticSearch(简历项目四全文检索)
- 第五章:Redisson 分布式锁
第一章:RocketMQ(简历项目四核心)
简历技能清单写了“熟悉 RocketMQ(事务消息/顺序消息/幂等)”,项目四直接用了事务消息 + 幂等键。 面试官会重点问:事务消息原理、消息幂等怎么设计。
1.1 消息模型(Topic / Tag / Group)
小白讲解
RocketMQ 的核心概念:
Topic:消息主题,一类消息的集合(如 order-topic 订单主题)
Tag:消息标签,Topic 下的二级分类(order-topic:paid 已支付 / order-topic:refund 退款)
→ 消费者可以按 Tag 过滤,只消费自己关心的消息
Producer Group:生产者组,一组生产相同 Topic 消息的实例
Consumer Group:消费者组,一组消费相同 Topic 的实例
→ 同一 Consumer Group 内的实例分摊消费(负载均衡)
→ 不同 Consumer Group 各自消费全量(广播给多个业务方)
Broker:消息中转节点,存储消息
NameServer:注册中心,管理 Broker 和 Topic 的路由信息
发送和消费流程:
Producer → NameServer(查 Topic 路由)→ Broker(存储消息)
Consumer → NameServer(查 Broker 地址)→ Broker(拉取消息)
为什么要有 Tag?
没有 Tag:
一个订单 Topic,支付成功、退款、取消三类消息混在一起
消费者要消费支付成功的,得把所有消息拉下来再手动过滤 → 浪费
有 Tag:
order-topic 下分 paid / refund / cancel 三个 Tag
消费者订阅 order-topic 时指定 tag=paid
→ Broker 端直接按 Tag 过滤,只投递支付成功的消息
面试题
Q1:RocketMQ 的 Topic、Tag、Group 分别是什么?(中频 ⭐⭐⭐)
答:
- Topic:消息主题,一类消息的集合
- Tag:Topic 下的二级分类,消费者可按 Tag 过滤
- Producer Group:生产组,一组生产者实例
- Consumer Group:消费组,组内负载均衡分摊消费,组间各自消费全量
Q2:Consumer Group 的消费模式?(中频 ⭐⭐⭐)
答:
- 集群模式(默认):同一 Consumer Group 内多个实例分摊消费(一条消息只被组内一个实例消费)
- 广播模式:组内每个实例都消费全量消息(一条消息被组内所有实例消费)
- 集群模式保证水平扩展(加实例提升消费吞吐),广播模式用于“通知所有实例”的场景
1.2 事务消息(简历直接写了,面试必问原理)
小白讲解
一句话:事务消息解决“本地事务和消息发送的一致性”——要么本地事务和消息都成功,要么都失败。
问题场景(不用事务消息会怎样):
用户下单流程:
① 本地数据库扣减库存(本地事务)
② 发送"下单成功"消息给 MQ,通知下游发货
如果 ① 成功但 ② 失败(网络抖动、进程崩溃):
→ 库存扣了,但下游没收到消息,不发货
→ 数据不一致!
如果先发消息再扣库存:
① 发消息成功
② 扣库存失败
→ 下游收到消息去发货,但库存没扣成功
→ 还是不一致!
根本问题:本地事务(数据库)和消息发送(MQ)是两个独立系统,
无法用同一个事务保证。
RocketMQ 事务消息的解决思路(两阶段 + 回查):
事务消息的完整流程(面试要能画出来):
① Producer 发送"半消息"(Half Message)
→ 半消息对消费者不可见(此时还不能被消费)
② Broker 存储半消息后,返回确认给 Producer
③ Producer 执行本地事务(扣库存)
→ 本地事务成功 → 提交半消息(Commit)
→ 本地事务失败 → 回滚半消息(Rollback)
④ Broker 收到 Commit → 半消息变成普通消息,消费者可以消费
Broker 收到 Rollback → 删除半消息,消费者看不到
⑤ 如果 Producer 崩溃,一直没确认(半消息挂起)
→ Broker 主动"回查"(Check Back)Producer 的本地事务状态
→ Producer 根据本地事务结果回复 Commit / Rollback
关键点:
半消息 = 暂存消息,先不投递,等事务结果
回查机制 = 兜底,防止 Producer 崩溃导致消息一直挂起
结合简历案例
简历原文:
"采用 RocketMQ 事务消息实现跨服务最终一致性,设计幂等键
(userId+activityId+date)解决重复消费"
面试话术:
"项目四社区平台的活动报名流程,涉及两个服务:
- 活动服务:扣减活动名额(本地事务)
- 消息服务:发送报名成功通知(MQ 消息)
如果扣名额成功但消息没发出去,用户报名了却没收到通知,数据不一致。
我用 RocketMQ 事务消息解决:
1. 先发半消息(此时用户还看不到)
2. 执行本地事务扣名额
3. 扣成功 → 提交半消息,下游收到通知
4. 扣失败 → 回滚半消息,下游收不到
5. 如果服务崩溃,Broker 回查本地事务状态兜底
这样保证了扣名额和发消息的一致性。"
代码示例
@Service
public class ActivitySignupService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private ActivityMapper activityMapper;
@Transactional // 本地事务
public void signup(Long activityId, Long userId) {
// 发送事务消息
rocketMQTemplate.sendMessageInTransaction(
"signup-topic", // Topic
MessageBuilder.withPayload(new SignupMessage(activityId, userId)).build(),
activityId // 业务参数,回查时用
);
}
}
// 事务监听器:执行本地事务 + 回查
@RocketMQTransactionListener
public class SignupTransactionListener implements RocketMQLocalTransactionListener {
@Autowired
private ActivityMapper activityMapper;
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 执行本地事务:扣减活动名额
Long activityId = (Long) arg;
try {
activityMapper.deductQuota(activityId); // 扣名额
return RocketMQLocalTransactionState.COMMIT; // 本地事务成功 → 提交消息
} catch (Exception e) {
return RocketMQLocalTransactionState.ROLLBACK; // 失败 → 回滚消息
}
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
// 回查:Producer 崩溃后 Broker 会调用这里
// 查询本地事务是否已提交(比如查名额是否已扣)
Long activityId = (Long) msg.getHeaders().get("activityId");
boolean deducted = activityMapper.isQuotaDeducted(activityId);
return deducted
? RocketMQLocalTransactionState.COMMIT
: RocketMQLocalTransactionState.ROLLBACK;
}
}
面试题
Q1:RocketMQ 事务消息的原理?(超高频 ⭐⭐⭐⭐⭐)
简历直接写了,必须会答!
答:两阶段 + 回查:
- Producer 发半消息(消费者不可见)
- Broker 存储半消息后返回确认
- Producer 执行本地事务
- 本地事务成功 → Commit(半消息转普通消息);失败 → Rollback(删除半消息)
- Producer 崩溃未确认 → Broker 回查本地事务状态兜底
Q2:事务消息的回查机制解决了什么问题?(高频 ⭐⭐⭐⭐)
答:解决了“Producer 执行本地事务后崩溃,没来得及告诉 Broker 结果”的问题。半消息会一直挂起(消费者收不到),Broker 定期回查 Producer 的本地事务状态,根据结果决定 Commit 还是 Rollback,避免消息永久挂起。
Q3:事务消息的局限?(中频 ⭐⭐⭐)
答:
- 只保证“本地事务 + 消息发送”的一致性,不保证下游消费一定成功(消费失败要靠重试/死信)
- 半消息会占用存储,需要定期清理超时的半消息
- 回查逻辑需要业务方实现,不能自动判断
1.3 顺序消息(简历技能清单写了)
小白讲解
什么是顺序消息?
要求消息按发送顺序被消费(如订单的状态流转必须有序)
为什么默认不保证顺序?
RocketMQ 为了提高吞吐,消息分布在多个队列(Queue),
消费者多线程并行消费 → 顺序被打乱
怎么保证顺序?
方案:局部顺序(把需要有序的消息放到同一个队列)
生产端:
用 MessageQueueSelector 把同一个业务 key 的消息路由到同一个 Queue
(如订单 ID 相同的消息 → 同一个 Queue)
消费端:
同一个 Queue 内的消息用一个线程顺序消费
(不同 Queue 之间可以并行,保证整体吞吐)
本质:牺牲"全局顺序"换"局部顺序 + 并行吞吐"
结合简历案例
简历技能清单写了"顺序消息",面试话术:
"项目四的活动报名,同一个用户的操作要按顺序处理
(先报名 → 再支付 → 再核销,不能乱序)。
生产端我用 MessageQueueSelector 按 userId 取模,
把同一个用户的消息路由到同一个 Queue;
消费端对同一个 Queue 用单线程顺序消费。
这样就保证了同一用户的业务操作有序,
不同用户之间仍然并行,吞吐不受大影响。"
代码示例
// 生产端:按业务 key 路由到同一个 Queue,保证局部顺序
rocketMQTemplate.sendOrderly(
"order-topic",
MessageBuilder.withPayload(order).build(),
order.getUserId(), // 相同的 userId 路由到同一个 Queue
5000L // 超时
);
// 消费端:顺序消费
@Component
@RocketMQMessageListener(
topic = "order-topic",
consumerGroup = "order-consumer",
consumeMode = ConsumeMode.ORDERLY // 顺序消费模式
)
public class OrderConsumer implements RocketMQListener<Order> {
@Override
public void onMessage(Order order) {
// 同一个 Queue 的消息按顺序进入这个方法
processOrder(order);
}
}
面试题
Q1:RocketMQ 怎么保证顺序消息?(高频 ⭐⭐⭐⭐)
答:
- 生产端:用 MessageQueueSelector 把相同业务 key 的消息路由到同一个 Queue
- 消费端:同一个 Queue 的消息用单线程顺序消费(ConsumeMode.ORDERLY)
- 本质是局部顺序:同 Queue 有序,跨 Queue 并行,兼顾顺序和吞吐
Q2:全局顺序消息怎么实现?(中频 ⭐⭐⭐)
答:把所有消息都路由到同一个 Queue(只有一个队列),消费端单线程消费。这样是全局有序,但完全失去并行能力,吞吐极低,实际很少用。生产环境用局部顺序即可。
1.4 消息幂等(简历直接写了)
小白讲解
一句话:幂等 = 同一条消息被消费多次,业务结果不变(不会重复扣款、重复报名)。
为什么需要幂等?
消息可能被重复消费:
1. 消费成功后,还没提交 offset 就崩溃 → 重启后重新消费
2. 网络抖动,Broker 没收到 ack → 重投递
3. 生产者重试 → 重复发送
所以消息队列只保证"至少一次投递"(At Least Once),
不保证"恰好一次"(Exactly Once),
幂等要靠业务层自己保证。
怎么设计幂等?
方案 1:唯一索引 / 去重表
用幂等键(业务唯一标识)做数据库唯一索引
插入时若违反唯一约束,说明已处理过,直接跳过
方案 2:Redis Set 去重
消费前 SETNX 幂等键,设置成功才处理,否则跳过
方案 3:状态机
检查业务状态,已处理则跳过
(如订单状态已是"已支付",再收到"支付成功"消息就不处理)
结合简历案例
简历原文:
"设计幂等键(userId+activityId+date)解决重复消费"
面试话术:
"项目四活动报名,消息可能因网络重试被重复消费。
我设计了幂等键 userId + activityId + date:
- 含义:同一用户在同一天对同一活动只能报名一次
- 数据库加唯一索引 uk_user_activity_date
- 消费消息时,先根据幂等键 insert 报名记录
→ insert 成功 = 首次消费,正常处理
→ insert 报唯一约束冲突 = 重复消费,直接跳过
这样即使消息投递了 3 次,也只报名成功 1 次。"
代码示例
@Component
public class SignupConsumer {
@Autowired
private SignupMapper signupMapper;
public void consume(SignupMessage msg) {
Long userId = msg.getUserId();
Long activityId = msg.getActivityId();
String date = msg.getDate();
// 幂等键 = userId + activityId + date
// 数据库唯一索引 uk_user_activity_date 兜底
try {
SignupRecord record = new SignupRecord(userId, activityId, date);
signupMapper.insert(record); // 首次插入成功
// 正常处理业务...
} catch (DuplicateKeyException e) {
// 唯一索引冲突 = 重复消费,跳过
log.info("重复消费,跳过: userId={}, activityId={}, date={}",
userId, activityId, date);
}
}
}
-- 幂等键对应的唯一索引
ALTER TABLE signup_record
ADD UNIQUE KEY uk_user_activity_date (user_id, activity_id, signup_date);
面试题
Q1:消息幂等怎么设计?(超高频 ⭐⭐⭐⭐⭐)
简历直接写了,必须会答!
答:
- 幂等键 + 唯一索引:用业务唯一标识(userId+activityId+date)做数据库唯一索引,重复插入报冲突就跳过(简历方案)
- Redis SETNX:消费前用幂等键 SETNX,成功才处理
- 状态机:检查业务状态,已处理则跳过
- 去重表:单独一张表记录已处理的幂等键
Q2:为什么消息队列不保证“恰好一次”?(中频 ⭐⭐⭐)
答:因为分布式场景下,“投递”和“确认”之间天然存在重复的可能(网络重试、offset 提交与消费的原子性无法保证)。MQ 只能保证“至少一次”,“恰好一次”要么靠幂等消费兜底,要么用事务消息(RocketMQ)保证发送端一致性。
1.5 消息积压与死信队列
小白讲解
消息积压(Backlog):
消费者处理速度 < 生产者发送速度 → 消息堆积
后果:消息延迟增大,最终可能撑爆磁盘
处理方案:
1. 扩容消费者:增加 Consumer 实例(但受 Queue 数量限制,实例数 ≤ Queue 数)
2. 增加 Queue:扩容 Broker 的队列数
3. 临时降级:跳过非核心消息,先消费核心消息
4. 异步化:慢的同步逻辑改异步
死信队列(DLQ):
一条消息消费失败重试 N 次仍失败 → 进入死信队列
死信队列里的消息需要人工介入处理(排查为什么一直失败)
RocketMQ 重试机制:
消费失败 → 重试 16 次(默认)→ 仍失败 → 进死信队列
每次重试间隔递增(10s → 30s → 1min ... 2h)
面试题
Q1:消息积压怎么处理?(高频 ⭐⭐⭐⭐)
答:
- 先定位是消费慢还是生产太快
- 消费慢:扩容消费者实例(受 Queue 数限制)、优化消费逻辑(批量、异步、缓存)
- 生产太快:限流生产端、削峰(大促前提前扩容)
- 应急:临时提高消费并行度,或写临时程序快速消费积压消息
- 治本:增加 Queue 数(支持更多并行消费实例)
Q2:什么是死信队列?消息进死信队列后怎么办?(中频 ⭐⭐⭐)
答:
- 死信队列:消费失败重试 N 次(RocketMQ 默认 16 次)仍失败的消息会进入死信队列
- 处理:人工排查失败原因(数据问题?代码 bug?),修复后重新投递,或手动处理业务
- 意义:避免坏消息无限重试阻塞队列,把“毒消息”隔离出来
第二章:Kafka(对比学习,简历项目一)
简历技能清单写了 Kafka,项目一用了“Kafka 实现指令状态变更的异步消息通知”。 面试官会问“为什么选 Kafka 而不是 RocketMQ”。
2.1 Kafka 基本概念
小白讲解
Kafka 核心概念:
Topic:消息主题(对应 RocketMQ 的 Topic)
Partition:分区,Topic 被拆成多个分区(对应 RocketMQ 的 Queue)
→ 分区是并行和有序的基本单位
→ 同一分区内有序,跨分区无序
Replica:副本,每个分区有多个副本(一主多从)
→ 保证高可用,主副本故障从副本接管
Consumer Group:消费组
→ 组内一个分区只能被一个消费者消费(保证分区内有序)
→ 分区数 ≥ 消费者数,才能全部并行
Kafka 为什么吞吐高(百万级/秒)?
1. 顺序写磁盘:消息追加到日志末尾,顺序写比随机写快几个数量级
2. 零拷贝(Zero Copy):sendfile 系统调用,数据不经过用户态
3. 页缓存(Page Cache):消息先写内存页缓存,由 OS 异步刷盘
4. 批量发送 + 压缩:Producer 攒一批再发,压缩减少网络和磁盘
结合简历案例
简历原文:
"基于 Kafka 实现指令状态变更的异步消息通知与事件驱动处理,
保障跨系统数据最终一致性"
面试话术:
"项目一的指令状态流转(录入→复核→审核→出款)涉及多个系统。
状态变更后要通知下游系统(清算系统、风控系统、报表系统)。
如果用同步调用,指令状态一变更就调下游接口,
下游慢会拖累主流程,且下游挂了主流程也挂。
我引入 Kafka 做异步解耦:
1. 指令状态变更 → 发布状态变更事件到 Kafka
2. 各下游系统订阅对应 Topic,异步消费
3. 下游消费失败不影响主流程,Kafka 重试保证最终一致
为什么选 Kafka 而不是 RocketMQ?
项目一的数据量(指令状态事件)吞吐要求高,
Kafka 顺序写 + 零拷贝的吞吐远高于 RocketMQ,
且这个场景不需要事务消息、延迟消息这些 RocketMQ 特性。"
面试题
Q1:Kafka 为什么吞吐量这么高?(高频 ⭐⭐⭐⭐)
答:
- 顺序写磁盘(日志追加),比随机写快几个数量级
- 零拷贝(sendfile),数据不经过用户态拷贝
- 页缓存,消息先写内存由 OS 异步刷盘
- 批量发送 + 压缩,减少网络和磁盘开销
Q2:Kafka 的分区有什么用?(高频 ⭐⭐⭐⭐)
答:
- 并行:多个分区可以被多个消费者并行消费,提升吞吐
- 有序:同一分区内消息有序
- 扩容:增加分区可以提升并行度
- 分区数 ≥ 消费者数,消费者才能全部并行工作
2.2 Kafka vs RocketMQ 对比(面试高频)
对比表
| 维度 | RocketMQ | Kafka |
|---|---|---|
| 事务消息 | 原生支持(半消息+回查) | 仅 Producer 事务(弱) |
| 延迟消息 | 原生支持(18 个级别) | 不支持 |
| 顺序消息 | 原生支持 | 分区内有序 |
| 消息回溯 | 按时间回溯 | 按 Offset 回溯 |
| 吞吐量 | 万级/秒 | 百万级/秒 |
| 架构 | NameServer + Broker | Zookeeper/ KRaft + Broker |
| 生态 | 阿里系(Spring Cloud Alibaba) | 大数据生态(Flink/Spark) |
| 适用场景 | 业务消息(事务/顺序/延迟) | 日志采集、大数据流处理 |
面试题
Q1:Kafka 和 RocketMQ 怎么选?(高频 ⭐⭐⭐⭐⭐)
答:
- 选 RocketMQ:需要事务消息、延迟消息、严格的顺序消息(业务场景)
- 选 Kafka:超高吞吐(日志采集、埋点、大数据流处理)、大数据生态集成(Flink/Spark)
- 简历经验:项目四业务消息用 RocketMQ(需要事务消息),项目一状态事件用 Kafka(吞吐高 + 事件驱动)
Q2:RocketMQ 为什么支持事务消息而 Kafka 不支持?(中频 ⭐⭐⭐)
答:设计定位不同。RocketMQ 定位“金融级业务消息”,事务消息是刚需;Kafka 定位“日志/流数据管道”,追求极致吞吐,事务场景少。Kafka 只有 Producer 幂等和事务(保证不重复不丢失),但没有 RocketMQ 那种“本地事务+半消息+回查”的业务事务消息。
第三章:RabbitMQ(了解即可)
小白讲解
RabbitMQ 特点:
- 基于 AMQP 协议,有 Exchange 路由概念(比 RocketMQ/Kafka 灵活)
- 延迟队列用"死信 + TTL"实现(比较绕)
- 吞吐量低(万级/秒以下),适合小规模企业应用
四大核心概念:
Exchange:交换机,接收消息并路由到队列
Queue:队列,存储消息
Routing Key:路由键,决定消息去哪个队列
Binding:绑定,Exchange 和 Queue 的关联关系
路由模式:
Direct:精确匹配 routing key
Topic:模糊匹配(通配符 * 和 #)
Fanout:广播到所有绑定队列
面试题
Q1:RabbitMQ 和 RocketMQ/Kafka 的区别?(低频 ⭐⭐)
答:
- RabbitMQ 基于 AMQP 协议,有 Exchange 路由,灵活但吞吐低
- RocketMQ/Kafka 面向高吞吐设计,适合大数据量场景
- 简历实际用 RocketMQ + Kafka,RabbitMQ 只是了解
第四章:ElasticSearch(简历项目四全文检索)
简历技能清单写了“了解 ES 全文检索”,项目四用了“基于 ElasticSearch 搭建全文检索引擎”。
4.1 倒排索引(面试必问)
小白讲解
一句话:倒排索引 = 从“文档找词”反转为“词找文档”,像书的末尾索引一样。
正排索引(正向):文档 → 包含的词
文档1:Java 高并发 编程
文档2:Java 面试 题
文档3:高并发 系统 设计
倒排索引(反向):词 → 包含它的文档
Java → 文档1, 文档2
高并发 → 文档1, 文档3
编程 → 文档1
面试 → 文档2
系统 → 文档3
搜索"Java 高并发":
查倒排索引:Java → {1, 2},高并发 → {1, 3}
求交集:{1, 2} ∩ {1, 3} = {1}
→ 文档1 最匹配
关键理解:
正排索引适合"给定文档查内容"
倒排索引适合"给定关键词查文档"(搜索场景)
ES 内部存的就是倒排索引,所以全文检索飞快
结合简历案例
简历原文:
"基于 ElasticSearch 搭建全文检索引擎"
面试话术:
"项目四社区平台,用户帖子、评论要支持全文搜索。
MySQL 的 LIKE '%关键词%' 无法走索引,百万级数据全表扫描要几秒。
我引入 ElasticSearch:
1. 帖子发布时同步到 ES(用 Canal 或 MQ 增量同步)
2. 帖子内容用 IK 分词器切词,建立倒排索引
3. 搜索时按关键词查倒排索引,毫秒级返回
还做了敏感词过滤:敏感词库导入 ES,
新内容发布时先检索是否命中敏感词,命中则拦截审核。"
代码示例
// ES 全文检索
@RestController
public class SearchController {
@Autowired
private ElasticsearchClient esClient;
public List<Post> search(String keyword, int page, int size) {
// 多字段匹配:标题 + 内容
SearchRequest request = new SearchRequest.Builder()
.index("post")
.from((page - 1) * size)
.size(size)
.query(q -> q
.multiMatch(m -> m
.fields("title", "content")
.query(keyword)
)
)
.highlight(h -> h
.fields("title", f -> f)
.fields("content", f -> f)
)
.build();
return esClient.search(request, Post.class).hits().hits().stream()
.map(hit -> hit.source())
.collect(Collectors.toList());
}
}
面试题
Q1:什么是倒排索引?(高频 ⭐⭐⭐⭐)
答:倒排索引是“词 → 文档列表”的映射。分词后,记录每个词出现在哪些文档中。搜索时先查词对应的文档列表,再做交集,快速定位相关文档。相比正排索引(文档→词),倒排索引让“关键词找文档”变成 O(1) 的哈希查找。
Q2:ES 为什么搜索快?(高频 ⭐⭐⭐⭐)
答:
- 倒排索引:关键词直接定位文档列表,不用全表扫描
- 分词:把长文本切成词,按词建索引
- 分片并行:数据分布在多个分片,搜索并行执行
- 缓存:查询结果缓存、字段数据缓存
Q3:ES 和 MySQL 的搜索有什么区别?(中频 ⭐⭐⭐)
答:
- MySQL LIKE ‘%关键词%’:无法走 B+ 树索引,全表扫描,百万级要几秒
- ES 倒排索引:词→文档映射,毫秒级返回
- 两者互补:MySQL 存业务数据(事务),ES 存搜索数据(副本),通过 MQ/Canal 增量同步
第五章:Redisson 分布式锁
详见《数据库与缓存-讲解与面试题.md》2.6 节,这里补充中间件视角的要点。
面试题
Q1:分布式锁有哪些实现方式?(中频 ⭐⭐⭐)
答:
- Redis(Redisson):SET NX EX + 看门狗续期,最常用
- ZooKeeper:临时顺序节点 + Watch,强一致但性能低
- 数据库:唯一索引 / SELECT FOR UPDATE,性能最差
- 简历用 Redisson(性能好、实现简单)
Q2:Redisson 的看门狗是什么?(中频 ⭐⭐⭐)
答:Redisson 加锁时不指定 leaseTime,会启动看门狗定时任务,每 10 秒检查持有锁的线程是否存活,存活就续期到 30 秒。解决“业务执行超过锁过期时间导致锁提前释放”的问题。
面试速查表(中间件)
| 考点 | 一句话答案 |
|---|---|
| RocketMQ 事务消息 | 半消息→本地事务→Commit/Rollback→回查兜底 |
| 顺序消息 | 同 key 路由同 Queue + 单线程顺序消费 |
| 消息幂等 | 幂等键+唯一索引 / SETNX / 状态机 |
| 消息积压 | 扩容消费者 + 增加 Queue + 降级 |
| 死信队列 | 重试 16 次失败进 DLQ,人工处理 |
| Kafka 高吞吐 | 顺序写 + 零拷贝 + 页缓存 + 批量压缩 |
| Kafka vs RocketMQ | Kafka 吞吐高无事务消息,RocketMQ 适合业务 |
| ES 倒排索引 | 词→文档映射,关键词直接定位 |
| 分布式锁 | Redis SETNX / Redisson 看门狗续期 |
最后更新:2026-08-19