RabbitMQ 使用场景详解

RabbitMQ 使用场景详解

每个场景都配有实际案例、完整代码、设计思路解析,方便理解和落地。


目录

  1. [RabbitMQ 基础概念](#RabbitMQ 基础概念)
  2. 场景一:异步处理
  3. 场景二:系统解耦
  4. 场景三:流量削峰
  5. 场景四:数据同步
  6. 场景五:分布式事务最终一致性
  7. 场景六:延迟任务(定时任务)
  8. 场景七:日志收集与监控
  9. [RabbitMQ 高级特性](#RabbitMQ 高级特性)
  10. [Spring Boot 整合 RabbitMQ](#Spring Boot 整合 RabbitMQ)
  11. 生产环境最佳实践

一、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 完整代码

生产者(注册服务)
java 复制代码
// ========== 事件定义 ==========
@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());
    }
}
消费者(多个服务分别消费)
java 复制代码
// ========== 消费者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 完整代码

订单系统(生产者)
java 复制代码
@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());
    }
}
库存系统(消费者)
java 复制代码
@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());
        }
    }
}
积分系统(消费者)
java 复制代码
@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, "订单消费积分");
    }
}
物流系统(消费者)
java 复制代码
@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 完整代码

秒杀接口(生产者)
java 复制代码
@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("排队中,请稍后查看订单");
    }
}
秒杀消费者
java 复制代码
@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
java 复制代码
// 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 消费者(更新搜索索引)
java 复制代码
@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 消费者(更新缓存)
java 复制代码
@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 完整代码

消息表
sql 复制代码
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)
);
订单服务(生产者)
java 复制代码
@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);
    }
}
商家服务(消费者)
java 复制代码
@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 完整代码

配置延迟交换机
java 复制代码
@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");
    }
}
生产者(创建订单时发送延迟消息)
java 复制代码
@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());
    }
}
消费者(检查订单状态)
java 复制代码
@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 完整代码

日志生产者
java 复制代码
@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)
java 复制代码
@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)

java 复制代码
// 确保消息成功发送到 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)

java 复制代码
@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)

java 复制代码
// 什么消息会进入死信队列?
// 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(过期时间)

java 复制代码
// 方式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 消息持久化

java 复制代码
// 交换机持久化
@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 依赖配置

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
yaml 复制代码
# 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 配置类

java 复制代码
@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 工具类封装

java 复制代码
@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 消息幂等性

java 复制代码
// 问题: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 重试与降级

java 复制代码
// 消费失败重试(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 监控与报警

java 复制代码
// 监控队列积压
@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 多消费者并行处理
相关推荐
宁渡AI大模型2 小时前
河南宁渡科技有限公司|宁渡课堂 AI 全栈面试分享,AI 应用开发高频考点汇总
java·c++·人工智能·python·深度学习·神经网络·rag
代码什么用2 小时前
JSON一文速通
java·json
Lyyaoo.2 小时前
【二分查找】【中等】搜索二维矩阵/排序数组的第一位和最后位置/搜索旋转排序数组/旋转排序数组中的最小值
java·数据结构·算法
shengjk12 小时前
AI会让程序员失业吗?200年前的手工织工已经给出了答案
后端
土司大王2 小时前
LeetCode hot100——153.寻找旋转排序数组中的最小值:Java 二分模板与 O(log n) 分析
java·算法·leetcode
磐链科技2 小时前
交易所开发中的合规与安全:KYC、AML、冷热钱包设计要点
java·区块链
盖伦发发3 小时前
MySQL InnoDB事务完整教程|ACID、MVCC、隔离级别、事务控制
经验分享·后端·spring·软件工程
一条泥憨鱼3 小时前
苍穹外卖【day09| 对订单的各种操作】
后端·苍穹外卖
码农飞哥3 小时前
企业级RAG系统架构详解
java·人工智能·ai编程·rag·ai应用