中间件 — 小白讲解 + 面试题精解

中间件 — 小白讲解 + 面试题精解

定位:假设你只会用 @Autowired 注入一个 Template 发消息,所有概念从零讲起,配代码示例 + 面试题。 覆盖: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 事务消息的原理?(超高频 ⭐⭐⭐⭐⭐)

简历直接写了,必须会答!

答:两阶段 + 回查:

  1. Producer 发半消息(消费者不可见)
  2. Broker 存储半消息后返回确认
  3. Producer 执行本地事务
  4. 本地事务成功 → Commit(半消息转普通消息);失败 → Rollback(删除半消息)
  5. Producer 崩溃未确认 → Broker 回查本地事务状态兜底

Q2:事务消息的回查机制解决了什么问题?(高频 ⭐⭐⭐⭐)

答:解决了“Producer 执行本地事务后崩溃,没来得及告诉 Broker 结果”的问题。半消息会一直挂起(消费者收不到),Broker 定期回查 Producer 的本地事务状态,根据结果决定 Commit 还是 Rollback,避免消息永久挂起。

Q3:事务消息的局限?(中频 ⭐⭐⭐)

答:

  1. 只保证“本地事务 + 消息发送”的一致性,不保证下游消费一定成功(消费失败要靠重试/死信)
  2. 半消息会占用存储,需要定期清理超时的半消息
  3. 回查逻辑需要业务方实现,不能自动判断

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 怎么保证顺序消息?(高频 ⭐⭐⭐⭐)

答:

  1. 生产端:用 MessageQueueSelector 把相同业务 key 的消息路由到同一个 Queue
  2. 消费端:同一个 Queue 的消息用单线程顺序消费(ConsumeMode.ORDERLY)
  3. 本质是局部顺序:同 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:消息幂等怎么设计?(超高频 ⭐⭐⭐⭐⭐)

简历直接写了,必须会答!

答:

  1. 幂等键 + 唯一索引:用业务唯一标识(userId+activityId+date)做数据库唯一索引,重复插入报冲突就跳过(简历方案)
  2. Redis SETNX:消费前用幂等键 SETNX,成功才处理
  3. 状态机:检查业务状态,已处理则跳过
  4. 去重表:单独一张表记录已处理的幂等键

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:消息积压怎么处理?(高频 ⭐⭐⭐⭐)

答:

  1. 先定位是消费慢还是生产太快
  2. 消费慢:扩容消费者实例(受 Queue 数限制)、优化消费逻辑(批量、异步、缓存)
  3. 生产太快:限流生产端、削峰(大促前提前扩容)
  4. 应急:临时提高消费并行度,或写临时程序快速消费积压消息
  5. 治本:增加 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 为什么吞吐量这么高?(高频 ⭐⭐⭐⭐)

答:

  1. 顺序写磁盘(日志追加),比随机写快几个数量级
  2. 零拷贝(sendfile),数据不经过用户态拷贝
  3. 页缓存,消息先写内存由 OS 异步刷盘
  4. 批量发送 + 压缩,减少网络和磁盘开销

Q2:Kafka 的分区有什么用?(高频 ⭐⭐⭐⭐)

答:

  1. 并行:多个分区可以被多个消费者并行消费,提升吞吐
  2. 有序:同一分区内消息有序
  3. 扩容:增加分区可以提升并行度
  4. 分区数 ≥ 消费者数,消费者才能全部并行工作

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 为什么搜索快?(高频 ⭐⭐⭐⭐)

答:

  1. 倒排索引:关键词直接定位文档列表,不用全表扫描
  2. 分词:把长文本切成词,按词建索引
  3. 分片并行:数据分布在多个分片,搜索并行执行
  4. 缓存:查询结果缓存、字段数据缓存

Q3:ES 和 MySQL 的搜索有什么区别?(中频 ⭐⭐⭐)

答:

  • MySQL LIKE ‘%关键词%’:无法走 B+ 树索引,全表扫描,百万级要几秒
  • ES 倒排索引:词→文档映射,毫秒级返回
  • 两者互补:MySQL 存业务数据(事务),ES 存搜索数据(副本),通过 MQ/Canal 增量同步

第五章:Redisson 分布式锁

详见《数据库与缓存-讲解与面试题.md》2.6 节,这里补充中间件视角的要点。

面试题

Q1:分布式锁有哪些实现方式?(中频 ⭐⭐⭐)

答:

  1. Redis(Redisson):SET NX EX + 看门狗续期,最常用
  2. ZooKeeper:临时顺序节点 + Watch,强一致但性能低
  3. 数据库:唯一索引 / 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