消息队列(Message Queue,简称MQ)本质上是一个遵循FIFO(先进先出)原则的队列结构,但队列中存储的内容是消息(message)。这种设计在分布式系统中扮演着重要角色,特别是在不同进程或线程间通信时。
在实际系统开发中,我们经常会遇到以下几个典型场景:
系统解耦:当两个系统直接通信时,修改一个系统往往需要同步修改另一个系统。引入消息队列后,系统间通过消息进行通信,彼此不再直接依赖。例如,订单系统生成订单后,只需将订单消息放入队列,库存系统和物流系统各自从队列获取消息进行处理,任何一方修改都不会影响其他系统。
流量削峰:在秒杀等高并发场景下,系统可能瞬间收到大量请求。通过消息队列可以将这些请求缓存起来,让下游系统按照自身处理能力逐步消费,避免系统被压垮。我曾经在一个电商项目中,使用RabbitMQ成功将峰值10万QPS的请求平稳处理,下游服务始终保持在3000QPS的稳定处理状态。
异步处理:对于不需要立即返回结果的操作,如发送邮件或短信通知,可以先将请求放入队列,立即返回响应给用户,后台再慢慢处理。这显著提升了用户体验和系统吞吐量。
RabbitMQ 实现了AMQP(高级消息队列协议),其核心架构包含以下几个关键组件:
虚拟主机类似于命名空间,用于逻辑隔离。每个虚拟主机拥有独立的交换机、队列和绑定关系。在实际项目中,我们通常会为不同环境(开发、测试、生产)或不同业务线创建独立的虚拟主机。
注意:RabbitMQ默认有一个"/"虚拟主机,生产环境中建议不要直接使用它,而是创建专门的虚拟主机并设置合理的权限。
交换机是消息的分发中心,负责将消息路由到一个或多个队列。RabbitMQ支持四种交换机类型:
队列是消息的最终目的地,具有以下重要特性:
durable=true可使队列在Broker重启后依然存在exclusive=true的队列仅对声明它的连接可见,连接关闭时队列自动删除auto-delete=true的队列在所有消费者断开连接后自动删除绑定是交换机和队列之间的连接规则,决定了消息如何从交换机路由到队列。对于Topic Exchange,绑定键可以包含通配符:
* 匹配一个单词# 匹配零个或多个单词例如,绑定键stock.#可以匹配stock.us.nyse和stock.eu等路由键。
在pom.xml中添加Spring Boot对AMQP的支持:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
application.yml中配置RabbitMQ连接信息:
yaml复制spring:
rabbitmq:
host: 192.168.1.100
port: 5672
username: admin
password: securepassword
virtual-host: /prod
connection-timeout: 5000
# 开启发送确认
publisher-confirms: true
# 开启发送失败退回
publisher-returns: true
listener:
simple:
# 消费者最小数量
concurrency: 5
# 消费者最大数量
max-concurrency: 10
# 每次从队列获取的消息数量
prefetch: 1
# 消费失败重试策略
retry:
enabled: true
max-attempts: 3
initial-interval: 1000
创建配置类定义队列、交换机和绑定关系:
java复制@Configuration
public class RabbitConfig {
// 订单队列
@Bean
public Queue orderQueue() {
return new Queue("order.queue", true); // 持久化队列
}
// 日志主题交换机
@Bean
public TopicExchange logExchange() {
return new TopicExchange("log.topic.exchange", true, false);
}
// 绑定错误日志队列
@Bean
public Binding errorLogBinding() {
return BindingBuilder.bind(errorLogQueue())
.to(logExchange())
.with("log.error.#");
}
}
使用RabbitTemplate发送消息:
java复制@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void createOrder(Order order) {
// 发送订单创建消息
rabbitTemplate.convertAndSend(
"order.exchange", // 交换机
"order.create", // 路由键
order, // 消息内容
message -> {
// 设置消息属性
message.getMessageProperties()
.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
message.getMessageProperties()
.setHeader("traceId", UUID.randomUUID().toString());
return message;
});
}
}
使用@RabbitListener注解监听队列:
java复制@Component
public class OrderListener {
@RabbitListener(queues = "order.queue")
public void processOrder(Order order,
@Header("traceId") String traceId) {
try {
// 处理订单业务逻辑
log.info("Processing order {} with traceId {}", order.getId(), traceId);
} catch (Exception e) {
// 处理异常
log.error("Order processing failed", e);
throw new AmqpRejectAndDontRequeueException(e);
}
}
}
RabbitMQ提供两种确认机制:
java复制rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("Message confirmed with ID: {}", correlationData.getId());
} else {
log.error("Message confirmation failed: {}", cause);
}
});
java复制@RabbitListener(queues = "order.queue")
public void processOrder(Order order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
try {
// 业务处理
channel.basicAck(tag, false); // 手动确认
} catch (Exception e) {
channel.basicNack(tag, false, false); // 拒绝且不重新入队
}
}
当消息被拒绝或过期时,可以路由到死信队列:
java复制@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 new Queue("order.queue", true, false, false, args);
}
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange("dlx.exchange");
}
@Bean
public Queue dlxQueue() {
return new Queue("dlx.queue");
}
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(dlxQueue())
.to(dlxExchange())
.with("dlx.order");
}
yaml复制spring:
rabbitmq:
cache:
channel:
size: 25
checkout-timeout: 10000
connection:
mode: CONNECTION
size: 5
java复制rabbitTemplate.invoke(template -> {
for (int i = 0; i < 100; i++) {
template.convertAndSend("exchange", "routingKey", "message " + i);
}
return null;
});
java复制rabbitTemplate.setBeforePublishPostProcessors(message -> {
byte[] compressed = CompressionUtils.compress(message.getBody());
message.getBody().setCompressedMessage(compressed);
return message;
});
bash复制rabbitmq-plugins enable rabbitmq_management
yaml复制management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
bash复制rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}'
| 特性 | RabbitMQ | Kafka | RocketMQ | ActiveMQ |
|---|---|---|---|---|
| 协议支持 | AMQP, STOMP等 | 自定义协议 | 自定义协议 | OpenWire, STOMP等 |
| 吞吐量 | 中等(万级) | 高(百万级) | 高(十万级) | 低(千级) |
| 延迟 | 低(毫秒级) | 中(毫秒到秒) | 低(毫秒级) | 低(毫秒级) |
| 顺序保证 | 单个队列内有序 | 分区内有序 | 队列内有序 | 单个消费者有序 |
| 持久化 | 内存/磁盘 | 磁盘 | 磁盘 | 内存/磁盘 |
| 适用场景 | 业务解耦、异步处理 | 日志处理、流计算 | 订单处理、金融交易 | 简单消息场景 |
在实际项目中,我曾遇到一个需要同时处理实时订单和日志分析的场景。最终方案是使用RabbitMQ处理订单核心流程,同时将日志消息通过RabbitMQ转发到Kafka进行大数据分析,充分发挥各自优势。
java复制@Bean
public BridgeListener bridgeListener() {
return new BridgeListener(kafkaTemplate);
}
public class BridgeListener {
private final KafkaTemplate<String, String> kafkaTemplate;
public BridgeListener(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
@RabbitListener(queues = "log.queue")
public void bridgeToKafka(LogMessage log) {
kafkaTemplate.send("log.topic", log.toString());
}
}
这种架构既保证了实时业务的低延迟需求,又满足了大数据分析的高吞吐要求,在实际项目中取得了很好的效果。