偷偷在项目里用了RabbitMQ,因为真的太好用了

0 阅读4分钟

上周和一个前同事聊天,说他们团队评审技术方案的时候,有人提议引入RabbitMQ,结果被技术主管当场否决:“咱们这小系统,用啥消息队列,直接同步调不行吗?别过度设计!”

结果这位朋友周末偷偷把RabbitMQ集成进了自己的模块

“我知道不该这么干,但实在忍不住啊。”他这么跟我说。

一、我们到底在忍受什么?

场景

假设你负责一个电商系统的优惠券发放模块。用户下单成功后,需要:

  1. 发放积分
  2. 发放优惠券
  3. 发送短信通知
  4. 更新用户标签
  5. 记录行为日志

传统做法(同步调用版):

public void afterOrderSuccess(Order order) {
    // 1. 发放积分
    pointService.addPoints(order.getUserId(), 100);
    
    // 2. 发放优惠券
    couponService.sendWelcomeCoupon(order.getUserId());
    
    // 3. 发送短信
    smsService.sendOrderSuccessSms(order.getPhone());
    
    // 4. 更新用户标签
    userTagService.updatePurchaseTag(order.getUserId());
    
    // 5. 记录日志
    logService.saveOrderLog(order);
    
    // 万一这里抛异常,前面都白干了?
}

问题:

  1. 性能瓶颈:每个调用都要等上个执行完,用户得等5-10秒
  2. 稳定性差:发短信服务挂了,整个流程就卡住了
  3. 难以维护:加个新功能就要改这坨代码
  4. 扩展困难:想提升性能?对不起,得重构

二、RabbitMQ 怎么解决问题的?

还是上面那个例子,用了RabbitMQ之后:

public void afterOrderSuccess(Order order) {
    // 只需要发个消息到快递柜
    rabbitTemplate.convertAndSend("order.success", order);
    // 完事,耗时50ms
}

消息去哪了?

RabbitMQ会把消息给到各个服务:

[下单成功] → RabbitMQ
        ├→ 积分服务(拿消息,加积分)
        ├→ 优惠券服务(拿消息,发券)
        ├→ 短信服务(拿消息,发短信)
        └→ ...各干各的,互不影响

三、核心概念

1. 生产者(Producer)和消费者(Consumer)

  • 生产者:发消息的人(比如下单成功,发消息的服务)
  • 消费者:收消息干活的人(比如积分服务)

2. 队列(Queue)

就是个有顺序的待办事项列表。消息按先进先出排队,等着被处理。

3. 交换机(Exchange)

消息的路由器,决定消息该去哪个队列。

RabbitMQ有几种交换机:

  • 直连交换机:精准投递(像快递,按地址送)
  • 扇形交换机:广播模式(像群发邮件)
  • 主题交换机:模式匹配(像规则路由)

四、实际代码怎么写?

1. 先添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2. 配置RabbitMQ

# application.yml
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

3. 发消息(生产者)

@Service
public class OrderService {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void completeOrder(Order order) {
        // 1. 本地业务逻辑
        order.setStatus(OrderStatus.SUCCESS);
        orderRepository.save(order);
        
        // 2. 发消息(非核心逻辑异步化)
        OrderMessage message = new OrderMessage(order.getId(), order.getUserId());
        rabbitTemplate.convertAndSend("order.exchange", "order.success", message);
        
        // 3. 立即返回给用户
        // 用户不用等了!
    }
}

4. 收消息(消费者)

@Component
public class CouponConsumer {
    
    // 监听队列,自动消费
    @RabbitListener(queues = "coupon.queue")
    public void handleOrderSuccess(OrderMessage message) {
        log.info("收到订单成功消息,准备发券: {}", message);
        
        // 这里可以慢慢处理,哪怕耗时5秒
        couponService.sendCoupon(message.getUserId());
        
        // 就算这里抛异常,也不会影响下单主流程
    }
}

五、实际解决了哪些痛点?

1. 系统解耦

  • 以前:A服务直接调B服务,B挂了A就挂
  • 现在:A发消息,B自己取,互相不认识

2. 异步处理:不用等了

  • 用户下单 → 50ms返回
  • 后续操作 → 慢慢处理,用户无感知

3. 流量削峰:应对突发流量

  • 大促时订单暴涨
  • 消息先堆积在队列里
  • 服务按能力慢慢处理,不被打垮

4. 失败重试

@RabbitListener(queues = "coupon.queue")
public void handleMessage(OrderMessage message, Channel channel) {
    try {
        couponService.sendCoupon(message.getUserId());
        channel.basicAck(deliveryTag, false); // 确认处理成功
    } catch (Exception e) {
        // 失败了?放回队列,等会重试
        channel.basicNack(deliveryTag, false, true);
    }
}

六、什么情况下该用RabbitMQ?

适合场景:

  1. 耗时操作:发邮件、发短信、生成报表
  2. 非核心流程:日志记录、数据同步
  3. 流量波动大:秒杀、抢券
  4. 服务间解耦:微服务通信

不建议用:

  1. 强一致性要求:比如支付、扣库存(可以用MQ来做补偿机制)
  2. 简单查询:获取用户基本信息
  3. 实时性极高:视频通话、游戏操作

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《代码里全是 new 对象,真的很 Low 吗?我认真想了一晚》

《为什么成熟的 SpringBoot 项目,都有一个全局异常处理器?》

《SpringBoot 参数配置你真的用对了吗?》

《写 CSS 用 px?这 3 个单位能让页面自动适配屏幕》