上周和一个前同事聊天,说他们团队评审技术方案的时候,有人提议引入RabbitMQ,结果被技术主管当场否决:“咱们这小系统,用啥消息队列,直接同步调不行吗?别过度设计!”
结果这位朋友周末偷偷把RabbitMQ集成进了自己的模块。
“我知道不该这么干,但实在忍不住啊。”他这么跟我说。
一、我们到底在忍受什么?
场景
假设你负责一个电商系统的优惠券发放模块。用户下单成功后,需要:
- 发放积分
- 发放优惠券
- 发送短信通知
- 更新用户标签
- 记录行为日志
传统做法(同步调用版):
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);
// 万一这里抛异常,前面都白干了?
}
问题:
- 性能瓶颈:每个调用都要等上个执行完,用户得等5-10秒
- 稳定性差:发短信服务挂了,整个流程就卡住了
- 难以维护:加个新功能就要改这坨代码
- 扩展困难:想提升性能?对不起,得重构
二、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?
适合场景:
- 耗时操作:发邮件、发短信、生成报表
- 非核心流程:日志记录、数据同步
- 流量波动大:秒杀、抢券
- 服务间解耦:微服务通信
不建议用:
- 强一致性要求:比如支付、扣库存(可以用MQ来做补偿机制)
- 简单查询:获取用户基本信息
- 实时性极高:视频通话、游戏操作
本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《代码里全是 new 对象,真的很 Low 吗?我认真想了一晚》