Spring Boot整合RabbitMQ
大佬文章,请优先查看!!!
简述
-
Spring支持
spring-jms提供了对JMS的支持
spring-rabbit提供了对AMQP的支持
需要ConnectionFactory的实现来连接消息代理
提供JmsTemplate、RabbitTemplate来发送消息
@JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息
@EnableJms、@EnableRabbit开启支持 -
Spring Boot自动配置
JmsAutoConfiguration
RabbitAutoConfiguration
自定义消息转换器
spring的对象系处理器是由org.springframework.amqp.support.converter.MessageConverter
来处理的,而默认实现就是SimpleMessageConverter
,基于JDK的ObjectOutputStream
完成序列化,如果要修改只需要定义一个MessageConverter
类型的Bean即可。
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; @Configuration public class MyAMQPConfig { /** * json格式消息转换 * * @return */ @Bean public MessageConverter jsonMessageConverter() { return new Jackson2JsonMessageConverter(); } }
rabbitmq配置说明
生产者重连
当网络不稳定的时候,利用重试机制可以有效提高消息发送的成功率。不过SpringAMQP提供的重试机制是阻塞式的重试,也就是说多次重试等待的过程中,当前线程是被阻塞的,会影响业务性能。
如果对于业务性能有要求,建议禁用重试机制。如果需要使用,需要合理配置等待时长和重试次数,也可以考虑使用异步线程来执行发送消息。
#开启超时重试机制 spring.rabbitmq.template.retry.enabled=true #失败后的初始等待时间 spring.rabbitmq.template.retry.initial-interval=1000 #失败后下次的等待时长倍数,下次等待时长=initial-interval * multiplier spring.rabbitmq.template.retry.multiplier=1 #最大重试次数 spring.rabbitmq.template.retry.max-attempts=3
生产者确认机制
RabbitMQ中有 Publisher Confirm
和Publisher Return
两种确认机制。开启确认机制后,在MQ成功收到消息后会返回确认消息给生产者。返回的结果有以下几种情况:
- 消息投递到了MQ,但是路由失败。此时会通过
PublisherReturn
返回路由异常原因,然后返回ACK,告知投递成功 - 临时消息投递到了MQ,并且入队成功,返回ACK,告知投递成功
- 持久消息投递到了MQ,并且入队完成持久化,返回ACK,告知投递成功
- 其他情况都会返回NACK,告知投递失败
spring: rabbitmq: publisher-confirm-type: correloted #开启publisher confirm消息确认模式,并设置confirm类型 publisher-returns: true #开启publisher return机制,一般不会开启
publisher-confirm-type
有三种模式:
none:关闭confirm机制
simple:同步阻塞等待MQ的回执消息
correlated:MQ异步回调方式返回回执消息
Return机制
每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目启动过程中配置:
@Slf4j @Configuration public class CommonConfig implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 获取RabbitTemplate RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class); // 设置ReturnCallback rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey)->{ log.info("消息发送失败,应答码:{},原因:{},交换机:{},路由键:{},消息:{}",replyCode,replyText,exchange,routingKey,message.toString()); }); } }
Confirm机制
发型消息,指定消息ID、消息ConfirmCallback
。
@Test public void testPublisherConfirm() throws InterruptedException { // 创建CorrelationData CorrelationData cd = new CorrelationData(UUID.randomUUID().toString()); // Future添加ConfirmCallback cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() { @Override public void onFailure(Throwable e) { // Future发送异常时的处理逻辑,属于spring处理异常,不是mq返回的失败,基本不会触发 log.error("handle message ack fail", e); } @Override public void onSuccess(CorrelationData.Confirm result) { // Future接收到回执的处理逻辑,参数中的result就是回执内容 if (result.isAck()) { log.debug("发送消息成功,收到ack..."); } else { log.error("发送消息失败,收到nack, reason:{}", result.getReason()); } } }); // 发送消息 rabbitTemplate.convertAndSend("exchange", "routingkey", "hello", cd); }
小结
生产者消息确认需要额外的网路和系统资源开销,尽量不要使用。如果需要使用,无需开启Publisher-Return
机制,因为一般路由失败是自身业务问题。对于nack消息可以有限次数重试,依然失败则记录异常消息。
消费者确认机制
为了确认消费者是否成功处理消息,RabbitMQ提供了消费者确认机制(Consumer Acknowledgement)。当消费者处理消息结束后,应该向RabbitMQ发送一个回执,告知RabbitMQ自己消息处理状态。回执有三种可选值:
- ack:成功处理消息,RabbitMQ从队列中删除该消息
- nack:消息处理失败,RabbitMQ需要再次投递消息
- reject:消息处理失败并拒绝该消息,RabbitMQ从队列中删除该消息
SpringAMQP已经实现了消息确认功能,并允许我们通过配置文件选择ACK处理方式,有三种方式:
-
none:不处理。即消息投递给消费者后立即ack,消息会立刻从MQ删除。非常不安全,不建议使用。
-
manual:手动模式。SpringAMQP利用AOP对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回ack,当业务出现异常时,根据异常判断返回不同结果:
如果是业务异常,会自动返回nack。
如果是消息处理或校验异常,自动返回reject。
spring: rabbitmq: listener: simple: prefetch: 1 acknowledge-mode: none # none:关闭ack;manual:手动ack;auto:自动ack
消费者失败重试机制
当消费者出现异常后,消息会不断requeue(重新入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理压力飙升。
我们可以利用Spring的retry
机制,在消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列:
spring: rabbitmq: listener: simple: prefetch: 1 retry: enabled: true # 开启消费者失败重试 initial-interval: 1000ms # 初始的失败等待时长为1秒 multiplier: 1 # 下次失败的等待时长倍数,下次等待时长=multiplier*lost-interval max-attempts: 3 # 最大重试次数 stateless: true # true:无状态;false:有状态。如果业务中包含事务,这里改为false
失败消息处理策略
在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有MessageRecoverer
接口来处理,它包含三种不同的实现:
RejectAndDontRequeueRecoverer
:重试耗尽后,直接reject,丢弃消息。默认就是这种方式。ImmediateRequeueMessageRecoverer
:重试耗尽后,返回nack,消息重新入队。RepublishMessageRecoverer
:重试耗尽后,将失败消息投递到指定的交换机。
RepublishMessageRecoverer方式示例:
将失败处理策略改为RepublishMessageRecoverer
,首先定义接收失败消息的交换机、队列及其绑定关系;然后定义RepublishMessageRecoverer
:
@Bean public MessageRecoverer republishMessageRecoverer(RabbitTemplate reabbitTempate) { return new RepublishMessageRecoverer(rabbitTemplate, "hguo.error.exchanges", "error.routing.key"); }
RabbitMQ整合
引入spring-boot-starter-amqp依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
application.yml配置
spring: rabbitmq: host: # 主机地址 username: # 用户名guest password: # 密码guest port: 5672 # 默认端口5672
启动类添加启动注解
import org.mybatis.spring.annotation.MapperScan; import org.springframework.amqp.rabbit.annotation.EnableRabbit; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 自动配置 * 1、RabbitAutoConfiguration * 2、有自动配置了连接工厂ConnectionFactory; * 3、RabbitProperties 封装了 RabbitMQ的配置 * 4、 RabbitTemplate :给RabbitMQ发送和接受消息; * 5、 AmqpAdmin : RabbitMQ系统管理功能组件; * AmqpAdmin:创建和删除 Queue,Exchange,Binding * 6、@EnableRabbit + @RabbitListener 监听消息队列的内容 * */ @EnableRabbit //开启基于注解的RabbitMQ模式 @SpringBootApplication @MapperScan({"com.example.demo.mapper"}) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
MQ配置(队列、交换机声明)
import lombok.RequiredArgsConstructor; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * rabbitMq配置 * */ @Configuration public class RabbitMqConfig { private final CachingConnectionFactory connectionFactory; public RabbitMqConfig(CachingConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; } /** * 消息监听器工厂 * * @return */ @Bean(name = "mqListenerContainer") public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory() { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); // 限流 一次性从队列中最大能拉取消息数 factory.setPrefetchCount(50); return factory; } // 其他方式声明监听器 /* public SimpleMessageListenerContainer getObject() throws Exception { SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setAmqpAdmin(amqpAdmin); container.setConnectionFactory(connectionFactory); container.setQueues(queue); container.setPrefetchCount(20); container.setConcurrentConsumers(20); container.setMaxConcurrentConsumers(100); container.setDefaultRequeueRejected(Boolean.FALSE); container.setAdviceChain(createRetry()); container.setAcknowledgeMode(autoAck ? AcknowledgeMode.AUTO : AcknowledgeMode.MANUAL); // container.stop(); if (Objects.nonNull(consumer)) { container.setMessageListener(consumer); } return container; }*/ /** * 动态生成队列 * * @param connectionFactory * @return */ @Bean public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) { return new RabbitAdmin(connectionFactory); } /** * 声明队列 * * @return */ @Bean public Queue createQueue() { /* * 第一种方式: * * durable():代表需要持久化 * exclusive(): 代表该队列独占(只允许有一个consumer监听) * autoDelete(): 代表需要自动删除(没有consumer自动删除) * withArgument(): 队列的其他参数 */ // return QueueBuilder.durable("boot_work_queue").exclusive().autoDelete().withArgument("key", "val").build(); /* * 第二种方式:通过new Queue对象来创建队列 * * name:队列名称 * durable:队列是否持久化,默认是true * exclusive:是否独占,队列是否设置为排他队列,默认是false。为true时设置为排他队列,只对首次声明它的连接可见, * 其他连接无法声明相同名称的其他队列,并且在连接断开时自动删除,即使持久化也会被删除 * autoDelete:队列是否自动删除,默认false。为true时,当没有消费者使用此队列,该队列会自动删除 * * 一般设置一下队列的持久化就好,其余两个就是默认false * */ return new Queue("chat.room.queue", true, false, false); } /** * 声明死信队列 * * @return */ @Bean public Queue deadQueue() { Map<String, Object> map = new HashMap<>(); // 队列中的每一个消息未被消费则5秒后过期,被自动删除并移到死信队列 map.put("x-message-ttl", 5000); return new Queue("chat.dead.queue", true, false, false, map); } /** * 声明发布订阅模式交换机 * * @return */ @Bean FanoutExchange fanoutExchange() { /* * 第一种方式: 通过ExchangeBuilder构建交换机 * * 通过ExchangeBuilder声明交换机 * 每种类型交换机有对应方法,如:fanoutExchange()、topicExchange() * - durable: 是否持久化 * - autoDelete: 是否自动删除 * - withArgument: 交换机其他参数 * */ // return ExchangeBuilder.fanoutExchange("boot_fanout_exchange").durable(true).build(); // return ExchangeBuilder.directExchange("boot_direct_exchange").durable(true).autoDelete().withArgument("key","val").build(); /* * 第二种方式:通过new FanoutExchange对象声明交换机 * * name:交换机名称 * durable:是否持久化(默认false) * autoDelete:是否自动删除(默认false) * */ return new FanoutExchange("fanout.exchange", true, false); } /** * 声明路由模式交换机 * * @return */ @Bean DirectExchange directExchange() { return new DirectExchange("direct.exchange", true, false); } /** * 声明主题模式交换机 */ @Bean TopicExchange topicExchange() { return new TopicExchange("topic.exchange", true, false); } /** * 交换机与队列进行绑定 */ @Bean public Binding bindQueueExchange() { /* * 第一种方式: 通过BindingBuilder绑定 * * bind(Queue): 需要绑定的queue * to(Exchange): 需要绑定到哪个交换机 * with(String): routing key * noargs(): 进行构建 */ // return BindingBuilder.bind(testQueue()).to(directExchange()).with("article").noargs(); // return BindingBuilder.bind(testQueue()).to(directExchange()).with(testQueue().getName()); /* * 第二种方式:通过new Binding对象绑定 * * destination: 绑定的队列 * destinationType: 绑定的类型 Binding.DestinationType.QUEUE: 绑定的类型为queue(交换机不仅可以绑定queue还可以绑定exchange) * exchange: 哪个交换机需要绑定 * routingKey: routing key * arguments: 其他参数 */ return new Binding("chat.room.queue", Binding.DestinationType.QUEUE, "fanout.exchange", "chat.room.key", null); } }
动态创建队列与交换机
一般都是通过注入Bean的方式去声明交换机、队列,应用启动时去自动创建。
如果需要动态创建,比如通过接口、或者业务代码自己去操作,这个使用就需要使用RabbitMQ提供的操作接口。如果是基于Spring Boot,则可以直接使用其提供的AmqpAdmin
,其对RabbitMQ的原生接口进行了二次封装,使用起来十分方便。AmqpAdmin
接口,只有一个实现类RabbitAdmin
。
import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; /** * RabbitMq动态配置 * 动态生成队列和交换机 * */ @Controller public class RabbitMqDynamicController { /** * 实现了AmqpAdmin接口 */ @Autowired private RabbitAdmin rabbitAdmin; /** * 通过AmqpAdmin:创建和删除 Queue,Exchange,Binding */ @Autowired private AmqpAdmin amqpAdmin; @PostMapping("/dynamic") public void dynamicConfig() { //创建mq队列 Queue test3Queue = new Queue("test3_Queue", true, false, false); rabbitAdmin.declareQueue(test3Queue); //创建交换机 DirectExchange direct1Exchange = new DirectExchange("direct1_Exchange", true, false); rabbitAdmin.declareExchange(direct1Exchange); //绑定交换机和队列,并设置Routing key Binding binding = BindingBuilder.bind(test3Queue).to(direct1Exchange).with(test3Queue.getName()); rabbitAdmin.declareBinding(binding); } /** * 删除mq队列 * * @return */ @PostMapping("/deleteMq") public String deleteMq(String mq) { rabbitAdmin.deleteQueue(mq); return "ok"; } @PostMapping("/") public void amqpAdminCreate(){ // 创建Exchange amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange")); /* * 创建消息队列 * - Queue是类可以直接new,构造器第一个参数:队列名,第二参数:是否持久化存在,若没有指定参数则随机给队列名 * */ amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true)); /* * 创建绑定规则 * - 参数1:目的地(队列) * - 参数2:绑定的类型->队列 * - 参数3:Exchange * - 参数4:路由件 * - 参数5:参数没有为null * */ amqpAdmin.declareBinding(new Binding("amqpadmin.queue", Binding.DestinationType.QUEUE,"amqpadmin.exchange","amqp.haha",null)); //amqpAdmin.deleteExchange(); // 删除交换器 //amqpAdmin.deleteQueue(); // 删除队列 } }
自定义消息转换器
默认使用jdk序列化方式
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 消息转换器 * */ @Configuration public class MessageConverterConfig { /** * 使用json转换方式 * * @return */ @Bean public MessageConverter messageConverter() { return new Jackson2JsonMessageConverter(); } }
消息实体
注意:如果消费者接受的消息是实体类对象,需要将类序列化
import lombok.Data; import java.io.Serializable; /** * 测试:消息实体 * */ @Data public class Book implements Serializable { private static final long serialVersionUID = 7512574760433830393L; /** * 书名 */ private String name; /** * 价格 */ private String price; }
生产者推送消息
import com.alibaba.fastjson.JSON; import com.mazha.rabbitmq.pojo.domain.Book; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 推送消息控制器 * */ @RestController @Slf4j @RequestMapping("/producer") public class ProducerController { private final RabbitTemplate rabbitTemplate; public ProducerController(RabbitTemplate rabbitTemplate) { this.rabbitTemplate = rabbitTemplate; } @PostMapping("/testSend") public void testSendMessage(@RequestBody Book book) { log.info("生产者发送消息:{}", JSON.toJSONString(book)); /* * 对象被默认序列化以后发送出去 * object默认当成消息体,只需要传入要发送的对象,自动序列化发送给rabbitmq * */ rabbitTemplate.convertAndSend("chat.fanout.exchange", "chat.room.key", book); } }
消费者监听消息
- 方式一
import com.mazha.rabbitmq.pojo.domain.Book; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; /** * 使用@RabbitListener注解在类上监听 * */ @Component @RabbitListener(queues = "chat.room.queue") @Slf4j public class TestMqReceiver { /** * 监听消息 * * @param message 消息体 */ @RabbitHandler public void process(Book message) { log.info("@RabbitListener注解在类上监听消息:{}", message); } }
2023-12-07 22:37:14.414 INFO 20184 --- [nio-8001-exec-2] c.m.r.controller.ProducerController : 生产者发送消息:{"name":"神雕侠侣","price":"100"} 2023-12-07 22:37:18.695 INFO 20184 --- [ntContainer#0-1] c.m.rabbitmq.listener.TestMqReceiver : @RabbitListener注解在类上监听消息:Book(name=神雕侠侣, price=100)
- 方式二
import com.alibaba.fastjson.JSONObject; import com.mazha.rabbitmq.pojo.domain.Book; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; /** * 使用@RabbitListener注解方法上监听消息 * */ @Component @Slf4j public class TestMqReceiver2 { /** * 监听指定的消息队列(数组类型,可以指定监听多个) * * @param book */ @RabbitListener(queues = {"chat.room.queue"}) public void receive(Book book) { log.info("使用@RabbitListener注解方法上监听消息:{}", JSONObject.toJSONString(book)); } }
2023-12-07 22:37:29.836 INFO 20184 --- [nio-8001-exec-1] c.m.r.controller.ProducerController : 生产者发送消息:{"name":"神雕侠侣","price":"100"} 2023-12-07 22:37:29.838 INFO 20184 --- [ntContainer#1-1] c.m.rabbitmq.listener.TestMqReceiver2 : 使用@RabbitListener注解方法上监听消息:{"name":"神雕侠侣","price":"100"}
- 方式三
import com.alibaba.fastjson.JSON; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; /** * rabbitTemplate.receiveAndConvert()方法接收消息 * */ @Controller @RequestMapping("/testMqReceiver3") @Slf4j @RequiredArgsConstructor public class TestMqReceiver3 { private final RabbitTemplate rabbitTemplate; /** * 接收数据 */ @GetMapping("/receive") public Object receiveMessage() { Object message = rabbitTemplate.receiveAndConvert("chat.room.queue"); log.info("receiveAndConvert方法方式监听消息:{}", JSON.toJSONString(message)); return message; } }
2023-12-07 22:43:28.158 INFO 15420 --- [nio-8001-exec-1] c.m.r.controller.ProducerController : 生产者发送消息:{"name":"神雕侠侣","price":"100"} 2023-12-07 22:43:28.171 INFO 15420 --- [nio-8001-exec-1] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [192.168.56.103:5672] 2023-12-07 22:43:28.199 INFO 15420 --- [nio-8001-exec-1] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory#59ed3e6c:0/SimpleConnection@133f6b8e [delegate=amqp://guest@192.168.56.103:5672/, localPort= 61478] 2023-12-07 22:44:04.910 INFO 15420 --- [nio-8001-exec-2] c.m.r.controller.ProducerController : 生产者发送消息:{"name":"神雕侠侣","price":"100"} 2023-12-07 22:44:18.177 INFO 15420 --- [nio-8001-exec-3] c.m.rabbitmq.listener.TestMqReceiver3 : receiveAndConvert方法方式监听消息:{"name":"神雕侠侣","price":"100"} 2023-12-07 22:44:25.370 INFO 15420 --- [nio-8001-exec-4] c.m.rabbitmq.listener.TestMqReceiver3 : receiveAndConvert方法方式监听消息:{"name":"神雕侠侣","price":"100"}
本文来自博客园,作者:Lz_蚂蚱,转载请注明原文链接:https://www.cnblogs.com/leizia/p/18050371
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步