消息幂等性处理:面试高频考点解析与实战思路

2025年Java面试宝典重磅资源:
👉 点击获取《2025年Java面试高频题库》
提取码:9b3g(永久有效,建议立刻保存!)
为什么面试官总盯着消息幂等性问?
最近在面试鸭返利网的模拟面试中,10个候选人里有8个被问到了消息幂等性处理。这其实是分布式系统的命门问题!想象一下:支付回调重复触发导致用户被扣款两次,订单重复发货导致公司亏损... 没有消息幂等性保障,线上事故分分钟教你做人。
到底什么是消息幂等性?
简单说就是:同一条消息无论被消费多少次,结果都像只消费了一次。举个典型场景:
- 用户支付成功后,订单系统收到MQ推送
- 第一次处理:订单状态更新为"已支付"
- 网络抖动导致MQ重推(相同消息)
- 系统必须识别这是重复消息,不能再次更新状态
重复消费的五大元凶
- 生产者重试:发送超时自动重发
- 消费者超时:处理时间超过MQ等待时间
- Rebalance:Kafka分区重分配
- 手动ACK丢失:RabbitMQ手动确认失败
- 鬼影消息:RocketMQ的RETRY主题机制
六大实战解决方案
✅ 方案一:唯一业务ID(最常用)
-- 建表时增加唯一索引
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
order_no VARCHAR(32) UNIQUE -- 订单号唯一索引
);
处理逻辑:
// 伪代码示例
public void process(Message msg) {
String orderNo = msg.getOrderNo();
if(orderDao.existsByOrderNo(orderNo)) { // 关键判断!
log.warn("重复订单:{}", orderNo);
return;
}
createOrder(orderNo); // 执行业务
}
适用场景:订单、支付等有唯一标识的业务
✅ 方案二:状态机驱动
定义订单状态流转规则:
待支付 → 支付中 → 已支付 → 已发货
↖─────────────↙ (不允许逆向操作)
处理时先查当前状态:
Order order = orderDao.findByNo(orderNo);
if(order.getStatus() == PAID) { // 已支付状态直接跳过
return;
}

✅ 方案三:Token令牌机制
- 生产者生成全局唯一Token写入Redis
- 将Token随消息发送
- 消费者用SETNX原子操作获取锁:
SET token_lock 1 EX 30 NX - 获取成功才执行业务
✅ 方案四:去重表方案
单独建表记录已处理消息:
CREATE TABLE msg_processed (
msg_id VARCHAR(64) PRIMARY KEY,
created_at TIMESTAMP
);
处理前先插入记录,主键冲突即重复
✅ 方案五:分布式锁拦截
// 使用Redisson分布式锁
RLock lock = redisson.getLock("ORDER_"+orderNo);
if(lock.tryLock(0, 30, TimeUnit.SECONDS)) {
try {
processOrder();
} finally {
lock.unlock();
}
}
技术选型三大原则
- 性能损耗:去重表 VS Redis 内存操作
- 业务复杂度:简单业务用唯一ID,复杂状态用状态机
- 数据一致性要求:金融级场景推荐分布式锁+事务
踩坑血泪经验
- 千万别依赖数据库自增ID!分库分表时可能重复
- Redis过期时间必须大于业务最大处理时间
- Kafka的offset要等业务完成再提交
- MQ的msgId不可靠!必须用业务标识
🔥 面试鸭会员限时福利:
通过面试鸭返利网购买会员可返现25元!
覆盖BATJ等大厂真题库+实时更新,点击直达 → mianshiyafanli.com

高频面试题拆解
面试官:如果重复消费导致数据库被压垮怎么办?
参考答案:
- 前置拦截:在MQ消费前用Redis布隆过滤器过滤90%重复请求
- 快速失败:识别重复后立即ACK,避免阻塞线程
- 熔断降级:监控异常流量触发限流
- 兜底方案:用HBase等写扩散能力强的数据库做去重表
记住这个公式:
消息幂等性 = 业务标识 + 状态判断 + 原子操作
掌握这些套路,下次面试遇到消息幂等性问题,你绝对能对答如流!


