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最基本的基础配置如下:

server:
port: 11000 spring:
application:
name: rabbitmq-test rabbitmq:
# 单机IP配置
# host: rabbitmq-host
# port: 5672 # 集群IP配置
addresses: rabbitmq01-host:5672,rabbitmq02-host:5672 # 用户名和密码,默认都是guest
username: xiongmin
password: xiongmin # 交换器名可以不设置默认 【"" --> /】 交换器
virtual-host: /rabbitmq_test
publisher-returns: true # 发送者开启 return 确认机制
publisher-confirm-type: correlated # 发送者开启 confirm 确认机制 等价于 spring.rabbitmq.publisher-returns=true
default-exchange: default_exchange
listener:
simple:
acknowledge-mode: manual # 设置消费端手动 ack
retry:
enabled: true # 支持重试 ---------------------------------------------------------------------------------------------- server:
port: 11000 spring:
application:
name: rabbitmq-test rabbitmq:
# 单机IP配置
# host: rabbitmq-host
# port: 5672 # 集群IP配置
addresses: rabbitmq01-host:5672,rabbitmq02-host:5672 # 用户名和密码,默认都是guest
username: xiongmin
password: xiongmin # 交换器名可以不设置默认 【"" --> /】 交换器
virtual-host: /rabbitmq_test
publisher-returns: true # 发送者开启 return 确认机制
publisher-confirm-type: correlated # 发送者开启 confirm 确认机制 等价于 spring.rabbitmq.publisher-returns=true
#连接超时时间
connection-timeout: 15000
# 使用return-callback时必须设置mandatory为true
template:
mandatory: true
default-exchange: default_exchange
# 消费端配置
listener:
simple:
retry:
enabled: true # 支持重试
#消费端
concurrency: 5
#最大消费端数
max-concurrency: 10
#自动签收auto 手动 manual
acknowledge-mode: manual # 设置消费端手动 ack
#限流(海量数据,同时只能过来一条)
prefetch: 1

1.2 配置类说明

package com.cli.springboot_rabbitmq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
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.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* @Author xiongmin
* @Description
* @Date 2021/2/25 18:31
* @Version 1.0
**/
@Configuration
public class RabbitMQConfig { @Value("${spring.rabbitmq.addresses}")
private String addresses; @Value("${spring.rabbitmq.virtual-host}")
private String virtualHost; @Value("${spring.rabbitmq.username}")
private String username; @Value("${spring.rabbitmq.password}")
private String password; @Value("${spring.rabbitmq.default-exchange}")
private String defaultExchange; @Autowired
private ConfirmCallbackService confirmCallbackService;
@Autowired
private ReturnCallbackService returnCallbackService; public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses(addresses);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(virtualHost);
// connectionFactory.setPublisherConfirms(rabbitmqProps.isPublisherConfirms()); //消息回调,必须要设置
return connectionFactory;
} /**
* 使用的自己的创建的RabbitMQ 完全没有使用到RabbitMQ相关的默认配置,即使在yaml文件中配置了消费者手动确认,使用如下的rabbitMQ 也是无效的,
* 因为RabbitTemplate相关的ConnectionFactory 没有设置消费者手动确认消息,这里不会使用到yaml的配置
* @return
*/
@Bean(value = "rabbitTemplateMessaging")
public RabbitTemplate rabbitTemplateMessaging() {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
// 消息类型转换器 -- 数据转换为json存入消息队列
rabbitTemplate.setMessageConverter(jackson2MessageConverter());
// 设置默认的交换机,如果发送的消息没有指定交换机,则使用默认的交换机
rabbitTemplate.setExchange(defaultExchange);
/**
* mandatory:交换器无法根据自身类型和路由键找到一个符合条件的队列时的处理方式
* true:RabbitMQ会调用Basic.Return命令将消息返回给生产者
* false:RabbitMQ会把消息直接丢弃
*/
rabbitTemplate.setMandatory(true);
/**
* 消费者确认收到消息后,手动ack回执回调处理
*/
rabbitTemplate.setConfirmCallback(confirmCallbackService);
/**
* 消息投递到队列失败回调处理
*/
rabbitTemplate.setReturnsCallback(returnCallbackService); // 服务端响应发送到的队列 reply-address 格式: exchange/routingKey
rabbitTemplate.setReplyAddress("messaging/messaging-response");
// 设置回复和接受消息的时间,单位为毫秒
rabbitTemplate.setReplyTimeout(20000);
rabbitTemplate.setReceiveTimeout(20000);
return rabbitTemplate;
} /**
* @param connectionFactory connectionFactory属性信息会直接是用yaml配置文件中配置的
* @return
*/
@Bean(value = "rabbitTemplateInit")
public RabbitTemplate rabbitTemplateInit(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
// 消息类型转换器 -- 数据转换为json存入消息队列
rabbitTemplate.setMessageConverter(jackson2MessageConverter());
// 设置默认的交换机,如果发送的消息没有指定交换机,则使用默认的交换机
rabbitTemplate.setExchange(defaultExchange);
/**
* mandatory:交换器无法根据自身类型和路由键找到一个符合条件的队列时的处理方式
* true:RabbitMQ会调用Basic.Return命令将消息返回给生产者
* false:RabbitMQ会把消息直接丢弃
*/
rabbitTemplate.setMandatory(true);
/**
* 消费者确认收到消息后,手动ack回执回调处理
*/
rabbitTemplate.setConfirmCallback(confirmCallbackService);
/**
* 消息投递到队列失败回调处理
*/
rabbitTemplate.setReturnsCallback(returnCallbackService); // 服务端响应发送到的队列 reply-address 格式: exchange/routingKey
rabbitTemplate.setReplyAddress("messaging/messaging-response");
// 设置回复和接受消息的时间,单位为毫秒
rabbitTemplate.setReplyTimeout(20000);
rabbitTemplate.setReceiveTimeout(20000);
return rabbitTemplate;
} /**
* 的作用解释如下:
* 数据转换为json存入消息队列
* [[RabbitMQ]Jackson2JsonMessageConverter转换实体类常的问题](https://blog.csdn.net/qq_31897023/article/details/103875594)
* [Springboot Rabbitmq 使用Jackson2JsonMessageConverter 消息传递后转对象](https://www.cnblogs.com/timseng/p/11688019.html)
* @return
*/
@Bean
public Jackson2JsonMessageConverter jackson2MessageConverter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
return converter;
} //--------------------------配置相关的Exchange和相关的Queue---------------------------------- /**
* 创建topic模式的交换器
* @return
*/
@Bean
TopicExchange exchange() {
return new TopicExchange("topicExchange",true,false);
} /**
* 创建fanout模式的交换器
* 发布订阅模式
* 发布订阅是交换机针对队列来说的,一个消息可投入一个或多个队列
* 注意:多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免的消息被重复消费。
* @return
*/
@Bean
FanoutExchange fanoutExchange() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效; 如果持久性,则RabbitMQ重启后,交换机还存在
// autoDelete:是否自动删除,当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它
return new FanoutExchange("fanoutExchange",true,false);
} //Direct交换机 起名:TestDirectExchange
@Bean
DirectExchange TestDirectExchange() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
return new DirectExchange("TestDirectExchange",true,false);
} //队列 起名:TestDirectQueue
@Bean
public Queue TestDirectQueue() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
// return new Queue("TestDirectQueue",true,true,false); //一般设置一下队列的持久化就好,其余两个就是默认false
return new Queue("TestDirectQueue",true);
} //绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
} // 默认的 Direct交换机 起名:DefaultDirectExchange
@Bean
DirectExchange DefaultDirectExchange() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
return new DirectExchange(defaultExchange,true,false);
} //队列 起名:DefaultDirectQueue
@Bean
public Queue DefaultDirectQueue() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
// return new Queue("TestDirectQueue",true,true,false); //一般设置一下队列的持久化就好,其余两个就是默认false
return new Queue("DefaultDirectQueue",true);
} //绑定 将队列和交换机绑定, 并设置用于匹配键:DefaultDirectRouting
@Bean
Binding bindingDefaultDirect() {
return BindingBuilder.bind(DefaultDirectQueue()).to(DefaultDirectExchange()).with("DefaultDirectRouting");
} /**
* 创建三个队列 :fanout.A fanout.B fanout.C
* 将三个队列都绑定在交换机 fanoutExchange 上
* 因为是扇型交换机, 路由键无需配置,配置也不起作用
*/
@Bean
public Queue queueA() {
return new Queue("fanout.A");
} @Bean
public Queue queueB() {
return new Queue("fanout.B");
} @Bean
public Queue queueC() {
return new Queue("fanout.C");
} @Bean
Binding bindingExchangeA() {
return BindingBuilder.bind(queueA()).to(fanoutExchange());
} @Bean
Binding bindingExchangeB() {
return BindingBuilder.bind(queueB()).to(fanoutExchange());
} @Bean
Binding bindingExchangeC() {
return BindingBuilder.bind(queueC()).to(fanoutExchange());
} //绑定键
public final static String MAN = "topic.MAN";
public final static String WOMAN = "topic.WOMAN"; @Bean
public Queue firstQueue() {
return new Queue(RabbitMQConfig.MAN);
} @Bean
public Queue secondQueue() {
return new Queue(RabbitMQConfig.WOMAN);
} //将firstQueue和topicExchange绑定,而且绑定的键值为topic.MAN
//这样只要是消息携带的路由键是topic.MAN,才会分发到该队列
@Bean
Binding bindingExchangeMessage() {
return BindingBuilder.bind(firstQueue()).to(exchange()).with(MAN);
} //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#
// 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列
@Bean
Binding bindingExchangeMessage2() {
return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");
} }
交换机的属性

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

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

2. 消息转换器

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

消息转换器接口源码:

/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package org.springframework.amqp.support.converter; import java.lang.reflect.Type; import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.lang.Nullable; /**
* Message converter interface.
*
* @author Mark Fisher
* @author Mark Pollack
* @author Gary Russell
*/
public interface MessageConverter { /**
* Convert a Java object to a Message. 将消息对象转换成java对象。
* @param object the object to convert
* @param messageProperties The message properties.
* @return the Message
* @throws MessageConversionException in case of conversion failure
*/
Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException; /**
* Convert a Java object to a Message. 将java对象和属性对象转换成Message对象。
* The default implementation calls {@link #toMessage(Object, MessageProperties)}.
* @param object the object to convert
* @param messageProperties The message properties.
* @param genericType the type to use to populate type headers.
* @return the Message
* @throws MessageConversionException in case of conversion failure
* @since 2.1
*/
default Message toMessage(Object object, MessageProperties messageProperties, @Nullable Type genericType)
throws MessageConversionException { return toMessage(object, messageProperties);
} /**
* Convert from a Message to a Java object.
* @param message the message to convert
* @return the converted Java object
* @throws MessageConversionException in case of conversion failure
*/
Object fromMessage(Message message) throws MessageConversionException; }

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

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter; public class TestMessageConverter implements MessageConverter { @Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
System.out.println("=======toMessage=========");
return new Message(object.toString().getBytes(),messageProperties);
} //消息类型转换器中fromMessage方法返回的类型就是消费端处理器接收的类型
@Override
public Object fromMessage(Message message) throws MessageConversionException {
System.out.println("=======fromMessage=========");
return new String(message.getBody());
}
}

简介Jackson2JsonMessageConverter消息转换器:

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

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

3.1 相关注解

3.2 消息的发送处理

package com.cli.springboot_rabbitmq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import javax.annotation.Resource;
import java.util.UUID; /**
* @Author xiongmin
* @Description
* @Date 2021/2/26 10:35
* @Version 1.0
**/
@Slf4j
@Service
public class RabbitMQService { // @Resource(name = "rabbitTemplateMessaging")
// private RabbitTemplate rabbitTemplate; /**
* 换成这个RabbitTemplate 之后,所有的消费者都要手动Ack消息确认被消费
*/
@Resource(name = "rabbitTemplateInit")
private RabbitTemplate rabbitTemplate;
@Autowired
private RabbitMQConfig amqpConfig; // 发送消息的后置处理器,MessagePostProcessor类的postProcessMessage方法得到的Message就是将参数Object内容转换成Message对象
// 没有指定具体的Exchange就会使用默认的Exchange
public void messageDeliver(String routineKey, Object o) {
rabbitTemplate.convertAndSend(routineKey, o, message -> {
System.out.println("-------处理前message-------------");
System.out.println(message);
// 设置message的一些头部信息
message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
return message;
});
} // 发送消息的后置处理器,MessagePostProcessor类的postProcessMessage方法得到的Message就是将参数Object内容转换成Message对象
public void messageDeliver(String exchange, String routineKey, Object o) {
rabbitTemplate.convertAndSend(exchange, routineKey, o, message -> {
System.out.println("-------处理前message-------------");
System.out.println(message);
// 设置message的一些头部信息
message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
System.out.println("-------处理后message-------------");
System.out.println(message);
return message;
},new CorrelationData(UUID.randomUUID().toString()));
} // 发送消息的后置处理器,MessagePostProcessor类的postProcessMessage方法得到的Message就是将参数Object内容转换成Message对象
public void messageDeliver(String exchange, String routineKey, User user) {
rabbitTemplate.convertAndSend(exchange, routineKey, user, message -> {
System.out.println("-------处理前message-------------");
System.out.println(message);
// 设置message的一些头部信息
/**
* 消息在消费时,先根据消息投中给的messageId找到对饮给的User, 在判断User的状态是否已经被消费过
* 感觉这也是一种防止消息重复消费的方式,即使同一个消息投递多次,也你能防止消息重复消费
*/
message.getMessageProperties().setMessageId(user.getName());
message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
System.out.println("-------处理后message-------------");
System.out.println(message);
return message;
},new CorrelationData(UUID.randomUUID().toString()));
} /**
* 防止同一时间段有好多个同步配置的消息发送,避免多个重复消息
* @param deviceId
*/
public void deliveConfigSyncMsgBeforeCheck(String deviceId) {
if (connect == null) {
connect = rabbitTemplate.getConnectionFactory().createConnection();
// connect = amqpConfig.connectionFactory().createConnection();
}
if (channel == null) {
try {
channel = connect.createChannel(false);
} catch (Exception e) {
connect = amqpConfig.connectionFactory().createConnection();
channel = connect.createChannel(false);
}
}
try {
long messageCount = channel.messageCount("f5-config");
// 如果f5-ltm-state为空那我就发一条消息,否者就不发,防止MQ消息堆积
if (messageCount == 0) {
messageDeliver("f5.config", deviceId);
} else {
boolean a = false;
int i;
for(i=0; i < messageCount; i++) {
channel.basicQos(1);
GetResponse response = channel.basicGet("f5-config", true);
if (response == null) {
continue;
}
AMQP.BasicProperties props = response.getProps();
byte[] body = response.getBody();
String message = new String(body);
channel.basicPublish("messaging","f5.config", props, message.getBytes("UTF-8"));
if (message.contains(deviceId)) {
a = true;
break;
}
}
if (!a && i >= messageCount) {
messageDeliver("f5.config", deviceId);
}
}
} catch (Exception e) {
logger.info("Retrieve Message fail " + e.getMessage());
}
} }

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

rabbitTemplate.convertAndSend(exchange, routineKey, o, message -> {
System.out.println("-------处理前message-------------");
System.out.println(message);
// 设置message的一些头部信息
message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
System.out.println("-------处理后message-------------");
System.out.println(message);
return message;
},new CorrelationData(UUID.randomUUID().toString())); rabbitTemplate的convertAndSend的相关源码:
@Override
public void convertAndSend(String exchange, String routingKey, final Object message,
final MessagePostProcessor messagePostProcessor,
@Nullable CorrelationData correlationData) throws AmqpException {
Message messageToSend = convertMessageIfNecessary(message);
messageToSend = messagePostProcessor.postProcessMessage(messageToSend, correlationData,
nullSafeExchange(exchange), nullSafeRoutingKey(routingKey));
send(exchange, routingKey, messageToSend, correlationData);
}

3.3 消息的消费处理

@RabbitListener和@RabbitHandler搭配使用

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

package com.cli.springboot_rabbitmq.consumer;

import com.cli.springboot_rabbitmq.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component; import java.util.Map; /**
* @Author xiongmin
* @Description
* @Date 2021/2/26 11:54
* @Version 1.0
**/
@Component
@Slf4j
@RabbitListener(queues = "topic.WOMAN") //监听的队列名称 topic.WOMAN
public class TopicConsumerListenerTwo { // @RabbitHandler
// public void receive(@Payload String message, @Headers Map<String, Object> headers) {
// try {
// log.info("TopicReceiver消费者收到消息 : ");
// log.info("message = " + message);
//
// log.info("TopicReceiver消费者收到消息头部信息 : ");
// if (null != headers && !headers.isEmpty()) {
// headers.forEach((key, value) -> {
// log.info(key + ": " + value + "\n");
// });
// } else {
// log.info("headers is empty");
// }
// } catch (Exception e) {
// log.error(e.getMessage(), e);
// }
// } /**
* 如果生产者生产的消息类型为String,那么就会执行该方法处理消息
* @param message
*/
@RabbitHandler
public void receive(@Payload String message) {
try {
log.info("TopicReceiver消费者收到消息 : ");
log.info("message = " + message);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
} // @RabbitHandler
// public void receive(@Payload User user, @Headers Map<String, Object> headers) {
// try {
// log.info("TopicReceiver消费者收到消息 : ");
// log.info("user = " + user);
//
// log.info("TopicReceiver消费者收到消息头部信息 : ");
// if (null != headers && !headers.isEmpty()) {
// headers.forEach((key, value) -> {
// log.info(key + ": " + value + "\n");
// });
// } else {
// log.info("headers is empty");
// }
// } catch (Exception e) {
// log.error(e.getMessage(), e);
// }
// } /**
* 如果生产者生产的消息类型为User,那么就会执行该方法处理消息
* @param user
*/
@RabbitHandler
public void receive(@Payload User user) {
try {
log.info("TopicReceiver消费者收到消息 : ");
log.info("user = " + user);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
} /**
* 如果生产者生产的消息类型为Map,那么就会执行该方法处理消息
* @param message
*/
@RabbitHandler
public void receive(@Payload Map message) {
log.info("TopicReceiver {topic.WOMAN}消费者收到消息 : ");
if (null != message && !message.isEmpty()) {
message.forEach((key, value) -> {
log.info(key + ": " + value + "\n");
});
} else {
log.info("message is empty");
}
} /**
* @DO 总结
* @RabbitListener标注在类上面表示当有收到消息的时候,就交给带有@RabbitHandler的方法处理,具体找哪个方法处理,需要更具MessageConverter转换后的java对象。
* 注意,如果需要消息的头部信息,由于头部信息是一个MAP数据结构,那么Payload的数据类型不能为MAP类型,否者会报错,且即使其他不是Map类型的Payload,要获取消息的头部信息也会报错
* 因为消费者消息Payload是MAP的类型的消息时,会查看那个方法中有MAP, 当有多个方法中具有MAP参数时,此时程序也不知道该使用哪个方法来处理这个消息,就会抛出异常
*/ }

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

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

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

4. 消息确认机制

yaml配置打开confirmCallback returnCallback

server:
port: 11000 spring:
application:
name: rabbitmq-test rabbitmq:
# 单机IP配置
# host: rabbitmq-host
# port: 5672 # 集群IP配置
addresses: rabbitmq01-host:5672,rabbitmq02-host:5672 # 用户名和密码,默认都是guest
username: xiongmin
password: xiongmin # 交换器名可以不设置默认 【"" --> /】 交换器
virtual-host: /rabbitmq_test
publisher-returns: true # 发送者开启 return 确认机制
publisher-confirm-type: correlated # 发送者开启 confirm 确认机制 等价于 spring.rabbitmq.publisher-returns=true
#连接超时时间
connection-timeout: 15000
# 使用return-callback时必须设置mandatory为true
template:
mandatory: true
default-exchange: default_exchange
# 消费端配置
listener:
simple:
retry:
enabled: true # 支持重试
#消费端
concurrency: 5
#最大消费端数
max-concurrency: 10
#自动签收auto 手动 manual
acknowledge-mode: manual # 设置消费端手动 ack
#限流(海量数据,同时只能过来一条)
prefetch: 1

分别实现confirmCallbackreturnCallback回调的类接口

ConfirmCallbackService.java

package com.cli.springboot_rabbitmq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component; /**
* @Author xiongmin
* @Description 监听消息是否发送交换机回调 只有投递失败的时候才会执行
* @Date 2021/2/27 11:16
* @Version 1.0
**/
@Component
@Slf4j
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback { @Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (!ack) {
log.error("消息发送异常!");
} else {
log.info("发送者爸爸已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, cause);
}
}
}

ReturnCallbackService.java

package com.cli.springboot_rabbitmq.config;

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

设置RabbitTemplate

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

消费者消费消息

package com.cli.springboot_rabbitmq.consumer;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component; import java.io.IOException;
import java.util.HashMap; /**
* @Author xiongmin
* @Description
* @Date 2021/2/26 10:51
* @Version 1.0
**/
@Component
@Slf4j
@RabbitListener(queues = "TestDirectQueue") //监听的队列名称 TestDirectQueue,监听多个队列需要用单号分隔
public class DirectConsumerListener { @RabbitHandler
public void receive(@Payload HashMap msg, Channel channel, Message message) throws IOException {
try {
log.info("DirectReceiver消费者收到消息 : ");
if (null != msg && !msg.isEmpty()) {
msg.forEach((key, value) -> {
log.info(key + ": " + value + "\n");
});
} else {
log.info("msg is empty");
} // 消费者手动ACK确认消息被消费, 生产者会执行ConfirmCallback回调
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
log.error(e.getMessage(),e);
try {
if (message.getMessageProperties().getRedelivered()) {
log.error("消息已重复处理失败,拒绝再次接收...");
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息
} else {
log.error("消息即将返回队列再次处理");
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
}
} catch (Exception ex) {
log.error("消息消费异常时处理失败",ex.getMessage(),ex);
} } }
}

消费者回执方法

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

1、basicAck

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

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

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

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

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

2、basicNack

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

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

deliveryTag:表示消息投递序号。

multiple:是否批量确认。

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

3、basicReject

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

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

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. Java学习day33

    线程池: 背景:经常创建和销毁.使用量特别大的资源,比如并发情况下的线程,对性能影响很大 思路:提前创建好多个线程.实现重复利用. 好处:提高响应速度,减少了创建新线程的时间:降低资源消耗,重复利用线 ...

  2. Vue快速入门(一)

    目录 Vue快速入门(一) 介绍 Vue.js 是什么 M-V-VM思想 安装 CDN引入 下载到本地 快速使用 双向数据绑定测试 模板语法 插值语法 指令 文本指令 v-html:让HTML渲染成页 ...

  3. SSM整合_年轻人的第一个增删改查_查找

    写在前面 SSM整合_年轻人的第一个增删改查_基础环境搭建 SSM整合_年轻人的第一个增删改查_查找 SSM整合_年轻人的第一个增删改查_新增 SSM整合_年轻人的第一个增删改查_修改 SSM整合_年 ...

  4. KotlinMall实战之注册部分MVP架构配置

    包目录如下: ①BaseView部分:基本的回调 interface BaseView { fun showLoading() fun hideLoading() fun onError()} ②Ba ...

  5. Dom基础(二):Dom性能优化

    一.尽量将DOM查询做缓存 1 let pElements = document.getElementById('div1') //将dom缓存 2 3 for(let i=0:i<pEleme ...

  6. Dapr 能否取代 Spring Cloud?

    很多人都是使用SpringBoot 和 Spring Cloud来开发微服务.Dapr 也是开发微服务的框架,它和Spring Cloud有什么区别呢,其实这不是一个区别的问题,它是不同的时代需要不同 ...

  7. Spring 源码(5)BeanFactory使用的准备及自定义属性值解析器

    BeanFactory 使用前的准备 上一篇文章 https://www.cnblogs.com/redwinter/p/16165878.html 介绍了自定义标签的使用,完成了AbstractAp ...

  8. Docker极简入门:使用Docker-Compose 运行网站浏览量统计Demo

    Docker-Compose 是一个可以对 Docker 容器集群的快速编排的工具,能够减轻您心智和手指的负担. 简单的来说 Docker-Compose 就是将你运行多个容器的命令编写到了一起,类似 ...

  9. Unity—TextMeshPro

    矢量文字,不会因为放大缩小而变的不清晰: 1.TextAsset Window/TextMeshPro/Font Assets Creator 创建TextAsset字体: SourceFont是.t ...

  10. 数据交换格式 JSON

    1. 什么是 JSON 概念 : JSON 的英文全称是 JavaScript ObjEct Notation, 即 "JavaScript 对象表示法" . 简单来讲 : JSO ...