RabbitMD大揭秘

欢迎关注H寻梦人公众号

通过SpringBoot整合RabbitMQ的案例来说明,RabbitMQ相关的各个属性以及使用方式;并通过相关源码深刻理解。

Queue(消息队列)

Queue(消息队列) 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

RabbitMQ 中消息只能存储在 队列 中,这一点和 Kafka 这种消息中间件相反。Kafka 将消息存储在 topic(主题) 这个逻辑层面,而相对应的队列逻辑只是topic实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。

多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免的消息被重复消费。

RabbitMQ 不支持队列层面的广播消费,如果有广播消费的需求,需要在其上进行二次开发,这样会很麻烦,不建议这样做。

交换机的类型:

交换机主要包括如下4种类型:

  1. Direct exchange(直连交换机)
  2. Fanout exchange(扇型交换机)
  3. Topic exchange(主题交换机)
  4. Headers exchange(头交换机)

1. 基本配置

1.1 配置文件属性说明

RabbitMQ最基本的基础配置如下:

  1. server:
  2. port: 11000
  3. spring:
  4. application:
  5. name: rabbitmq-test
  6. rabbitmq:
  7. # 单机IP配置
  8. # host: rabbitmq-host
  9. # port: 5672
  10. # 集群IP配置
  11. addresses: rabbitmq01-host:5672,rabbitmq02-host:5672
  12. # 用户名和密码,默认都是guest
  13. username: xiongmin
  14. password: xiongmin
  15. # 交换器名可以不设置默认 【"" --> /】 交换器
  16. virtual-host: /rabbitmq_test
  17. publisher-returns: true # 发送者开启 return 确认机制
  18. publisher-confirm-type: correlated # 发送者开启 confirm 确认机制 等价于 spring.rabbitmq.publisher-returns=true
  19. default-exchange: default_exchange
  20. listener:
  21. simple:
  22. acknowledge-mode: manual # 设置消费端手动 ack
  23. retry:
  24. enabled: true # 支持重试
  25. ----------------------------------------------------------------------------------------------
  26. server:
  27. port: 11000
  28. spring:
  29. application:
  30. name: rabbitmq-test
  31. rabbitmq:
  32. # 单机IP配置
  33. # host: rabbitmq-host
  34. # port: 5672
  35. # 集群IP配置
  36. addresses: rabbitmq01-host:5672,rabbitmq02-host:5672
  37. # 用户名和密码,默认都是guest
  38. username: xiongmin
  39. password: xiongmin
  40. # 交换器名可以不设置默认 【"" --> /】 交换器
  41. virtual-host: /rabbitmq_test
  42. publisher-returns: true # 发送者开启 return 确认机制
  43. publisher-confirm-type: correlated # 发送者开启 confirm 确认机制 等价于 spring.rabbitmq.publisher-returns=true
  44. #连接超时时间
  45. connection-timeout: 15000
  46. # 使用return-callback时必须设置mandatory为true
  47. template:
  48. mandatory: true
  49. default-exchange: default_exchange
  50. # 消费端配置
  51. listener:
  52. simple:
  53. retry:
  54. enabled: true # 支持重试
  55. #消费端
  56. concurrency: 5
  57. #最大消费端数
  58. max-concurrency: 10
  59. #自动签收auto 手动 manual
  60. acknowledge-mode: manual # 设置消费端手动 ack
  61. #限流(海量数据,同时只能过来一条)
  62. prefetch: 1

1.2 配置类说明

  1. package com.cli.springboot_rabbitmq.config;
  2. import org.springframework.amqp.core.Binding;
  3. import org.springframework.amqp.core.BindingBuilder;
  4. import org.springframework.amqp.core.DirectExchange;
  5. import org.springframework.amqp.core.FanoutExchange;
  6. import org.springframework.amqp.core.Queue;
  7. import org.springframework.amqp.core.TopicExchange;
  8. import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
  9. import org.springframework.amqp.rabbit.connection.ConnectionFactory;
  10. import org.springframework.amqp.rabbit.core.RabbitTemplate;
  11. import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
  12. import org.springframework.beans.factory.annotation.Autowired;
  13. import org.springframework.beans.factory.annotation.Value;
  14. import org.springframework.context.annotation.Bean;
  15. import org.springframework.context.annotation.Configuration;
  16. /**
  17. * @Author xiongmin
  18. * @Description
  19. * @Date 2021/2/25 18:31
  20. * @Version 1.0
  21. **/
  22. @Configuration
  23. public class RabbitMQConfig {
  24. @Value("${spring.rabbitmq.addresses}")
  25. private String addresses;
  26. @Value("${spring.rabbitmq.virtual-host}")
  27. private String virtualHost;
  28. @Value("${spring.rabbitmq.username}")
  29. private String username;
  30. @Value("${spring.rabbitmq.password}")
  31. private String password;
  32. @Value("${spring.rabbitmq.default-exchange}")
  33. private String defaultExchange;
  34. @Autowired
  35. private ConfirmCallbackService confirmCallbackService;
  36. @Autowired
  37. private ReturnCallbackService returnCallbackService;
  38. public ConnectionFactory connectionFactory() {
  39. CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
  40. connectionFactory.setAddresses(addresses);
  41. connectionFactory.setUsername(username);
  42. connectionFactory.setPassword(password);
  43. connectionFactory.setVirtualHost(virtualHost);
  44. // connectionFactory.setPublisherConfirms(rabbitmqProps.isPublisherConfirms()); //消息回调,必须要设置
  45. return connectionFactory;
  46. }
  47. /**
  48. * 使用的自己的创建的RabbitMQ 完全没有使用到RabbitMQ相关的默认配置,即使在yaml文件中配置了消费者手动确认,使用如下的rabbitMQ 也是无效的,
  49. * 因为RabbitTemplate相关的ConnectionFactory 没有设置消费者手动确认消息,这里不会使用到yaml的配置
  50. * @return
  51. */
  52. @Bean(value = "rabbitTemplateMessaging")
  53. public RabbitTemplate rabbitTemplateMessaging() {
  54. final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
  55. // 消息类型转换器 -- 数据转换为json存入消息队列
  56. rabbitTemplate.setMessageConverter(jackson2MessageConverter());
  57. // 设置默认的交换机,如果发送的消息没有指定交换机,则使用默认的交换机
  58. rabbitTemplate.setExchange(defaultExchange);
  59. /**
  60. * mandatory:交换器无法根据自身类型和路由键找到一个符合条件的队列时的处理方式
  61. * true:RabbitMQ会调用Basic.Return命令将消息返回给生产者
  62. * false:RabbitMQ会把消息直接丢弃
  63. */
  64. rabbitTemplate.setMandatory(true);
  65. /**
  66. * 消费者确认收到消息后,手动ack回执回调处理
  67. */
  68. rabbitTemplate.setConfirmCallback(confirmCallbackService);
  69. /**
  70. * 消息投递到队列失败回调处理
  71. */
  72. rabbitTemplate.setReturnsCallback(returnCallbackService);
  73. // 服务端响应发送到的队列 reply-address 格式: exchange/routingKey
  74. rabbitTemplate.setReplyAddress("messaging/messaging-response");
  75. // 设置回复和接受消息的时间,单位为毫秒
  76. rabbitTemplate.setReplyTimeout(20000);
  77. rabbitTemplate.setReceiveTimeout(20000);
  78. return rabbitTemplate;
  79. }
  80. /**
  81. * @param connectionFactory connectionFactory属性信息会直接是用yaml配置文件中配置的
  82. * @return
  83. */
  84. @Bean(value = "rabbitTemplateInit")
  85. public RabbitTemplate rabbitTemplateInit(ConnectionFactory connectionFactory) {
  86. RabbitTemplate rabbitTemplate = new RabbitTemplate();
  87. rabbitTemplate.setConnectionFactory(connectionFactory);
  88. // 消息类型转换器 -- 数据转换为json存入消息队列
  89. rabbitTemplate.setMessageConverter(jackson2MessageConverter());
  90. // 设置默认的交换机,如果发送的消息没有指定交换机,则使用默认的交换机
  91. rabbitTemplate.setExchange(defaultExchange);
  92. /**
  93. * mandatory:交换器无法根据自身类型和路由键找到一个符合条件的队列时的处理方式
  94. * true:RabbitMQ会调用Basic.Return命令将消息返回给生产者
  95. * false:RabbitMQ会把消息直接丢弃
  96. */
  97. rabbitTemplate.setMandatory(true);
  98. /**
  99. * 消费者确认收到消息后,手动ack回执回调处理
  100. */
  101. rabbitTemplate.setConfirmCallback(confirmCallbackService);
  102. /**
  103. * 消息投递到队列失败回调处理
  104. */
  105. rabbitTemplate.setReturnsCallback(returnCallbackService);
  106. // 服务端响应发送到的队列 reply-address 格式: exchange/routingKey
  107. rabbitTemplate.setReplyAddress("messaging/messaging-response");
  108. // 设置回复和接受消息的时间,单位为毫秒
  109. rabbitTemplate.setReplyTimeout(20000);
  110. rabbitTemplate.setReceiveTimeout(20000);
  111. return rabbitTemplate;
  112. }
  113. /**
  114. * 的作用解释如下:
  115. * 数据转换为json存入消息队列
  116. * [[RabbitMQ]Jackson2JsonMessageConverter转换实体类常的问题](https://blog.csdn.net/qq_31897023/article/details/103875594)
  117. * [Springboot Rabbitmq 使用Jackson2JsonMessageConverter 消息传递后转对象](https://www.cnblogs.com/timseng/p/11688019.html)
  118. * @return
  119. */
  120. @Bean
  121. public Jackson2JsonMessageConverter jackson2MessageConverter() {
  122. Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
  123. return converter;
  124. }
  125. //--------------------------配置相关的Exchange和相关的Queue----------------------------------
  126. /**
  127. * 创建topic模式的交换器
  128. * @return
  129. */
  130. @Bean
  131. TopicExchange exchange() {
  132. return new TopicExchange("topicExchange",true,false);
  133. }
  134. /**
  135. * 创建fanout模式的交换器
  136. * 发布订阅模式
  137. * 发布订阅是交换机针对队列来说的,一个消息可投入一个或多个队列
  138. * 注意:多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免的消息被重复消费。
  139. * @return
  140. */
  141. @Bean
  142. FanoutExchange fanoutExchange() {
  143. // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效; 如果持久性,则RabbitMQ重启后,交换机还存在
  144. // autoDelete:是否自动删除,当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它
  145. return new FanoutExchange("fanoutExchange",true,false);
  146. }
  147. //Direct交换机 起名:TestDirectExchange
  148. @Bean
  149. DirectExchange TestDirectExchange() {
  150. // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
  151. // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
  152. return new DirectExchange("TestDirectExchange",true,false);
  153. }
  154. //队列 起名:TestDirectQueue
  155. @Bean
  156. public Queue TestDirectQueue() {
  157. // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
  158. // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
  159. // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
  160. // return new Queue("TestDirectQueue",true,true,false);
  161. //一般设置一下队列的持久化就好,其余两个就是默认false
  162. return new Queue("TestDirectQueue",true);
  163. }
  164. //绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
  165. @Bean
  166. Binding bindingDirect() {
  167. return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
  168. }
  169. // 默认的 Direct交换机 起名:DefaultDirectExchange
  170. @Bean
  171. DirectExchange DefaultDirectExchange() {
  172. // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
  173. // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
  174. return new DirectExchange(defaultExchange,true,false);
  175. }
  176. //队列 起名:DefaultDirectQueue
  177. @Bean
  178. public Queue DefaultDirectQueue() {
  179. // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
  180. // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
  181. // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
  182. // return new Queue("TestDirectQueue",true,true,false);
  183. //一般设置一下队列的持久化就好,其余两个就是默认false
  184. return new Queue("DefaultDirectQueue",true);
  185. }
  186. //绑定 将队列和交换机绑定, 并设置用于匹配键:DefaultDirectRouting
  187. @Bean
  188. Binding bindingDefaultDirect() {
  189. return BindingBuilder.bind(DefaultDirectQueue()).to(DefaultDirectExchange()).with("DefaultDirectRouting");
  190. }
  191. /**
  192. * 创建三个队列 :fanout.A fanout.B fanout.C
  193. * 将三个队列都绑定在交换机 fanoutExchange 上
  194. * 因为是扇型交换机, 路由键无需配置,配置也不起作用
  195. */
  196. @Bean
  197. public Queue queueA() {
  198. return new Queue("fanout.A");
  199. }
  200. @Bean
  201. public Queue queueB() {
  202. return new Queue("fanout.B");
  203. }
  204. @Bean
  205. public Queue queueC() {
  206. return new Queue("fanout.C");
  207. }
  208. @Bean
  209. Binding bindingExchangeA() {
  210. return BindingBuilder.bind(queueA()).to(fanoutExchange());
  211. }
  212. @Bean
  213. Binding bindingExchangeB() {
  214. return BindingBuilder.bind(queueB()).to(fanoutExchange());
  215. }
  216. @Bean
  217. Binding bindingExchangeC() {
  218. return BindingBuilder.bind(queueC()).to(fanoutExchange());
  219. }
  220. //绑定键
  221. public final static String MAN = "topic.MAN";
  222. public final static String WOMAN = "topic.WOMAN";
  223. @Bean
  224. public Queue firstQueue() {
  225. return new Queue(RabbitMQConfig.MAN);
  226. }
  227. @Bean
  228. public Queue secondQueue() {
  229. return new Queue(RabbitMQConfig.WOMAN);
  230. }
  231. //将firstQueue和topicExchange绑定,而且绑定的键值为topic.MAN
  232. //这样只要是消息携带的路由键是topic.MAN,才会分发到该队列
  233. @Bean
  234. Binding bindingExchangeMessage() {
  235. return BindingBuilder.bind(firstQueue()).to(exchange()).with(MAN);
  236. }
  237. //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#
  238. // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列
  239. @Bean
  240. Binding bindingExchangeMessage2() {
  241. return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");
  242. }
  243. }
交换机的属性

除交换机类型外,在声明交换机时还可以附带许多其他的属性,其中最重要的几个分别是:

  • Name:交换机名称
  • Durability:是否持久化。如果持久性,则RabbitMQ重启后,交换机还存在
  • Auto-delete:当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它
  • Arguments:扩展参数

2. 消息转换器

可以注意到的是上面的配置中RabbitTemplate设置的消息转换器是Jackson2JsonMessageConverter;下面将对消息转换器说明

消息转换器接口源码:

  1. /*
  2. * Copyright 2002-2019 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * https://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.amqp.support.converter;
  17. import java.lang.reflect.Type;
  18. import org.springframework.amqp.core.Message;
  19. import org.springframework.amqp.core.MessageProperties;
  20. import org.springframework.lang.Nullable;
  21. /**
  22. * Message converter interface.
  23. *
  24. * @author Mark Fisher
  25. * @author Mark Pollack
  26. * @author Gary Russell
  27. */
  28. public interface MessageConverter {
  29. /**
  30. * Convert a Java object to a Message. 将消息对象转换成java对象。
  31. * @param object the object to convert
  32. * @param messageProperties The message properties.
  33. * @return the Message
  34. * @throws MessageConversionException in case of conversion failure
  35. */
  36. Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException;
  37. /**
  38. * Convert a Java object to a Message. 将java对象和属性对象转换成Message对象。
  39. * The default implementation calls {@link #toMessage(Object, MessageProperties)}.
  40. * @param object the object to convert
  41. * @param messageProperties The message properties.
  42. * @param genericType the type to use to populate type headers.
  43. * @return the Message
  44. * @throws MessageConversionException in case of conversion failure
  45. * @since 2.1
  46. */
  47. default Message toMessage(Object object, MessageProperties messageProperties, @Nullable Type genericType)
  48. throws MessageConversionException {
  49. return toMessage(object, messageProperties);
  50. }
  51. /**
  52. * Convert from a Message to a Java object.
  53. * @param message the message to convert
  54. * @return the converted Java object
  55. * @throws MessageConversionException in case of conversion failure
  56. */
  57. Object fromMessage(Message message) throws MessageConversionException;
  58. }

可以通过实现MessageConverter接口,实现自定义的消息转换器,如下:

  1. import org.springframework.amqp.core.Message;
  2. import org.springframework.amqp.core.MessageProperties;
  3. import org.springframework.amqp.support.converter.MessageConversionException;
  4. import org.springframework.amqp.support.converter.MessageConverter;
  5. public class TestMessageConverter implements MessageConverter {
  6. @Override
  7. public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
  8. System.out.println("=======toMessage=========");
  9. return new Message(object.toString().getBytes(),messageProperties);
  10. }
  11. //消息类型转换器中fromMessage方法返回的类型就是消费端处理器接收的类型
  12. @Override
  13. public Object fromMessage(Message message) throws MessageConversionException {
  14. System.out.println("=======fromMessage=========");
  15. return new String(message.getBody());
  16. }
  17. }

简介Jackson2JsonMessageConverter消息转换器:

使用Jackson2JsonMessageConverter后,反序列化时要求发送的类和接受的类完全一样(字段,类名,包路径)。【也就是消息的生产的消息类型和消息的消费方法的消息参数类型一致

3. 生产者消费者使用案例

3.1 相关注解

3.2 消息的发送处理

  1. package com.cli.springboot_rabbitmq.config;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.amqp.core.MessageDeliveryMode;
  4. import org.springframework.amqp.rabbit.connection.CorrelationData;
  5. import org.springframework.amqp.rabbit.core.RabbitTemplate;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. import javax.annotation.Resource;
  9. import java.util.UUID;
  10. /**
  11. * @Author xiongmin
  12. * @Description
  13. * @Date 2021/2/26 10:35
  14. * @Version 1.0
  15. **/
  16. @Slf4j
  17. @Service
  18. public class RabbitMQService {
  19. // @Resource(name = "rabbitTemplateMessaging")
  20. // private RabbitTemplate rabbitTemplate;
  21. /**
  22. * 换成这个RabbitTemplate 之后,所有的消费者都要手动Ack消息确认被消费
  23. */
  24. @Resource(name = "rabbitTemplateInit")
  25. private RabbitTemplate rabbitTemplate;
  26. @Autowired
  27. private RabbitMQConfig amqpConfig;
  28. // 发送消息的后置处理器,MessagePostProcessor类的postProcessMessage方法得到的Message就是将参数Object内容转换成Message对象
  29. // 没有指定具体的Exchange就会使用默认的Exchange
  30. public void messageDeliver(String routineKey, Object o) {
  31. rabbitTemplate.convertAndSend(routineKey, o, message -> {
  32. System.out.println("-------处理前message-------------");
  33. System.out.println(message);
  34. // 设置message的一些头部信息
  35. message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
  36. message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
  37. return message;
  38. });
  39. }
  40. // 发送消息的后置处理器,MessagePostProcessor类的postProcessMessage方法得到的Message就是将参数Object内容转换成Message对象
  41. public void messageDeliver(String exchange, String routineKey, Object o) {
  42. rabbitTemplate.convertAndSend(exchange, routineKey, o, message -> {
  43. System.out.println("-------处理前message-------------");
  44. System.out.println(message);
  45. // 设置message的一些头部信息
  46. message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
  47. message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
  48. message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
  49. System.out.println("-------处理后message-------------");
  50. System.out.println(message);
  51. return message;
  52. },new CorrelationData(UUID.randomUUID().toString()));
  53. }
  54. // 发送消息的后置处理器,MessagePostProcessor类的postProcessMessage方法得到的Message就是将参数Object内容转换成Message对象
  55. public void messageDeliver(String exchange, String routineKey, User user) {
  56. rabbitTemplate.convertAndSend(exchange, routineKey, user, message -> {
  57. System.out.println("-------处理前message-------------");
  58. System.out.println(message);
  59. // 设置message的一些头部信息
  60. /**
  61. * 消息在消费时,先根据消息投中给的messageId找到对饮给的User, 在判断User的状态是否已经被消费过
  62. * 感觉这也是一种防止消息重复消费的方式,即使同一个消息投递多次,也你能防止消息重复消费
  63. */
  64. message.getMessageProperties().setMessageId(user.getName());
  65. message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
  66. message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
  67. System.out.println("-------处理后message-------------");
  68. System.out.println(message);
  69. return message;
  70. },new CorrelationData(UUID.randomUUID().toString()));
  71. }
  72. /**
  73. * 防止同一时间段有好多个同步配置的消息发送,避免多个重复消息
  74. * @param deviceId
  75. */
  76. public void deliveConfigSyncMsgBeforeCheck(String deviceId) {
  77. if (connect == null) {
  78. connect = rabbitTemplate.getConnectionFactory().createConnection();
  79. // connect = amqpConfig.connectionFactory().createConnection();
  80. }
  81. if (channel == null) {
  82. try {
  83. channel = connect.createChannel(false);
  84. } catch (Exception e) {
  85. connect = amqpConfig.connectionFactory().createConnection();
  86. channel = connect.createChannel(false);
  87. }
  88. }
  89. try {
  90. long messageCount = channel.messageCount("f5-config");
  91. // 如果f5-ltm-state为空那我就发一条消息,否者就不发,防止MQ消息堆积
  92. if (messageCount == 0) {
  93. messageDeliver("f5.config", deviceId);
  94. } else {
  95. boolean a = false;
  96. int i;
  97. for(i=0; i < messageCount; i++) {
  98. channel.basicQos(1);
  99. GetResponse response = channel.basicGet("f5-config", true);
  100. if (response == null) {
  101. continue;
  102. }
  103. AMQP.BasicProperties props = response.getProps();
  104. byte[] body = response.getBody();
  105. String message = new String(body);
  106. channel.basicPublish("messaging","f5.config", props, message.getBytes("UTF-8"));
  107. if (message.contains(deviceId)) {
  108. a = true;
  109. break;
  110. }
  111. }
  112. if (!a && i >= messageCount) {
  113. messageDeliver("f5.config", deviceId);
  114. }
  115. }
  116. } catch (Exception e) {
  117. logger.info("Retrieve Message fail " + e.getMessage());
  118. }
  119. }
  120. }

发送消息的后置处理器,MessagePostProcessor类的postProcessMessage方法得到的Message就是将参数Object内容转换成Message对象

  1. rabbitTemplate.convertAndSend(exchange, routineKey, o, message -> {
  2. System.out.println("-------处理前message-------------");
  3. System.out.println(message);
  4. // 设置message的一些头部信息
  5. message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
  6. message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
  7. message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
  8. System.out.println("-------处理后message-------------");
  9. System.out.println(message);
  10. return message;
  11. },new CorrelationData(UUID.randomUUID().toString()));
  12. rabbitTemplateconvertAndSend的相关源码:
  13. @Override
  14. public void convertAndSend(String exchange, String routingKey, final Object message,
  15. final MessagePostProcessor messagePostProcessor,
  16. @Nullable CorrelationData correlationData) throws AmqpException {
  17. Message messageToSend = convertMessageIfNecessary(message);
  18. messageToSend = messagePostProcessor.postProcessMessage(messageToSend, correlationData,
  19. nullSafeExchange(exchange), nullSafeRoutingKey(routingKey));
  20. send(exchange, routingKey, messageToSend, correlationData);
  21. }

3.3 消息的消费处理

@RabbitListener和@RabbitHandler搭配使用

@RabbitListener可以标注在类上面,当使用在类上面的时候,需要配合@RabbitHandler注解一起使用,@RabbitListener标注在类上面表示当有收到消息的时候,就交给带有@RabbitHandler的方法处理,具体找哪个方法处理,需要跟进MessageConverter转换后的java对象。

  1. package com.cli.springboot_rabbitmq.consumer;
  2. import com.cli.springboot_rabbitmq.model.User;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.amqp.rabbit.annotation.RabbitHandler;
  5. import org.springframework.amqp.rabbit.annotation.RabbitListener;
  6. import org.springframework.messaging.handler.annotation.Payload;
  7. import org.springframework.stereotype.Component;
  8. import java.util.Map;
  9. /**
  10. * @Author xiongmin
  11. * @Description
  12. * @Date 2021/2/26 11:54
  13. * @Version 1.0
  14. **/
  15. @Component
  16. @Slf4j
  17. @RabbitListener(queues = "topic.WOMAN") //监听的队列名称 topic.WOMAN
  18. public class TopicConsumerListenerTwo {
  19. // @RabbitHandler
  20. // public void receive(@Payload String message, @Headers Map<String, Object> headers) {
  21. // try {
  22. // log.info("TopicReceiver消费者收到消息 : ");
  23. // log.info("message = " + message);
  24. //
  25. // log.info("TopicReceiver消费者收到消息头部信息 : ");
  26. // if (null != headers && !headers.isEmpty()) {
  27. // headers.forEach((key, value) -> {
  28. // log.info(key + ": " + value + "\n");
  29. // });
  30. // } else {
  31. // log.info("headers is empty");
  32. // }
  33. // } catch (Exception e) {
  34. // log.error(e.getMessage(), e);
  35. // }
  36. // }
  37. /**
  38. * 如果生产者生产的消息类型为String,那么就会执行该方法处理消息
  39. * @param message
  40. */
  41. @RabbitHandler
  42. public void receive(@Payload String message) {
  43. try {
  44. log.info("TopicReceiver消费者收到消息 : ");
  45. log.info("message = " + message);
  46. } catch (Exception e) {
  47. log.error(e.getMessage(), e);
  48. }
  49. }
  50. // @RabbitHandler
  51. // public void receive(@Payload User user, @Headers Map<String, Object> headers) {
  52. // try {
  53. // log.info("TopicReceiver消费者收到消息 : ");
  54. // log.info("user = " + user);
  55. //
  56. // log.info("TopicReceiver消费者收到消息头部信息 : ");
  57. // if (null != headers && !headers.isEmpty()) {
  58. // headers.forEach((key, value) -> {
  59. // log.info(key + ": " + value + "\n");
  60. // });
  61. // } else {
  62. // log.info("headers is empty");
  63. // }
  64. // } catch (Exception e) {
  65. // log.error(e.getMessage(), e);
  66. // }
  67. // }
  68. /**
  69. * 如果生产者生产的消息类型为User,那么就会执行该方法处理消息
  70. * @param user
  71. */
  72. @RabbitHandler
  73. public void receive(@Payload User user) {
  74. try {
  75. log.info("TopicReceiver消费者收到消息 : ");
  76. log.info("user = " + user);
  77. } catch (Exception e) {
  78. log.error(e.getMessage(), e);
  79. }
  80. }
  81. /**
  82. * 如果生产者生产的消息类型为Map,那么就会执行该方法处理消息
  83. * @param message
  84. */
  85. @RabbitHandler
  86. public void receive(@Payload Map message) {
  87. log.info("TopicReceiver {topic.WOMAN}消费者收到消息 : ");
  88. if (null != message && !message.isEmpty()) {
  89. message.forEach((key, value) -> {
  90. log.info(key + ": " + value + "\n");
  91. });
  92. } else {
  93. log.info("message is empty");
  94. }
  95. }
  96. /**
  97. * @DO 总结
  98. * @RabbitListener标注在类上面表示当有收到消息的时候,就交给带有@RabbitHandler的方法处理,具体找哪个方法处理,需要更具MessageConverter转换后的java对象。
  99. * 注意,如果需要消息的头部信息,由于头部信息是一个MAP数据结构,那么Payload的数据类型不能为MAP类型,否者会报错,且即使其他不是Map类型的Payload,要获取消息的头部信息也会报错
  100. * 因为消费者消息Payload是MAP的类型的消息时,会查看那个方法中有MAP, 当有多个方法中具有MAP参数时,此时程序也不知道该使用哪个方法来处理这个消息,就会抛出异常
  101. */
  102. }

@RabbitListener标注在类上面表示当有收到消息的时候,就交给带有@RabbitHandler的方法处理,具体找哪个方法处理,需要更具MessageConverter转换后的java对象。

注意: 如果需要消息的头部信息,由于头部信息是一个MAP数据结构,那么Payload的数据类型不能为MAP类型,否者会报错,且即使其他不是Map类型的Payload,要获取消息的头部信息也会报错;

因为消费者消息Payload是MAP的类型的消息时,会查看那个方法中有MAP, 当有多个方法中具有MAP参数时,此时程序也不知道该使用哪个方法来处理这个消息,就会抛出异常

4. 消息确认机制

yaml配置打开confirmCallback returnCallback

  1. server:
  2. port: 11000
  3. spring:
  4. application:
  5. name: rabbitmq-test
  6. rabbitmq:
  7. # 单机IP配置
  8. # host: rabbitmq-host
  9. # port: 5672
  10. # 集群IP配置
  11. addresses: rabbitmq01-host:5672,rabbitmq02-host:5672
  12. # 用户名和密码,默认都是guest
  13. username: xiongmin
  14. password: xiongmin
  15. # 交换器名可以不设置默认 【"" --> /】 交换器
  16. virtual-host: /rabbitmq_test
  17. publisher-returns: true # 发送者开启 return 确认机制
  18. publisher-confirm-type: correlated # 发送者开启 confirm 确认机制 等价于 spring.rabbitmq.publisher-returns=true
  19. #连接超时时间
  20. connection-timeout: 15000
  21. # 使用return-callback时必须设置mandatory为true
  22. template:
  23. mandatory: true
  24. default-exchange: default_exchange
  25. # 消费端配置
  26. listener:
  27. simple:
  28. retry:
  29. enabled: true # 支持重试
  30. #消费端
  31. concurrency: 5
  32. #最大消费端数
  33. max-concurrency: 10
  34. #自动签收auto 手动 manual
  35. acknowledge-mode: manual # 设置消费端手动 ack
  36. #限流(海量数据,同时只能过来一条)
  37. prefetch: 1

分别实现confirmCallbackreturnCallback回调的类接口

ConfirmCallbackService.java

  1. package com.cli.springboot_rabbitmq.config;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.amqp.rabbit.connection.CorrelationData;
  4. import org.springframework.amqp.rabbit.core.RabbitTemplate;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * @Author xiongmin
  8. * @Description 监听消息是否发送交换机回调 只有投递失败的时候才会执行
  9. * @Date 2021/2/27 11:16
  10. * @Version 1.0
  11. **/
  12. @Component
  13. @Slf4j
  14. public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {
  15. @Override
  16. public void confirm(CorrelationData correlationData, boolean ack, String cause) {
  17. if (!ack) {
  18. log.error("消息发送异常!");
  19. } else {
  20. log.info("发送者爸爸已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, cause);
  21. }
  22. }
  23. }

ReturnCallbackService.java

  1. package com.cli.springboot_rabbitmq.config;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.amqp.core.ReturnedMessage;
  4. import org.springframework.amqp.rabbit.core.RabbitTemplate;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * @Author xiongmin
  8. * @Description 消息为路由到队列监听类 只有投递失败的时候才会执行
  9. * @Date 2021/2/27 11:20
  10. * @Version 1.0
  11. * 如果消息未能投递到目标 queue 里将触发回调 returnCallback ,一旦向 queue 投递消息未成功,这里一般会记录下当前消息的详细投递数据,方便后续做重发或者补偿等操作。
  12. **/
  13. @Component
  14. @Slf4j
  15. public class ReturnCallbackService implements RabbitTemplate.ReturnsCallback {
  16. @Override
  17. public void returnedMessage(ReturnedMessage returned) {
  18. log.error("Fail... message:{},从交换机exchange:{},以路由键routingKey:{}," +
  19. "未找到匹配队列,replyCode:{},replyText:{}",
  20. returned.getMessage(), returned.getExchange(), returned.getRoutingKey(), returned.getReplyCode(), returned.getReplyText());
  21. }
  22. }

设置RabbitTemplate

  1. /**
  2. * @param connectionFactory connectionFactory属性信息会直接是用yaml配置文件中配置的
  3. * @return
  4. */
  5. @Bean(value = "rabbitTemplateInit")
  6. public RabbitTemplate rabbitTemplateInit(ConnectionFactory connectionFactory) {
  7. RabbitTemplate rabbitTemplate = new RabbitTemplate();
  8. rabbitTemplate.setConnectionFactory(connectionFactory);
  9. // 消息类型转换器 -- 数据转换为json存入消息队列
  10. rabbitTemplate.setMessageConverter(jackson2MessageConverter());
  11. // 设置默认的交换机,如果发送的消息没有指定交换机,则使用默认的交换机
  12. rabbitTemplate.setExchange(defaultExchange);
  13. /**
  14. * mandatory:交换器无法根据自身类型和路由键找到一个符合条件的队列时的处理方式
  15. * true:RabbitMQ会调用Basic.Return命令将消息返回给生产者
  16. * false:RabbitMQ会把消息直接丢弃
  17. */
  18. rabbitTemplate.setMandatory(true);
  19. /**
  20. * 消费者确认收到消息后,手动ack回执回调处理
  21. */
  22. rabbitTemplate.setConfirmCallback(confirmCallbackService);
  23. /**
  24. * 消息投递到队列失败回调处理
  25. */
  26. rabbitTemplate.setReturnsCallback(returnCallbackService);
  27. // 服务端响应发送到的队列 reply-address 格式: exchange/routingKey
  28. rabbitTemplate.setReplyAddress("messaging/messaging-response");
  29. // 设置回复和接受消息的时间,单位为毫秒
  30. rabbitTemplate.setReplyTimeout(20000);
  31. rabbitTemplate.setReceiveTimeout(20000);
  32. return rabbitTemplate;
  33. }

消费者消费消息

  1. package com.cli.springboot_rabbitmq.consumer;
  2. import com.rabbitmq.client.Channel;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.amqp.core.Message;
  5. import org.springframework.amqp.rabbit.annotation.RabbitHandler;
  6. import org.springframework.amqp.rabbit.annotation.RabbitListener;
  7. import org.springframework.messaging.handler.annotation.Payload;
  8. import org.springframework.stereotype.Component;
  9. import java.io.IOException;
  10. import java.util.HashMap;
  11. /**
  12. * @Author xiongmin
  13. * @Description
  14. * @Date 2021/2/26 10:51
  15. * @Version 1.0
  16. **/
  17. @Component
  18. @Slf4j
  19. @RabbitListener(queues = "TestDirectQueue") //监听的队列名称 TestDirectQueue,监听多个队列需要用单号分隔
  20. public class DirectConsumerListener {
  21. @RabbitHandler
  22. public void receive(@Payload HashMap msg, Channel channel, Message message) throws IOException {
  23. try {
  24. log.info("DirectReceiver消费者收到消息 : ");
  25. if (null != msg && !msg.isEmpty()) {
  26. msg.forEach((key, value) -> {
  27. log.info(key + ": " + value + "\n");
  28. });
  29. } else {
  30. log.info("msg is empty");
  31. }
  32. // 消费者手动ACK确认消息被消费, 生产者会执行ConfirmCallback回调
  33. channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
  34. } catch (Exception e) {
  35. log.error(e.getMessage(),e);
  36. try {
  37. if (message.getMessageProperties().getRedelivered()) {
  38. log.error("消息已重复处理失败,拒绝再次接收...");
  39. channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息
  40. } else {
  41. log.error("消息即将返回队列再次处理");
  42. channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
  43. }
  44. } catch (Exception ex) {
  45. log.error("消息消费异常时处理失败",ex.getMessage(),ex);
  46. }
  47. }
  48. }
  49. }

消费者回执方法

消费消息有三种回执方法,我们来分析一下每种方法的含义。

1、basicAck

basicAck:表示成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。

  1. void basicAck(long deliveryTag, boolean multiple)
  2. 复制代码

deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。手动消息确认模式下,我们可以对指定deliveryTag的消息进行acknackreject等操作。

multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。

举个栗子: 假设我先发送三条消息deliveryTag分别是5、6、7,可它们都没有被确认,当我发第四条消息此时deliveryTag为8,multiple设置为 true,会将5、6、7、8的消息全部进行确认。

2、basicNack

basicNack :表示失败确认,一般在消费消息业务异常时用到此方法,可以将消息重新投递入队列。

  1. void basicNack(long deliveryTag, boolean multiple, boolean requeue)
  2. 复制代码

deliveryTag:表示消息投递序号。

multiple:是否批量确认。

requeue:值为 true 消息将重新入队列。

3、basicReject

basicReject:拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。

  1. void basicReject(long deliveryTag, boolean requeue)
  2. 复制代码

deliveryTag:表示消息投递序号。

requeue:值为 true 消息将重新入队列。

ConfirmCallBackreturnBackCall回调执行时机如下:

先从总体的情况分析,推送消息存在四种情况:

①消息推送到server,但是在server里找不到交换机

②消息推送到server,找到交换机了,但是没找到队列

③消息推送到sever,交换机和队列啥都没找到

④消息推送成功

那么我先写几个接口来分别测试和认证下以上4种情况,消息确认触发回调函数的情况:

①消息推送到server,但是在server里找不到交换机

结论: ①这种情况触发的是 ConfirmCallback 回调函数。

②消息推送到server,找到交换机了,但是没找到队列

结论:②这种情况触发的是 ConfirmCallback和RetrunCallback两个回调函数。

③消息推送到sever,交换机和队列啥都没找到

这种情况其实一看就觉得跟①很像,没错 ,③和①情况回调是一致的,所以不做结果说明了。

结论: ③这种情况触发的是 ConfirmCallback 回调函数。

④消息推送成功

结论: ④这种情况触发的是 ConfirmCallback 回调函数。

相关链接

RabbitMD大揭秘的更多相关文章

  1. 【腾讯Bugly干货分享】iOS黑客技术大揭秘

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5791da152168f2690e72daa4 “8小时内拼工作,8小时外拼成长 ...

  2. Spark Streaming揭秘 Day3-运行基石(JobScheduler)大揭秘

    Spark Streaming揭秘 Day3 运行基石(JobScheduler)大揭秘 引子 作为一个非常强大框架,Spark Streaming兼具了流处理和批处理的特点.还记得第一天的谜团么,众 ...

  3. 【高德地图API】汇润做爱地图技术大揭秘

    原文:[高德地图API]汇润做爱地图技术大揭秘 昨日收到了高德地图微信公众号的消息推送,说有[一大波免费情趣用品正在袭来],点进去看了一眼,说一个电商公司(估计是卖情趣用品的)用高德云图制作了一张可以 ...

  4. 【高德地图API】从零开始学高德JS API(七)——定位方式大揭秘

    原文:[高德地图API]从零开始学高德JS API(七)——定位方式大揭秘 摘要:关于定位,分为GPS定位和网络定位2种.GPS定位,精度较高,可达到10米,但室内不可用,且超级费电.网络定位,分为w ...

  5. 诗人般的机器学习,ML工作原理大揭秘

    诗人般的机器学习,ML工作原理大揭秘 https://mp.weixin.qq.com/s/7N96aPAM_M6t0rV0yMLKbg 选自arXiv 作者:Cassie Kozyrkov 机器之心 ...

  6. 编码(1)学点编码知识又不会死:Unicode的流言终结者和编码大揭秘

    学点编码知识又不会死:Unicode的流言终结者和编码大揭秘 http://www.freebuf.com/articles/web/25623.html 如果你是一个生活在2003年的程序员,却不了 ...

  7. [百家号]看完再也不会被坑!笔记本接口大揭秘:HDMI、DP、雷电

    看完再也不会被坑!笔记本接口大揭秘:HDMI.DP.雷电 https://baijiahao.baidu.com/s?id=1577309281431438678&wfr=spider& ...

  8. 谷歌钦定的编程语言Kotlin大揭秘

    第一时间关注程序猿(媛)身边的故事 谷歌钦定的编程语言Kotlin大揭秘 语法+高级特性+实现原理:移动开发者升职加薪宝典! 谷歌作为世界级的科技公司巨头,强悍的技术研发与创新能力使其一直是业界的楷模 ...

  9. Web安全大揭秘

    web安全大揭秘,通常会有那些web安全问题呢? 1,xss 2,sql注入 3,ddos攻击

随机推荐

  1. [翻译] Cassandra 分布式结构化存储系统

    Cassandra 分布式结构化存储系统 摘要 Cassandra 是一个分布式存储系统,用于管理分布在许多商品服务器上的大量结构化数据,同时提供无单点故障(no single point of fa ...

  2. Java学习day2

    今天学习了Java的数据类型.运算符.选择循环结构以及数组. Java所有的关键字全部小写 Java的数据类型与c语言相似,但是c语言定义数组时可以不主动对其初始化,而Java则必须先初始化,有动态和 ...

  3. Linux中权限对于文件和目录的区别

    Linux系统中的权限对于文件和目录来说,是有一定区别的 下面先列举下普通文件对应的权限 1)可读r:表示具有读取.浏览文件内容的权限,例如,可以对文件执行 cat.more.less.head.ta ...

  4. 字符串/16进制/ASCII码的转换

    1 /// <字符串转16进制格式,不够自动前面补零> 2 /// 假设文本框里面填写的是:01 02 03 04 05 06 3 /// Str获取的是01 02 03 04 05 06 ...

  5. 2021.08.09 P4868 Preprefix sum(树状数组)

    2021.08.09 P4868 Preprefix sum(树状数组) P4868 Preprefix sum - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意: 前缀和(pr ...

  6. Linux:文件解压、复制和移动的若干坑

    Linux下进行文件的解压.复制.移动应该是最常见的操作了.尤其是我们在项目中使用大量的数据集文件(比如机器学习)时.然而使用这些命令时一不留神就会掉进坑里,这篇文章我们就来细数用Shell进行文件操 ...

  7. IDEA打包javaFX及踩坑解决

    开门见山的说,先打包,再说坑. File-->Project Structure --> Artifacts-->(此处点加号)JAR-->From modules with ...

  8. vue-cli4 vue-config.js配置及其备注

    // vue.config.js const path = require('path'); const CompressionWebpackPlugin = require("compre ...

  9. 【面试普通人VS高手系列】Redis和Mysql如何保证数据一致性

    今天分享一道一线互联网公司高频面试题. "Redis和Mysql如何保证数据一致性". 这个问题难倒了不少工作5年以上的程序员,难的不是问题本身,而是解决这个问题的思维模式. 下面 ...

  10. 如何查看和修改Windows远程桌面端口

    Windows远程桌面的默认端口为3389.基于安全性考虑,部分用户有修改默认端口的需要,以减少通过远程桌面恶意攻击和扫描主机的次数. 因此今天带大家一起学习下,如何查看和修改Windows远程桌面的 ...