在上一章,我们使用Seata解决了下单过程中的分布式事务问题,保证了数据的一致性。然而,在下单这样的高并发场景下,同步执行所有操作(创建订单、锁库存、减积分)可能会导致接口响应时间过长,用户体验下降。更好的方式是采用异步化处理,利用消息队列(MQ)来解耦核心流程和非核心流程。本章我们将引入RabbitMQ,对订单服务进行异步化改造,实现服务解耦和流量削峰。
24.1 异步处理与消息队列
内容讲解
为什么需要异步处理?
在下单流程中,对用户来说最核心的操作是“订单创建成功”。至于“库存锁定”是否完成,“积分扣减”是否成功,用户并不需要立即知道结果。这些操作可以放到后台异步执行。这样做有几个好处:
- 提升用户体验:核心的创单操作非常快,可以迅速响应用户,告知“下单成功”。
- 服务解耦:订单服务不再直接调用库存服务和用户服务,而是发送一条消息到MQ。库存服务和用户服务分别监听自己的队列,消费消息并执行业务。这样,即使库存服务暂时不可用,也不会影响用户下单。
- 流量削峰:在秒杀等大促活动中,瞬间会有大量下单请求涌入。如果同步处理,可能会压垮数据库。使用MQ,可以将请求暂存在队列中,下游服务根据自己的处理能力平稳地消费,起到了“缓冲垫”的作用。
异步改造后的下单流程
sequenceDiagram
participant User as 用户
participant Order as 订单服务
participant RabbitMQ
participant Ware as 库存服务
User->>+Order: 提交订单
Order->>Order: 创建订单数据 (状态为“待支付”)
Order->>Order: 保存订单到数据库
Order-->>-User: 立即返回下单成功
Order->>+RabbitMQ: 发送“订单创建成功”消息 (包含订单号、商品信息)
RabbitMQ-->>-Order: 消息发送成功
subgraph 后台异步处理
RabbitMQ-->>Ware: 投递消息
Ware->>Ware: 消费消息,执行库存锁定
end
24.2 RabbitMQ核心概念与Exchange设计
内容讲解
为了实现灵活的路由和解耦,我们需要合理地设计RabbitMQ的交换机(Exchange)、队列(Queue)和绑定(Binding)。
核心概念回顾:
- Exchange (交换机): 接收生产者发送的消息,并根据路由键(Routing Key)和绑定规则将消息路由到一个或多个队列。
- Queue (队列): 存储消息的缓冲区。
- Binding (绑定): 连接Exchange和Queue的桥梁,它包含一个路由键,用于匹配。
订单业务的Exchange设计:
我们可以为订单业务创建一个统一的Topic类型的交换机,例如order-event-exchange
。
- 创建订单: 订单服务创建订单后,发送一条消息到
order-event-exchange
,路由键为order.create.order
。 - 锁定库存: 库存服务创建一个队列
stock.release.stock.queue
,并将其绑定到order-event-exchange
,绑定键为order.create.order
。这样,所有新创建的订单消息都会被路由到这个队列,库存服务消费后进行锁库存操作。 - 订单超时关单: 我们可以利用RabbitMQ的延迟队列(或死信队列)功能。订单创建后,发送一条延迟消息(如延迟30分钟)到交换机,路由键为
order.release.other
。30分钟后,如果订单仍未支付,这条消息就会被一个专门的关单队列消费,执行关单和库存释放逻辑。
架构图
24.3 Spring Cloud Stream整合RabbitMQ
内容讲解
Spring Cloud Stream为我们整合RabbitMQ提供了极大的便利,它通过声明式的@Bean
和配置文件,屏蔽了底层的API细节。
整合步骤:
- 引入依赖: 在订单服务和库存服务中添加
spring-cloud-starter-stream-rabbit
依赖。 - 配置RabbitMQ: 在
application.yml
中配置RabbitMQ的地址、端口、用户名、密码和虚拟主机。 - 配置绑定关系: 在
application.yml
中使用spring.cloud.stream.bindings
来声明输入/输出通道,并将其绑定到具体的Exchange和Queue。 - 发送消息: 通过注入
StreamBridge
对象,可以方便地向指定的输出通道发送消息。 - 接收消息: 创建一个
@Bean
,类型为java.util.function.Consumer
,并使用@ServiceActivator
注解(或基于函数的定义)来监听指定的输入通道。
代码示例
1. application.yml
(订单服务)
spring:
cloud:
stream:
rabbit:
bindings:
output-order: # 自定义输出通道名
producer:
exchange-type: topic
bindings:
output-order:
destination: order-event-exchange # 目标交换机
content-type: application/json
2. 发送消息 (OrderServiceImpl.java
)
package com.cloudmall.order.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.stereotype.Service;
@Service("orderService")
public class OrderServiceImpl implements OrderService {
@Autowired
private StreamBridge streamBridge;
@Override
public void createOrder(OrderCreateVo vo) {
// ... 创建订单逻辑 ...
OrderEntity order = buildOrder(vo);
this.save(order);
// 发送消息到RabbitMQ
// "output-order" 是在yml中定义的通道名
// "order.create.order" 是路由键
streamBridge.send("output-order", order, "order.create.order");
}
}
3. 接收消息 (StockListener.java
- 库存服务)
package com.cloudmall.ware.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(queues = "stock.release.stock.queue") // 监听指定队列
public class StockListener {
@RabbitHandler
public void handleStockLock(OrderEntity order, Message message, Channel channel) {
try {
// 执行库存锁定逻辑
System.out.println("收到订单消息,准备锁定库存:" + order.getOrderSn());
// ... 锁库存 ...
// 手动确认消息消费成功
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// 消费失败,将消息重新入队或丢弃
// channel.basicReject(...) 或 channel.basicNack(...)
}
}
}
本章总结
本章我们对订单服务进行了一次重要的架构升级,从同步调用转向了异步消息驱动。我们分析了异步化带来的巨大优势,包括提升用户体验、服务解耦和流量削峰。我们基于RabbitMQ的Topic交换机,为订单业务设计了一套灵活、可扩展的消息路由方案。在实战中,我们利用Spring Cloud Stream,通过简单的配置和注解就完成了与RabbitMQ的整合,并演示了如何发送和接收消息。通过引入消息队列,我们的电商系统在应对高并发、高可用方面迈出了坚实的一步,架构也变得更加健壮和有弹性。
资源下载
本章的示例代码可以从以下链接下载:
微服务开发课程第16-26章的源码