Quick Start
RabbitMQ是AMQP协议的一个实现,spring boot提供了快速的接入方案.参考
配置build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-amqp'
testImplementation 'org.springframework.amqp:spring-rabbit-test'
}
配置application.yml
spring:
rabbitmq:
host: 192.168.56.101
username: admin
password: pw
virtual-host: first_vhost
发送消息
public class FireEventController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/event")
public void visit(@RequestParam("name") String name) {
rabbitTemplate.convertAndSend("spring-boot-exchange", "foo.bar.baz",name);
}
}
接收消息
我们可以直接使用@RabbitListener注解声明MQ消费者,同springMVC一样,spring可以自动帮我们处理参数转换的工作.
@RabbitListener(queues = "spring-boot")
public void receive(String message){
System.out.println("收到消息:"+ message);
}
生产级消息队列配置
AMQP模型在观察者模式基础上多做了一层抽象“交换器 ”(Exchange). 在合理配置RabbitMQ时,我们需要正确理解AMQP的概念.参考
AMQP重要的概念有:虚拟主机,交换机,队列,和绑定。
- 虚拟主机:一个虚拟主机持有一组交换机、队列和绑定。为什么需要多个虚拟主机呢?很简单,RabbitMQ当中,用户只能在虚拟主机的粒度进行权限控制。 因此,如果需要禁止A组访问B组的交换机/队列/绑定,必须为A和B分别创建一个虚拟主机。每一个RabbitMQ服务器都有一个默认的虚拟主机“/”。
- 交换机:Exchange 用于转发消息,但是它不会做存储 ,如果没有 Queue bind 到 Exchange 的话,它会直接丢弃掉 Producer 发送过来的消息。 这里有一个比较重要的概念:路由键 。消息到交换机的时候,交互机会转发到对应的队列中,那么究竟转发到哪个队列,就要根据该路由键。
- 绑定:也就是交换机需要和队列相绑定,这其中如下图所示,是多对多的关系。
理想情况下,我们需要一个消息队列做信息解耦,配置一个队列(Queue)就够了,生产者向这个队列发送消息,消费者订阅这个队列消费.但在实际生存环境,为了保证消息的不丢失,我们应该针对一些异常场景做一系列的配置.
参考
-
exchange: 配置业务交换机和备用交换机
属性 业务交换机 AE交换机 死信交换机 Name exchange.bizTopic exchange.bizTopic.fanout exchange.bizTopic.dlx Type topic fanout topic Durability Durable Durable Durable Auto delete no no no Arguments “alternate-exchange” = “exchange.bizTopic.fanout” -
queue:配置消息队列
属性 业务队列 AE队列 死信队列 Name queue.bizTopic.case1 queue.bizTopic.fanout queue.bizTopic.dlx Durability Durable Durable Durable Auto delete no no no Arguments “x-dead-letter-exchange” = “exchange.bizTopic.dlx” 一个消费场景一个消费队列,如果有多个场景消费,建议配置多个业务队列.可以基于发送的routingKey分发到不同的队列,无法投递的转到AE队列.死信队列根据对错误的关注程度不同,可以统一输出到一个死信队列,或者不同的业务队列输出到对应的死信队列.
-
binding:配置exchange路由
属性 业务路由 AE路由 死信路由 Exchange exchange.bizTopic exchange.bizTopic.fanout exchange.bizTopic.dlx To queue queue.bizTopic.case1 queue.bizTopic.fanout queue.bizTopic.dlx Routing key queue.bizTopic.case1 # #
可靠消息投递
在大多数互联网消息解耦的场景下,以上方案可以实现99%的消息送达和消费.但在业务一致性和完整性要求非常高的情况下,1%的消息丢失也是不可接受的.我们需要对消息的送达和消费做更严格的管理.
配置消息发送到交换机确认机制:
spring:
rabbitmq:
publisher-confirms: true # 消息发送到交换机确认机制,是否确认回调
publisher-returns: true # 消息发送到交换机确认机制,是否返回回馈
template.mandatory: true
申明消息回调handler:
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnCallback(returnCallback);
}
public void publishCreated(Order order) {
rabbitTemplate.convertAndSend(properties.bizExchange(), properties.createRoutingKey(), order,
new CorrelationData("" + order.getOrderId()));
}
当消息发送到交换机(exchange)时,MsgSendConfirmCallBack#confirm()被调用.
- 1.如果消息没有到exchange,则 ack=false
- 2.如果消息到达exchange,则 ack=true
public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("MsgSendConfirmCallBack , 回调correlationData:" + correlationData);
if (ack) {
log.info("消息发送到exchange成功");
// TODO 删除 msgId 与 Message 的关系
} else {
log.info("消息发送到exchange失败");
// TODO 消息发送到exchange失败 , 重新发送
}
}
}
当消息从交换机到队列失败时,MsgSendReturnCallback#returnedMessage()被调用。(若成功,则不调用)
**需要注意的是:该方法调用后,{@link MsgSendConfirmCallBack}中的confirm方法也会被调用,且ack = true **
public class MsgSendReturnCallback implements RabbitTemplate.ReturnCallback {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("MsgSendReturnCallback [消息从交换机到队列失败] message:" + message);
// TODO 消息从交换机到队列失败,重新发送
}
}
可靠消息消费
Spring boot 中进行 AOP拦截 自动帮助做重试,消息会自动重试参考.为了防止死循环,我们需要做一些配置.参考
spring:
rabbitmq:
listener:
simple:
retry:
####开启消费者重试,默认开启
enabled: true
####最大重试次数(默认无数次)
max-attempts: 2
####重试间隔
initial-interval: 1