RabbitMQ 使用场景详解
每个场景都配有实际案例、完整代码、设计思路解析,方便理解和落地。
目录
- [RabbitMQ 基础概念](#RabbitMQ 基础概念)
- 场景一:异步处理
- 场景二:系统解耦
- 场景三:流量削峰
- 场景四:数据同步
- 场景五:分布式事务最终一致性
- 场景六:延迟任务(定时任务)
- 场景七:日志收集与监控
- [RabbitMQ 高级特性](#RabbitMQ 高级特性)
- [Spring Boot 整合 RabbitMQ](#Spring Boot 整合 RabbitMQ)
- 生产环境最佳实践
一、RabbitMQ 基础概念
1.1 核心组件
Producer(生产者) → 发送消息的应用
Consumer(消费者) → 接收消息的应用
Exchange(交换机) → 接收消息,根据规则路由到队列
Queue(队列) → 存储消息
Binding(绑定) → 交换机与队列的关联关系
Routing Key(路由键) → 生产者发送消息时指定的路由规则
1.2 消息流转过程
Producer → Exchange → Binding → Queue → Consumer
1. Producer 发送消息到 Exchange
2. Exchange 根据 Routing Key 和 Binding 规则
3. 将消息路由到对应的 Queue
4. Consumer 从 Queue 中拉取/推送消息
1.3 四种交换机类型
| 交换机类型 |
路由规则 |
典型场景 |
| Direct |
Routing Key 精确匹配 |
点对点通信 |
| Fanout |
广播到所有绑定的队列 |
广播通知 |
| Topic |
Routing Key 模式匹配(* #) |
灵活路由 |
| Headers |
根据消息头匹配 |
少用 |
1.4 为什么用 RabbitMQ 而不是直接调用?
直接调用的问题:
1. 同步阻塞:调用方需要等待被调用方返回
2. 强耦合:调用方依赖被调用方的接口、地址
3. 可用性:被调用方挂了,调用方也跟着挂
4. 性能瓶颈:被调用方处理慢,调用方也被拖慢
RabbitMQ 解决了什么:
1. 异步:发完消息立即返回,消费者异步处理
2. 解耦:生产者不关心谁消费,只管发消息
3. 可用性:消费者挂了,消息堆积在 MQ,恢复后继续消费
4. 削峰:MQ 缓冲高并发请求,消费者按能力消费
二、场景一:异步处理
2.1 案例背景
用户注册成功后,系统需要:
1. 发送注册确认短信(300ms)
2. 发送欢迎邮件(200ms)
3. 初始化用户积分(150ms)
4. 写入操作日志(100ms)
5. 推送注册优惠券(100ms)
同步方式总耗时:300 + 200 + 150 + 100 + 100 = 850ms
用户感知:点击注册后等了将近 1 秒才看到结果 → 体验差
2.2 设计思路
核心原则:只同步执行核心操作,非核心操作异步处理
什么是核心操作?
→ 用户注册本身(写入用户表)→ 必须同步,保证用户能注册成功
什么是非核心操作?
→ 发短信、发邮件、初始化积分、写日志、发优惠券
→ 这些操作即使失败,也不影响用户注册
→ 可以异步处理
为什么用 MQ 而不是线程池异步?
1. 线程池异步:如果服务重启,异步任务会丢失
2. MQ 异步:消息持久化在 MQ 中,服务重启后消费者可以继续消费
3. MQ 还支持重试、死信队列等高级特性
2.2 完整代码
生产者(注册服务)
// ========== 事件定义 ==========
@Data
public class UserRegisteredEvent {
private Long userId;
private String phone;
private String email;
private String username;
private Date registerTime;
public UserRegisteredEvent(User user) {
this.userId = user.getId();
this.phone = user.getPhone();
this.email = user.getEmail();
this.username = user.getUsername();
this.registerTime = user.getCreateTime();
}
}
// ========== 生产者 ==========
@Service
@Slf4j
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
public Result register(RegisterDTO dto) {
// 1. 参数校验
validate(dto);
// 2. 核心操作:创建用户(同步)
User user = new User();
user.setUsername(dto.getUsername());
user.setPassword(BCrypt.hashpw(dto.getPassword()));
user.setPhone(dto.getPhone());
user.setEmail(dto.getEmail());
user.setCreateTime(new Date());
userMapper.insert(user);
// 3. 非核心操作:发送 MQ 消息(异步)
UserRegisteredEvent event = new UserRegisteredEvent(user);
rabbitTemplate.convertAndSend(
"user.exchange", // 交换机
"user.registered", // 路由键
event // 消息体
);
log.info("用户注册成功,已发送MQ消息: userId={}", user.getId());
// 4. 立即返回(不等待异步操作)
return Result.success("注册成功", user.getId());
}
}
消费者(多个服务分别消费)
// ========== 消费者1:发送短信 ==========
@Component
@Slf4j
public class SmsConsumer {
@Autowired
private SmsService smsService;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "sms.register.queue", durable = "true"),
exchange = @Exchange(value = "user.exchange", type = "topic"),
key = "user.registered"
)
)
public void handleUserRegistered(UserRegisteredEvent event) {
log.info("收到用户注册消息,准备发送短信: phone={}", event.getPhone());
try {
smsService.send(event.getPhone(),
"尊敬的" + event.getUsername() + ",您已注册成功,欢迎加入!");
log.info("短信发送成功: phone={}", event.getPhone());
} catch (Exception e) {
log.error("短信发送失败: phone={}", event.getPhone(), e);
// 抛出异常,MQ 会重新投递(重试)
throw e;
}
}
}
// ========== 消费者2:发送邮件 ==========
@Component
@Slf4j
public class EmailConsumer {
@Autowired
private EmailService emailService;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "email.register.queue", durable = "true"),
exchange = @Exchange(value = "user.exchange", type = "topic"),
key = "user.registered"
)
)
public void handleUserRegistered(UserRegisteredEvent event) {
log.info("收到用户注册消息,准备发送邮件: email={}", event.getEmail());
emailService.send(event.getEmail(),
"欢迎注册",
"尊敬的" + event.getUsername() + ",欢迎加入我们的平台!");
}
}
// ========== 消费者3:初始化积分 ==========
@Component
@Slf4j
public class PointConsumer {
@Autowired
private PointService pointService;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "point.register.queue", durable = "true"),
exchange = @Exchange(value = "user.exchange", type = "topic"),
key = "user.registered"
)
)
public void handleUserRegistered(UserRegisteredEvent event) {
log.info("收到用户注册消息,初始化积分: userId={}", event.getUserId());
pointService.initPoints(event.getUserId(), 100); // 新用户送100积分
}
}
// ========== 消费者4:写入日志 ==========
@Component
@Slf4j
public class LogConsumer {
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "log.register.queue", durable = "true"),
exchange = @Exchange(value = "user.exchange", type = "topic"),
key = "user.registered"
)
)
public void handleUserRegistered(UserRegisteredEvent event) {
log.info("[注册日志] userId={}, username={}, time={}",
event.getUserId(), event.getUsername(), event.getRegisterTime());
}
}
// ========== 消费者5:发放优惠券 ==========
@Component
@Slf4j
public class CouponConsumer {
@Autowired
private CouponService couponService;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "coupon.register.queue", durable = "true"),
exchange = @Exchange(value = "user.exchange", type = "topic"),
key = "user.registered"
)
)
public void handleUserRegistered(UserRegisteredEvent event) {
log.info("收到用户注册消息,发放优惠券: userId={}", event.getUserId());
couponService.sendNewUserCoupon(event.getUserId());
}
}
2.3 时序对比
同步方式:
[注册]---300ms---[短信]---200ms---[邮件]---150ms---[积分]---100ms---[日志]---100ms---[返回]
总耗时:850ms
异步方式(MQ):
[注册]---30ms---[发MQ]---[返回]
总耗时:30ms(快了 28 倍)
同时:
[短信消费者]---300ms---[完成]
[邮件消费者]---200ms---[完成]
[积分消费者]---150ms---[完成]
[日志消费者]---100ms---[完成]
[优惠券消费者]---100ms---[完成]
所有异步任务并行执行,最慢 300ms 完成
三、场景二:系统解耦
3.1 案例背景
电商系统中,订单创建成功后需要通知多个下游系统:
1. 库存系统:扣减库存
2. 积分系统:增加积分
3. 物流系统:创建物流单
4. 通知系统:发送通知
5. 数据分析系统:记录数据
直接调用的问题:
- 订单系统要维护 5 个下游系统的地址、接口、超时时间
- 物流系统挂了 → 订单创建失败(不应该)
- 新增一个下游系统 → 要改订单系统代码(不应该)
- 下游系统接口变更 → 订单系统也要改(不应该)
3.2 设计思路
核心原则:订单系统只管发消息,不关心谁消费
生产者(订单系统):
- 只负责发送"订单创建成功"事件
- 不知道有哪些消费者
- 不关心消费者怎么处理
消费者(各下游系统):
- 各自订阅自己关心的消息
- 自己实现处理逻辑
- 独立部署、独立升级
解耦效果:
✅ 物流系统挂了 → 消息堆积在 MQ → 恢复后继续消费
✅ 新增数据分析系统 → 只需订阅 topic,订单系统不改
✅ 下游接口变更 → 只改消费者,订单系统不改
3.3 完整代码
订单系统(生产者)
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
@Transactional
public Result createOrder(CreateOrderDTO dto) {
// 1. 校验参数
validate(dto);
// 2. 创建订单(核心业务)
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(dto.getUserId());
order.setTotalAmount(dto.getTotalAmount());
order.setStatus("CREATED");
order.setCreateTime(new Date());
orderMapper.insert(order);
// 3. 保存订单商品
for (OrderItemDTO item : dto.getItems()) {
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProductId(item.getProductId());
orderItem.setQuantity(item.getQuantity());
orderItem.setPrice(item.getPrice());
orderItemMapper.insert(orderItem);
}
// 4. 发送订单创建事件(解耦下游系统)
OrderCreatedEvent event = OrderCreatedEvent.builder()
.orderId(order.getId())
.orderNo(order.getOrderNo())
.userId(order.getUserId())
.totalAmount(order.getTotalAmount())
.items(dto.getItems())
.createTime(order.getCreateTime())
.build();
rabbitTemplate.convertAndSend(
"order.exchange",
"order.created",
event
);
log.info("订单创建成功,已发送MQ消息: orderNo={}", order.getOrderNo());
return Result.success("下单成功", order.getOrderNo());
}
}
库存系统(消费者)
@Component
@Slf4j
public class StockConsumer {
@Autowired
private StockService stockService;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "stock.order.queue", durable = "true"),
exchange = @Exchange(value = "order.exchange", type = "topic"),
key = "order.created"
)
)
public void handleOrderCreated(OrderCreatedEvent event) {
log.info("收到订单创建消息,扣减库存: orderNo={}", event.getOrderNo());
for (OrderItemDTO item : event.getItems()) {
// 扣减库存
stockService.deduct(item.getProductId(), item.getQuantity());
log.info("库存扣减成功: productId={}, quantity={}",
item.getProductId(), item.getQuantity());
}
}
}
积分系统(消费者)
@Component
@Slf4j
public class PointConsumer {
@Autowired
private PointService pointService;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "point.order.queue", durable = "true"),
exchange = @Exchange(value = "order.exchange", type = "topic"),
key = "order.created"
)
)
public void handleOrderCreated(OrderCreatedEvent event) {
log.info("收到订单创建消息,增加积分: orderNo={}", event.getOrderNo());
// 订单金额的 10% 作为积分
int points = event.getTotalAmount().multiply(new BigDecimal("0.1")).intValue();
pointService.addPoints(event.getUserId(), points, "订单消费积分");
}
}
物流系统(消费者)
@Component
@Slf4j
public class LogisticsConsumer {
@Autowired
private LogisticsService logisticsService;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "logistics.order.queue", durable = "true"),
exchange = @Exchange(value = "order.exchange", type = "topic"),
key = "order.created"
)
)
public void handleOrderCreated(OrderCreatedEvent event) {
log.info("收到订单创建消息,创建物流单: orderNo={}", event.getOrderNo());
logisticsService.createShipment(event.getOrderId(), event.getOrderNo());
}
}
3.4 解耦效果对比
没有 MQ(强耦合):
订单系统 ──→ 库存系统
│
├──→ 积分系统
│
├──→ 物流系统 ← 物流系统挂了?订单创建失败!
│
├──→ 通知系统
│
└──→ 数据分析系统 ← 新增这个?要改订单系统代码!
有 MQ(解耦):
订单系统 ──→ MQ ──→ 库存系统(自己消费)
│
├──→ 积分系统(自己消费)
│
├──→ 物流系统(自己消费,挂了消息堆积)
│
├──→ 通知系统(自己消费)
│
└──→ 数据分析系统(自己订阅,订单系统不改)
四、场景三:流量削峰
4.1 案例背景
秒杀活动:iPhone 限量 1000 台,预计瞬间涌入 10 万用户
直接打数据库:
10 万请求/秒 → 数据库连接池(200 连接)→ 扛不住 → 服务崩溃
需要的效果:
10 万请求 → MQ 缓冲 → 数据库按能力消费(1000/秒)
→ 用户看到"排队中" → 后台慢慢处理
4.2 设计思路
核心原则:快速失败 + 异步处理
1. 接入层限流:Nginx/网关层限制请求量
2. Redis 预减库存:内存中扣减,响应极快(< 1ms)
3. 发送 MQ 消息:告诉后台"有一个订单要处理"
4. 返回"排队中":用户不用等待
5. 消费者异步处理:创建订单、扣库存、发通知
关键点:
- Redis 预减库存保证不会超卖
- MQ 消息保证请求不会丢失
- 消费者按数据库能力处理,不会压垮数据库
4.3 完整代码
秒杀接口(生产者)
@Service
@Slf4j
public class SeckillService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
private static final String STOCK_KEY_PREFIX = "seckill:stock:";
private static final String ORDER_KEY_PREFIX = "seckill:order:";
/**
* 秒杀下单(快速响应)
*/
public Result seckill(SeckillDTO dto) {
Long userId = dto.getUserId();
Long productId = dto.getProductId();
// 1. 判断是否已下单(防重复)
String orderKey = ORDER_KEY_PREFIX + productId + ":" + userId;
if (Boolean.TRUE.equals(redisTemplate.hasKey(orderKey))) {
return Result.fail(400, "您已参与过秒杀");
}
// 2. Redis 预减库存(内存操作,极快)
String stockKey = STOCK_KEY_PREFIX + productId;
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock < 0) {
// 库存不足,恢复扣减
redisTemplate.opsForValue().increment(stockKey);
return Result.fail(400, "已售罄");
}
// 3. 标记用户已下单
redisTemplate.opsForValue().set(orderKey, "1", 30, TimeUnit.MINUTES);
// 4. 发送 MQ 消息(异步创建订单)
SeckillMessage message = new SeckillMessage();
message.setUserId(userId);
message.setProductId(productId);
message.setQuantity(1);
rabbitTemplate.convertAndSend(
"seckill.exchange",
"seckill.order",
message
);
// 5. 返回"排队中"(不用等订单创建完成)
return Result.success("排队中,请稍后查看订单");
}
}
秒杀消费者
@Component
@Slf4j
public class SeckillConsumer {
@Autowired
private OrderService orderService;
@Autowired
private StockMapper stockMapper;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "seckill.order.queue", durable = "true"),
exchange = @Exchange(value = "seckill.exchange", type = "topic"),
key = "seckill.order"
)
)
public void handleSeckill(SeckillMessage message) {
log.info("收到秒杀消息: userId={}, productId={}",
message.getUserId(), message.getProductId());
try {
// 1. 创建订单
Order order = orderService.createSeckillOrder(
message.getUserId(),
message.getProductId(),
message.getQuantity()
);
// 2. 扣减数据库库存
stockMapper.deductStock(message.getProductId(), message.getQuantity());
// 3. 发送订单创建通知
rabbitTemplate.convertAndSend(
"order.exchange",
"order.created",
order
);
log.info("秒杀订单创建成功: orderNo={}", order.getOrderNo());
} catch (Exception e) {
log.error("秒杀订单创建失败", e);
// 库存恢复
String stockKey = "seckill:stock:" + message.getProductId();
redisTemplate.opsForValue().increment(stockKey);
// 移除用户下单标记
String orderKey = "seckill:order:" + message.getProductId() + ":" + message.getUserId();
redisTemplate.delete(orderKey);
throw e; // 抛出异常,MQ 重试
}
}
}
4.4 削峰效果
不削峰:
10万请求/秒 → 数据库(200连接)→ 数据库崩溃 → 所有用户失败
削峰后:
10万请求/秒 → Redis 预减库存(< 1ms 响应)
→ MQ(缓冲 10 万消息)
→ 消费者(1000/秒)→ 数据库(稳定)
用户视角:
点击秒杀 → 看到"排队中"(< 100ms 响应)
10 秒后 → 订单创建成功,收到通知
五、场景四:数据同步
5.1 案例背景
电商系统数据同步需求:
1. MySQL 数据变更 → 同步到 ES(搜索)
2. MySQL 数据变更 → 同步到 Redis(缓存)
3. MySQL 数据变更 → 同步到数据仓库(分析)
4. 用户数据变更 → 同步到 CRM 系统
传统方式:每个下游都直接查 MySQL → MySQL 压力大
5.2 设计思路
核心原则:基于 Binlog 监听 + MQ 实现数据同步
架构:
MySQL Binlog → Canal → RabbitMQ
├──→ ES 消费者(更新搜索索引)
├──→ Redis 消费者(更新缓存)
└──→ 数据仓库消费者(ETL)
优势:
- Canal 监听 Binlog 对业务代码零侵入
- 一次 Binlog 变更,多个消费者并行处理
- 消费失败可以重试,保证最终一致性
5.3 完整代码
Canal 监听 Binlog 发送 MQ
// Canal 服务端配置(canal.properties)
// canal.instance.master.address=127.0.0.1:3306
// canal.mq.servers=127.0.0.1:5672
// canal.mq.topic=canal.binlog.topic
// Canal 会自动将 MySQL Binlog 变更发送到 MQ
// 消息格式:
{
"database": "ecommerce",
"table": "product",
"type": "UPDATE",
"data": [
{
"id": 1001,
"name": "iPhone 15",
"price": 5999.00,
"stock": 100
}
],
"timestamp": 1700000000000
}
ES 消费者(更新搜索索引)
@Component
@Slf4j
public class EsSyncConsumer {
@Autowired
private ProductEsRepository productEsRepository;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "canal.product.es.queue", durable = "true"),
exchange = @Exchange(value = "canal.exchange", type = "topic"),
key = "canal.product"
)
)
public void handleBinlog(CanalMessage message) {
String tableName = message.getTable();
if (!"product".equals(tableName)) {
return;
}
for (Map<String, Object> rowData : message.getData()) {
ProductEsDoc doc = new ProductEsDoc();
doc.setId(Long.valueOf(rowData.get("id").toString()));
doc.setName(rowData.get("name").toString());
doc.setPrice(new BigDecimal(rowData.get("price").toString()));
doc.setStock(Integer.valueOf(rowData.get("stock").toString()));
String type = message.getType();
switch (type) {
case "INSERT":
case "UPDATE":
productEsRepository.save(doc);
log.info("ES 更新商品: id={}", doc.getId());
break;
case "DELETE":
productEsRepository.deleteById(doc.getId());
log.info("ES 删除商品: id={}", doc.getId());
break;
}
}
}
}
Redis 消费者(更新缓存)
@Component
@Slf4j
public class RedisSyncConsumer {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "canal.product.redis.queue", durable = "true"),
exchange = @Exchange(value = "canal.exchange", type = "topic"),
key = "canal.product"
)
)
public void handleBinlog(CanalMessage message) {
if (!"product".equals(message.getTable())) {
return;
}
for (Map<String, Object> rowData : message.getData()) {
String productId = rowData.get("id").toString();
String key = "product:" + productId;
switch (message.getType()) {
case "INSERT":
case "UPDATE":
redisTemplate.opsForValue().set(key, JSON.toJSONString(rowData), 30, TimeUnit.MINUTES);
log.info("Redis 更新商品缓存: id={}", productId);
break;
case "DELETE":
redisTemplate.delete(key);
log.info("Redis 删除商品缓存: id={}", productId);
break;
}
}
}
}
六、场景五:分布式事务最终一致性
6.1 案例背景
跨服务事务:用户下单扣款 + 商家收款
订单服务(MySQL-A):扣减用户余额
商家服务(MySQL-B):增加商家余额
问题:
- 两个数据库不是同一个事务
- 扣款成功,收款失败 → 用户的钱去哪了?
- 传统分布式事务(2PC)性能差,不适合高并发
6.2 设计思路(本地消息表 + MQ)
核心原则:利用 MQ 的可靠投递实现最终一致性
流程:
1. 订单服务:扣款 + 写消息表(同一个事务)
2. 定时任务:扫描消息表,发送到 MQ
3. 商家服务:收到消息,增加余额
4. 商家服务:处理成功后回调确认
5. 订单服务:收到确认,更新消息状态
保证:
- 扣款和消息写入是同一个事务 → 要么都成功,要么都失败
- MQ 消息持久化 → 不会丢失
- 消费失败重试 → 最终一定会处理成功
- 幂等处理 → 重复消费不会出错
6.3 完整代码
消息表
CREATE TABLE outbox_message (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
topic VARCHAR(100) NOT NULL,
routing_key VARCHAR(100) NOT NULL,
message TEXT NOT NULL,
status INT DEFAULT 0 COMMENT '0-待发送 1-已发送 2-已确认 3-发送失败',
retry_count INT DEFAULT 0,
next_retry_time DATETIME,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status_retry (status, next_retry_time)
);
订单服务(生产者)
@Service
@Slf4j
public class PaymentService {
@Autowired
private UserAccountMapper userAccountMapper;
@Autowired
private OutboxMessageMapper outboxMessageMapper;
/**
* 用户支付(扣款 + 写消息表,同一个事务)
*/
@Transactional
public Result pay(PayDTO dto) {
// 1. 扣减用户余额
int affected = userAccountMapper.deductBalance(dto.getUserId(), dto.getAmount());
if (affected == 0) {
return Result.fail(400, "余额不足");
}
// 2. 写入消息表(同一个事务,保证原子性)
PayMessage message = new PayMessage();
message.setPaymentId(dto.getPaymentId());
message.setUserId(dto.getUserId());
message.setMerchantId(dto.getMerchantId());
message.setAmount(dto.getAmount());
OutboxMessage outbox = new OutboxMessage();
outbox.setTopic("payment.exchange");
outbox.setRoutingKey("payment.success");
outbox.setMessage(JSON.toJSONString(message));
outbox.setStatus(0); // 待发送
outbox.setNextRetryTime(new Date());
outboxMessageMapper.insert(outbox);
log.info("扣款成功,消息已写入 outbox: paymentId={}", dto.getPaymentId());
return Result.success("支付成功");
}
}
// ========== 定时任务:扫描消息表发送 MQ ==========
@Component
@Slf4j
public class OutboxScheduler {
@Autowired
private OutboxMessageMapper outboxMessageMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
@Scheduled(fixedDelay = 1000) // 每秒执行
public void sendPendingMessages() {
// 查询待发送的消息(最多 100 条)
List<OutboxMessage> messages = outboxMessageMapper.selectPending(100);
for (OutboxMessage outbox : messages) {
try {
// 发送 MQ 消息
rabbitTemplate.convertAndSend(
outbox.getTopic(),
outbox.getRoutingKey(),
outbox.getMessage()
);
// 更新状态为已发送
outbox.setStatus(1);
outboxMessageMapper.updateStatus(outbox);
} catch (Exception e) {
log.error("消息发送失败: id={}", outbox.getId(), e);
// 增加重试次数
outbox.setRetryCount(outbox.getRetryCount() + 1);
outbox.setNextRetryTime(calculateNextRetry(outbox.getRetryCount()));
outboxMessageMapper.updateRetry(outbox);
}
}
}
private Date calculateNextRetry(int retryCount) {
// 指数退避:1s, 2s, 4s, 8s, 16s, 30s(最大)
long delay = Math.min((long) Math.pow(2, retryCount) * 1000, 30000);
return new Date(System.currentTimeMillis() + delay);
}
}
商家服务(消费者)
@Component
@Slf4j
public class PaymentConsumer {
@Autowired
private MerchantAccountMapper merchantAccountMapper;
@Autowired
private PaymentRecordMapper paymentRecordMapper;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "merchant.payment.queue", durable = "true"),
exchange = @Exchange(value = "payment.exchange", type = "topic"),
key = "payment.success"
)
)
public void handlePaymentSuccess(String messageJson) {
PayMessage message = JSON.parseObject(messageJson, PayMessage.class);
// 幂等检查:是否已处理过
PaymentRecord record = paymentRecordMapper.selectByPaymentId(message.getPaymentId());
if (record != null && record.getStatus() == 1) {
log.info("支付消息已处理,跳过: paymentId={}", message.getPaymentId());
return;
}
try {
// 1. 增加商家余额
merchantAccountMapper.addBalance(message.getMerchantId(), message.getAmount());
// 2. 记录处理结果
if (record == null) {
record = new PaymentRecord();
record.setPaymentId(message.getPaymentId());
record.setMerchantId(message.getMerchantId());
record.setAmount(message.getAmount());
record.setStatus(1); // 已处理
record.setHandleTime(new Date());
paymentRecordMapper.insert(record);
} else {
record.setStatus(1);
record.setHandleTime(new Date());
paymentRecordMapper.updateStatus(record);
}
log.info("商家收款成功: merchantId={}, amount={}",
message.getMerchantId(), message.getAmount());
} catch (Exception e) {
log.error("商家收款失败", e);
throw e; // 抛出异常,MQ 重试
}
}
}
七、场景六:延迟任务(定时任务)
7.1 案例背景
电商系统中的延迟任务:
1. 订单 30 分钟未支付 → 自动取消
2. 退款申请 72 小时未处理 → 自动同意
3. 优惠券到期前 1 天 → 发送提醒
4. 注册后 24 小时未认证 → 发送提醒
5. 发货后 15 天 → 自动确认收货
传统方式:定时任务扫描数据库
SELECT * FROM orders WHERE status='UNPAID' AND create_time < NOW() - 30 MINUTE
→ 数据库压力大,有延迟,精度不够
7.2 设计思路(RabbitMQ 延迟插件)
核心原则:消息延迟投递
使用 RabbitMQ 延迟插件(rabbitmq_delayed_message_exchange)
流程:
1. 创建订单时,发送延迟消息(30 分钟后投递)
2. 消费者收到消息 → 检查订单状态
3. 如果仍未支付 → 取消订单
4. 如果已支付 → 忽略消息
优势:
- 不用扫描数据库,减轻 DB 压力
- 延迟精度高(秒级)
- 消息持久化,服务重启不丢失
7.3 完整代码
配置延迟交换机
@Configuration
public class RabbitConfig {
// 延迟交换机
@Bean
public TopicExchange delayedExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "topic");
return new TopicExchange("order.delayed.exchange", true, false, args);
}
// 延迟队列
@Bean
public Queue delayedQueue() {
return QueueBuilder.durable("order.delayed.queue").build();
}
// 绑定
@Bean
public Binding delayedBinding() {
return BindingBuilder.bind(delayedQueue())
.to(delayedExchange())
.with("order.delayed");
}
}
生产者(创建订单时发送延迟消息)
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
public Result createOrder(CreateOrderDTO dto) {
// 1. 创建订单
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(dto.getUserId());
order.setTotalAmount(dto.getTotalAmount());
order.setStatus("UNPAID");
order.setCreateTime(new Date());
orderMapper.insert(order);
// 2. 发送延迟消息(30 分钟后投递)
OrderDelayMessage message = new OrderDelayMessage();
message.setOrderId(order.getId());
message.setOrderNo(order.getOrderNo());
rabbitTemplate.convertAndSend(
"order.delayed.exchange",
"order.delayed",
message,
msg -> {
// 设置延迟时间(毫秒)
msg.getMessageProperties().setDelay(30 * 60 * 1000); // 30 分钟
return msg;
}
);
log.info("订单创建成功,已发送延迟消息: orderNo={}, 30分钟后检查支付状态",
order.getOrderNo());
return Result.success("下单成功", order.getOrderNo());
}
}
消费者(检查订单状态)
@Component
@Slf4j
public class OrderDelayConsumer {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
@RabbitListener(queues = "order.delayed.queue")
public void handleOrderDelay(OrderDelayMessage message) {
log.info("收到延迟消息,检查订单状态: orderNo={}", message.getOrderNo());
// 查询订单
Order order = orderMapper.selectById(message.getOrderId());
if (order == null) {
log.warn("订单不存在: orderNo={}", message.getOrderNo());
return;
}
// 检查状态
if ("UNPAID".equals(order.getStatus())) {
// 未支付,取消订单
order.setStatus("CANCELLED");
order.setCancelReason("超时未支付");
order.setCancelTime(new Date());
orderMapper.updateById(order);
log.info("订单超时取消: orderNo={}", message.getOrderNo());
// 发送订单取消事件(通知库存恢复等)
rabbitTemplate.convertAndSend(
"order.exchange",
"order.cancelled",
new OrderCancelledEvent(order)
);
} else {
log.info("订单已支付,无需取消: orderNo={}, status={}",
message.getOrderNo(), order.getStatus());
}
}
}
八、场景七:日志收集与监控
8.1 案例背景
微服务架构下的日志需求:
1. 多个服务的日志需要集中收集
2. 日志需要实时分析(异常监控、报警)
3. 日志需要归档存储(ELK)
4. 业务日志需要实时统计(QPS、错误率)
传统方式:每个服务直接写 ES → ES 压力大,耦合
8.2 设计思路
架构:
各服务 → RabbitMQ → 日志消费者 → ES(存储+搜索)
→ 实时分析消费者 → 监控报警
→ 统计消费者 → Grafana 展示
优势:
- 服务只管发消息,不关心日志存储
- 日志消费可以按需扩展
- MQ 缓冲突发日志量
8.3 完整代码
日志生产者
@Component
@Slf4j
public class LogProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendLog(String service, String level, String message, Object data) {
LogEvent event = new LogEvent();
event.setId(UUID.randomUUID().toString());
event.setService(service);
event.setLevel(level);
event.setMessage(message);
event.setData(data);
event.setTimestamp(new Date());
// 根据日志级别路由到不同队列
String routingKey = "log." + level.toLowerCase();
rabbitTemplate.convertAndSend("log.exchange", routingKey, event);
}
public void info(String service, String message, Object data) {
sendLog(service, "INFO", message, data);
}
public void error(String service, String message, Object data) {
sendLog(service, "ERROR", message, data);
}
}
日志消费者(写入 ES)
@Component
@Slf4j
public class LogEsConsumer {
@Autowired
private LogEsRepository logEsRepository;
@RabbitListener(queues = "log.all.queue")
public void handleLog(LogEvent event) {
LogEsDoc doc = new LogEsDoc();
doc.setId(event.getId());
doc.setService(event.getService());
doc.setLevel(event.getLevel());
doc.setMessage(event.getMessage());
doc.setData(JSON.toJSONString(event.getData()));
doc.setTimestamp(event.getTimestamp());
logEsRepository.save(doc);
}
}
九、RabbitMQ 高级特性
9.1 消息确认机制(Producer Confirm)
// 确保消息成功发送到 MQ
@Configuration
public class RabbitConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// 开启确认模式
template.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("消息发送到交换机成功: {}", correlationData);
} else {
log.error("消息发送到交换机失败: {}, cause: {}", correlationData, cause);
// 重试或记录到数据库
}
});
// 开启退回模式(消息无法路由到队列时回调)
template.setReturnsCallback(returned -> {
log.error("消息无法路由到队列: exchange={}, routingKey={}, replyCode={}",
returned.getExchange(), returned.getRoutingKey(), returned.getReplyCode());
});
return template;
}
}
9.2 消息消费确认(Manual ACK)
@RabbitListener(queues = "order.queue")
public void handleOrder(OrderDTO dto, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) {
try {
// 业务处理
orderService.process(dto);
// 手动确认(告诉 MQ 消费成功)
channel.basicAck(tag, false);
} catch (Exception e) {
log.error("消费失败", e);
try {
// 拒绝消息,重新入队(MQ 会重新投递)
channel.basicNack(tag, false, true);
// 或者拒绝消息,不重新入队(进入死信队列)
// channel.basicNack(tag, false, false);
} catch (IOException ex) {
log.error(" nack 失败", ex);
}
}
}
9.3 死信队列(Dead Letter Queue)
// 什么消息会进入死信队列?
// 1. 消费者拒绝消息(basic.reject / basic.nack)且 requeue=false
// 2. 消息 TTL 过期
// 3. 队列达到最大长度
// 配置死信交换机
@Bean
public Queue orderQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "dlx.exchange");
args.put("x-dead-letter-routing-key", "dlx.order");
return QueueBuilder.durable("order.queue").withArguments(args).build();
}
@Bean
public Queue deadLetterQueue() {
return QueueBuilder.durable("dlx.order.queue").build();
}
// 死信消费者(人工处理)
@Component
@Slf4j
public class DeadLetterConsumer {
@RabbitListener(queues = "dlx.order.queue")
public void handleDeadLetter(String message) {
log.error("收到死信消息: {}", message);
// 1. 记录到数据库
// 2. 发送报警
// 3. 人工处理
}
}
9.4 消息 TTL(过期时间)
// 方式1:队列级别(所有消息统一过期时间)
@Bean
public Queue ttlQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 30000); // 30 秒
return QueueBuilder.durable("ttl.queue").withArguments(args).build();
}
// 方式2:消息级别(单条消息过期时间)
rabbitTemplate.convertAndSend("exchange", "routing.key", message, msg -> {
msg.getMessageProperties().setExpiration("60000"); // 60 秒
return msg;
});
9.5 消息持久化
// 交换机持久化
@Bean
public TopicExchange durableExchange() {
return new TopicExchange("durable.exchange", true, false); // durable=true
}
// 队列持久化
@Bean
public Queue durableQueue() {
return QueueBuilder.durable("durable.queue").build(); // durable=true
}
// 消息持久化
rabbitTemplate.convertAndSend("exchange", "routing.key", message, msg -> {
msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return msg;
});
十、Spring Boot 整合 RabbitMQ
10.1 依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
# application.yml
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
# 连接池
cache:
channel:
size: 10
# 消息确认
publisher-confirm-type: correlated
publisher-returns: true
# 消费者
listener:
simple:
acknowledge-mode: manual
concurrency: 5
max-concurrency: 10
prefetch: 10
retry:
enabled: true
initial-interval: 1000
max-attempts: 3
multiplier: 2
10.2 配置类
@Configuration
public class RabbitMqConfig {
// 通用交换机
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("app.exchange", true, false);
}
// 通用队列
@Bean
public Queue orderQueue() {
return QueueBuilder.durable("order.queue").build();
}
// 绑定
@Bean
public Binding orderBinding(Queue orderQueue, TopicExchange topicExchange) {
return BindingBuilder.bind(orderQueue).to(topicExchange).with("order.*");
}
}
10.3 工具类封装
@Component
@Slf4j
public class MqProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息(可靠投递)
*/
public <T> boolean send(String exchange, String routingKey, T message) {
try {
CorrelationData correlationData = new CorrelationData(
UUID.randomUUID().toString()
);
rabbitTemplate.convertAndSend(
exchange,
routingKey,
message,
correlationData
);
log.info("消息发送成功: exchange={}, routingKey={}, id={}",
exchange, routingKey, correlationData.getId());
return true;
} catch (Exception e) {
log.error("消息发送失败: exchange={}, routingKey={}", exchange, routingKey, e);
return false;
}
}
/**
* 发送延迟消息
*/
public <T> boolean sendDelay(String exchange, String routingKey, T message, long delayMs) {
try {
rabbitTemplate.convertAndSend(
exchange,
routingKey,
message,
msg -> {
msg.getMessageProperties().setDelay((int) delayMs);
return msg;
}
);
return true;
} catch (Exception e) {
log.error("延迟消息发送失败", e);
return false;
}
}
}
十一、生产环境最佳实践
11.1 消息幂等性
// 问题:MQ 可能重复投递消息,消费者需要幂等处理
// 方案1:唯一索引
// 数据库唯一索引防止重复插入
ALTER TABLE orders ADD UNIQUE INDEX uk_order_no (order_no);
// 方案2:Redis 幂等标记
public boolean processMessage(String messageId) {
String key = "mq:processed:" + messageId;
// SETNX(原子操作)
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "1", 24, TimeUnit.HOURS);
if (Boolean.FALSE.equals(success)) {
return false; // 已处理过
}
// 业务处理
doProcess();
return true;
}
// 方案3:数据库状态检查
public void handlePayment(PayMessage message) {
// 检查是否已处理
PaymentRecord record = paymentRecordMapper.selectByPaymentId(message.getPaymentId());
if (record != null && record.getStatus() == 1) {
return; // 已处理,跳过
}
// 处理逻辑...
}
11.2 重试与降级
// 消费失败重试(Spring Retry)
@RabbitListener(queues = "order.queue")
@Retryable(
value = {Exception.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public void handleOrder(OrderDTO dto) {
orderService.process(dto);
}
// 重试耗尽后的降级处理
@Recover
public void handleOrderRecover(Exception e, OrderDTO dto) {
log.error("重试耗尽,降级处理: {}", dto, e);
// 1. 记录到数据库
// 2. 发送报警
// 3. 人工处理
failedMessageService.save(dto);
}
11.3 监控与报警
// 监控队列积压
@Component
@Slf4j
public class QueueMonitor {
@Autowired
private RabbitAdmin rabbitAdmin;
@Scheduled(fixedDelay = 10000) // 每 10 秒检查
public void checkQueueDepth() {
String[] queues = {"order.queue", "stock.queue", "payment.queue"};
for (String queueName : queues) {
Long messageCount = rabbitAdmin.getQueueInfo(queueName).getMessageCount();
if (messageCount > 10000) {
// 队列积压严重,发送报警
alarmService.send("队列积压报警",
queueName + " 积压 " + messageCount + " 条消息");
}
}
}
}
11.4 队列命名规范
格式:{服务名}.{业务类型}.{版本}
示例:
order.service.create.v1 → 订单创建
order.service.cancel.v1 → 订单取消
stock.service.deduct.v1 → 库存扣减
payment.service.success.v1 → 支付成功
notification.sms.v1 → 短信通知
notification.email.v1 → 邮件通知
log.collect.v1 → 日志收集
dlx.order.service.v1 → 死信队列
11.5 生产环境检查清单
□ 交换机、队列、消息都设置了持久化(durable=true)
□ 开启了生产者确认(publisher-confirm-type: correlated)
□ 消费者使用手动确认(acknowledge-mode: manual)
□ 配置了死信队列(处理失败的消息)
□ 消费者实现了幂等处理
□ 设置了合理的 prefetch(预取数量)
□ 监控了队列积压情况
□ 配置了消息报警
□ 消费者有重试和降级机制
□ 队列命名规范统一
附录:场景速查表
| 场景 |
核心作用 |
交换机类型 |
关键配置 |
| 异步处理 |
提升响应速度 |
Topic |
多个消费者各自消费 |
| 系统解耦 |
降低耦合度 |
Topic |
生产者不关心消费者 |
| 流量削峰 |
保护数据库 |
Direct |
消费者控制并发数 |
| 数据同步 |
多端数据一致 |
Topic |
Canal + Binlog |
| 分布式事务 |
最终一致性 |
Direct |
本地消息表 + 定时任务 |
| 延迟任务 |
定时触发 |
Delayed |
延迟插件 |
| 日志收集 |
集中管理 |
Fanout/Topic |
多消费者并行处理 |