<!--引入SpringBoot-->
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<dependencies>
<!--web基础依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--测试依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--spirngboot集成rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
@SpringBootApplication
public class MqApp {
public static void main(String[] args) {
SpringApplication.run(MqApp.class);
}
}
server:
port: 8001
spring:
application:
name: myDemo
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
listener:
simple:
acknowledge-mode: manual #手动签收
prefetch: 1
publisher-confirms: true #消息发送到交换机后的回调
publisher-returns: true #消息由交换机发到队列失败后的回调
template:
mandatory: true # 必须设置成true 消息路由失败通知监听者,而不是将消息丢弃
package cn.itsource.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitmqConfig {
public static final String EXCHANGE_TOPIC_0507 = "exchange_topic_0723";//交换机名称
public static final String QUEUE_NAME_SMS = "queue_sms";//短信队列名称
public static final String QUEUE_NAME_EMAIL = "queue_email";//邮件队列名称
public static final String ROUTINGKEY_QUEUE_SMS = "#.sms";//短信队列的routingkey
public static final String ROUTINGKEY_QUEUE_EMAIL = "#.email";//邮件队列的routingkey
//注册交换机
@Bean
public Exchange topicExchange(){
//durable:设置持久化
return ExchangeBuilder.topicExchange(EXCHANGE_TOPIC_0507).durable(true).build();
}
//注册短信队列-用来发短信
@Bean
public Queue smsQueue(){
return new Queue(QUEUE_NAME_SMS, true, false, false);
}
//注册邮件队列-用来发邮件
@Bean
public Queue emailQueue(){
return new Queue(QUEUE_NAME_EMAIL, true, false, false);
}
//短信队列绑定到交换机
@Bean
public Binding bindSmsQueue(){
return BindingBuilder.bind(smsQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_SMS).noargs();
}
//邮件队列绑定到交换机
@Bean
public Binding bindEmailQueue(){
return BindingBuilder.bind(emailQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_EMAIL).noargs();
}
}
知识点:对象与json互转
对象转json串:JSONObject.toJSONString(对象名)
json串转对象:
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
/**
* @description: 做序列化
*/
@Configuration
public class RabbitMQConverterConfig implements RabbitListenerConfigurer{
//以下配置RabbitMQ消息服务
@Autowired
public ConnectionFactory connectionFactory;
@Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
// 这里的转换器设置实现了 通过 @Payload 注解 自动反序列化message body
factory.setMessageConverter(new MappingJackson2MessageConverter());
return factory;
}
@Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
}
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setConcurrentConsumers(3);
factory.setMaxConcurrentConsumers(10);
//设置手动签收
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return factory;
}
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// 这里的转换器设置实现了发送消息时自动序列化消息对象为message body
template.setMessageConverter(jsonMessageConverter());
template.setMandatory(true);
return template;
}
}
//RabbitMQ常量类
public class RabbitMQConstants{
public static final String QUEUE_NAME_SMS = "queue_sms";//短信队列名称
public static final String QUEUE_NAME_EMAIL = "queue_email";//邮件队列名称
public static final String QUEUE_NAME_STATION = "queue_station";//站内信队列名称
}
package cn.itsource.sender;
import cn.itsource.MqApp;
import cn.itsource.config.RabbitmqConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @description: 生产者
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MqApp.class)
public class Sender {
@Autowired
private RabbitTemplate rabbitTemplate;
//发送消息
@Test
public void send(){
//发短信消息
rabbitTemplate.convertAndSend(RabbitMQConstants.QUEUE_NAME_SMS, "course.sms", "号外号外,最新的Java架构师课程上线了,9.9元即可试听哦");
//发邮件消息
rabbitTemplate.convertAndSend(RabbitMQConstants.QUEUE_NAME_EMAIL, "course.email", "号外号外,最新的Java架构师课程上线了,9.9元即可试听哦");
}
}
/**
* @description: 消费者
*/
@Component
public class Consumer {
/**
* 监听某个队列
* queues:监听队列的名字,可以写多个
*/
@RabbitListener(queues = {RabbitmqConfig.QUEUE_NAME_SMS})
public void handlerSMS(String content, Message message, Channel channel) throws IOException {
System.out.println("拿到短信消息:" + content);
//手动签收消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
/**
* 监听某个队列
* queues:监听队列的名字,可以写多个
*/
@RabbitListener(queues = {RabbitmqConfig.QUEUE_NAME_EMAIL})
public void handlerEmail(String content, Message message, Channel channel) throws IOException {
System.out.println("拿到邮件消息:" + content);
//手动签收消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
在生产者端,消息投递到交换机失败了 进行回调处理(保证消息不丢失)
这样的回调处理只需要写一次就可以了,所以我们就写到RabbitMQConverterConfig这个配置类里面,rabbitTemplate方法改动如下
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
/**
* @description: 做序列化
*/
@Configuration
public class RabbitMQConverterConfig implements RabbitListenerConfigurer{
//以下配置RabbitMQ消息服务
@Autowired
public ConnectionFactory connectionFactory;
public int retry_count = 3;//最大重试次数
@Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
// 这里的转换器设置实现了 通过 @Payload 注解 自动反序列化message body
factory.setMessageConverter(new MappingJackson2MessageConverter());
return factory;
}
@Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
}
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setConcurrentConsumers(3);
factory.setMaxConcurrentConsumers(10);
//设置手动签收
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return factory;
}
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// 这里的转换器设置实现了发送消息时自动序列化消息对象为message body
template.setMessageConverter(jsonMessageConverter());
template.setMandatory(true);
//设置消息发送到交换机的回调
/**
* 不管消息发送到交换机是否成功,该方法都会被回调
* @param correlationData:相关数据,发送的时候可以指定一个correlationData,回调的时候传回来给我
* @param ack:true表示消息发到交换机成功,false表示消息发送交换机失败
* @param cause:失败原因
*/
template.setConfirmCallback((correlationData, ack, cause) ->{
if(ack){
System.out.println("消息发送到交换机成功...");
}else{
System.out.println("消息发送到交换机失败...");
}
});
//设置消息发送到队列的回调
/**
* 消息发到队列失败时才会回调该方法
* @param message:封装消息内容的对象
* @param replyCode:错误码
* @param replyText:错误对象
* @param exchange:交换机
* @param routingKey:路由键
*/
template.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
if(retry_count > 0){
System.out.println("再次发送消息...");
template.convertAndSend(exchange, routingKey, message);
retry_count--;
}
});
return template;
}
}
死信,在官网中对应的单词为“Dead Letter”,可以看出翻译确实非常的简单粗暴。那么死信是个什么东西呢?
“死信”是RabbitMQ中的一种消息机制,当你在消费消息时,如果队列里的消息出现以下情况:
channel.basicNack
或 channel.basicReject
,并且此时requeue
属性被设置为false
。那么该消息将成为“死信”。
“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。
代码思路(模拟消息过期场景):
1. 用户下单后,发送消息(带有TTL过期时间)到一个普通队列,然后这个普通队列设置了死信交换机和路由
2. 到达过期时间后,消息成为死信,由死信交换机转发到死信队列
3. 消费者监听死信队列,处理过期订单
package cn.zsh.config.rabbit;
import cn.zsh.constants.BaseConstants;
import org.springframework.amqp.core.*;
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 {
public static final String EXCHANGE_TOPIC_0907 = "exchange_topic_0907";//交换机名称
// public static final String QUEUE_NAME_SMS = "queue_sms";//短信队列名称
// public static final String QUEUE_NAME_EMAIL = "queue_email";//邮件队列名称
// public static final String QUEUE_NAME_STATION = "queue_station";//站内信队列名称
public static final String ROUTINGKEY_QUEUE_SMS = "#.sms";//短信队列的routingkey
public static final String ROUTINGKEY_QUEUE_EMAIL = "#.email";//邮件队列的routingkey
public static final String ROUTINGKEY_QUEUE_STATION = "#.station";//站内信队列的routingkey
public static final String EXCHANGE_DEAD = "exchange_dead";//死信交换机名称
public static final String ROUTINGKEY_QUEUE_DEAD = "routing_dead_key";//死信的路由key
public static final String QUEUE_NAME_ORDER = "queue_dead";//死信队列名称
//注册交换机
@Bean
public Exchange topicExchange(){
//durable:设置持久化
// return new TopicExchange(EXCHANGE_TOPIC_0907,true,false);
return ExchangeBuilder.topicExchange(EXCHANGE_TOPIC_0907).durable(true).build();
}
//注册短信队列-用来发短信
@Bean
public Queue smsQueue(){
return new Queue(BaseConstants.RabbitMQConstants.QUEUE_NAME_SMS, true, false, false);
}
//注册邮件队列-用来发邮件
@Bean
public Queue emailQueue(){
return new Queue(BaseConstants.RabbitMQConstants.QUEUE_NAME_EMAIL, true, false, false);
}
//注册站内信队列-用来发邮件
@Bean
public Queue stationQueue(){
Map<String, Object> arguments = new HashMap<>(2);
// 设置死信参数
// 绑定死信交换机
arguments.put("x-dead-letter-exchange", EXCHANGE_DEAD);
// 绑定死信的路由key
arguments.put("x-dead-letter-routing-key", ROUTINGKEY_QUEUE_DEAD);
return new Queue(
BaseConstants.RabbitMQConstants.QUEUE_NAME_STATION,
true,
false,
false,
arguments);
}
//短信队列绑定到交换机
@Bean
public Binding bindSmsQueue(){
return BindingBuilder.bind(smsQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_SMS).noargs();
}
//邮件队列绑定到交换机
@Bean
public Binding bindEmailQueue(){
return BindingBuilder.bind(emailQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_EMAIL).noargs();
}
//站内信队列绑定到交换机
@Bean
public Binding bindStationQueue(){
return BindingBuilder.bind(stationQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_STATION).noargs();
}
//注册死信交换机
@Bean
public Exchange deadExchange(){
//durable:设置持久化
// return new TopicExchange(EXCHANGE_TOPIC_0907,true,false);
return ExchangeBuilder.topicExchange(EXCHANGE_DEAD).durable(true).build();
}
//注册真实存储死信的队列,当死信队列中消息过期后,转发到此队列,真实存储死信队列需要绑定死信交换机和路由
@Bean
public Queue deadQueue(){
return new Queue(QUEUE_NAME_ORDER, true, false, false);
}
//死信队列绑定到死信交换机
@Bean
public Binding bindDeadQueue(){
return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(ROUTINGKEY_QUEUE_DEAD).noargs();
}
}
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPIC_0907, "course.email",JSONObject.toJSONString(emailDto)
,new MessagePostProcessor() {
//在消息发送之前应用于消息的处理器
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties messageProperties = message.getMessageProperties();
//设置过期时间TTL 5秒
messageProperties.setExpiration(String.valueOf(5000));
//设置持久化
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
}
});
@Component
@Slf4j
public class DeadConsumer {
/**
* 监听死信队列
*/
@RabbitListener(queues = {BaseConstants.RabbitMQConstants.QUEUE_NAME_DEAD})
public void handleEmail(String content, Message message, Channel channel){
System.out.println("监听死信队列,收到的消息: "+ content);
//.......
//由于配置类做了手动ack 所以要手动签收消息
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
System.out.println("发送短信,签收失败:" + e);
}
}
}