SpringBoot整合RabbitMQ
生产者
- 创建生产者SpringBoot工程
- pom.xml引入amqp的starter
- 编写yml配置文件,配置基本信息
- 定义交换机,队列以及绑定关系的配置类
- 注入RabbitTemplate,调用方法,完成消息发送
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
# rabbitmq基本信息
spring:
rabbitmq:
host: 42.192.120.127
port: 5672
username: admin
password: admin
virtual-host: my_vhost
@Configuration
public class RabbitMqConfig {
private static final String EXCHANGE_NAME = "boot_topic_exchange";
private static final String QUEUE_NAME = "boot_queue";
/**
* 交换机 Exchange
*/
@Bean("bootExchange")
public Exchange bootExchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
}
/**
* 队列 Queue
*/
@Bean("bootQueue")
public Queue bootQueue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}
/**
* 队列和交换机绑定关系 Binding
*/
@Bean
public Binding bootBinding(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange")Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
}
}
@SpringBootTest
public class SpringBootProducerTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSend() {
rabbitTemplate.convertAndSend("boot_topic_exchange", "boot.hh", "test_message");
}
}
消费者
- 创建消费者工程
- 引入pom依赖
- 编写yml配置
- 定义监听类,使用@RabbitListener注解完成队列监听
@Component
public class RabbitMqListener {
@RabbitListener(queues = "boot_queue")
public void listenerQueue(Message message) {
System.out.println("消息为:" + message);
}
}
// 结果
消息为:(Body:'test_message' MessageProperties [headers={}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=boot_topic_exchange, receivedRoutingKey=boot.hh, deliveryTag=1, consumerTag=amq.ctag-lQeTsqwVpQPwT_mguRiyCQ, consumerQueue=boot_queue])
RabbitMQ高级特性
消息可靠性投递
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。
- confirm 确认模式
- return 退回模式
rabbitmq 整个消息投递的路径为:
producer—>rabbitmq broker—>exchange—>queue—>consumer
- 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
- 消息从 exchange 到 queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的可靠性投递
设置ConnectionFactory的publisher-confirms=“true” 开启 确认模式。
- 使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。
- 设置ConnectionFactory的publisher-returns=“true” 开启 退回模式。
- 使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到queue失败后,如果设置了rabbitTemplate.setMandatory(true)参数,则会将消息退回给producer。并执行回调函数returnedMessage。
配置文件
# rabbitmq基本信息
spring:
rabbitmq:
host: 42.192.120.127
port: 5672
username: admin
password: admin
virtual-host: my_vhost
# 开启confirm模式
publisher-confirm-type: correlated
# 开启return模式
publisher-returns: true
配置类
@Configuration
public class RabbitMqConfig {
private static final String EXCHANGE_NAME = "test_exchange";
private static final String QUEUE_NAME = "test_queue";
/**
* 交换机 Exchange
*/
@Bean("exchange")
public Exchange exchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
}
/**
* 队列 Queue
*/
@Bean("queue")
public Queue queue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}
/**
* 队列和交换机绑定关系 Binding
*/
@Bean
public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
}
测试方法
@Test
public void testConfirm() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 相关配置信息
* @param ack 交换机是否成功收到消息
* @param cause 错误信息
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("消息发送成功:" + cause);
} else {
System.out.println("消息发送失败:" + cause);
}
}
});
// 从producer到exchange失败的话,会返回一个confirmCallback
rabbitTemplate.convertAndSend("exchange111", "", "hh");
}
// 结果
消息发送失败:clean channel shutdown; protocol method: #method<channel.close>(reply-code=200, reply-text=OK, class-id=0, method-id=0)
@Test
public void testReturn() {
// 设置交换机处理失败消息的模式
// 如果消息没有路由到queue,设置为false丢弃消息,设置为true返回给消息发送方
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.out.println("message: " + returnedMessage.getMessage());
System.out.println("exchange: " + returnedMessage.getExchange());
System.out.println("replyCode: " + returnedMessage.getReplyCode());
System.out.println("replyText: " + returnedMessage.getReplyText());
System.out.println("routingKey: " + returnedMessage.getRoutingKey());
}
});
// exchange -> queue失败的话,会返回一个returnsCallback
rabbitTemplate.convertAndSend("test_exchange", "testReturn", "haha");
}
// 结果
message: (Body:'haha' MessageProperties [headers={}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])
exchange: test_exchange
replyCode: 312
replyText: NO_ROUTE
routingKey: testReturn
Consumer ACK
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有三种确认方式:
- 自动确认:acknowledge=“none”
- 手动确认:acknowledge=“manual”
- 根据异常情况确认:acknowledge=“auto”
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
消费者端yml配置
spring:
rabbitmq:
host: 42.192.120.127
port: 5672
username: admin
password: admin
virtual-host: my_vhost
listener:
simple:
acknowledge-mode: manual # 手动
retry:
enabled: true # 是否允许重试
消费者代码
@Component
public class RabbitMqListener {
private static Logger logger = LoggerFactory.getLogger(RabbitMqListener.class);
@RabbitListener(queues = "test_queue")
public void listenerQueue(Message message, Channel channel) throws IOException {
long tag = message.getMessageProperties().getDeliveryTag();
try {
// 消息
logger.info("**** {}", new String(message.getBody()));
// 手动接收
/*
basicAck(long deliveryTag, boolean multiple)
deliveryTag:取出来当前消息在队列中的的索引;
multiple:为true的话就是批量确认,如果当前deliveryTag为5,那么就会确认
deliveryTag为5及其以下的消息;一般设置为false
*/
channel.basicAck(tag, false);
} catch (Exception e) {
/*
basicNack(long deliveryTag, boolean multiple, boolean requeue)
requeue:true为将消息重返当前消息队列,还可以重新发送给消费者;
false:将消息丢弃
*/
channel.basicNack(tag, false, true);
}
}
}
生产者代码
@Configuration
public class RabbitMqConfig {
private static final String EXCHANGE_NAME = "test_exchange";
private static final String QUEUE_NAME = "test_queue";
/**
* 交换机 Exchange
*/
@Bean("exchange")
public Exchange exchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
}
/**
* 队列 Queue
*/
@Bean("queue")
public Queue queue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}
/**
* 队列和交换机绑定关系 Binding
*/
@Bean
public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("#.test").noargs();
}
}
@Test
public void testAck() {
rabbitTemplate.convertAndSend("test_exchange", "hello.test", "test_ack");
}
消息已被接收
如果消费者在处理业务逻辑的时候,出现了异常,那么就不会接收该消息
@Component
public class RabbitMqListener {
private static Logger logger = LoggerFactory.getLogger(RabbitMqListener.class);
@RabbitListener(queues = "test_queue")
public void listenerQueue(Message message, Channel channel) throws IOException {
long tag = message.getMessageProperties().getDeliveryTag();
try {
// 消息
logger.info("**** {}", new String(message.getBody()));
// 手动接收
/*
basicAck(long deliveryTag, boolean multiple)
deliveryTag:取出来当前消息在队列中的的索引;
multiple:为true的话就是批量确认,如果当前deliveryTag为5,那么就会确认
deliveryTag为5及其以下的消息;一般设置为false
*/
int i = 10/0 ;
channel.basicAck(tag, false);
} catch (Exception e) {
/*
basicNack(long deliveryTag, boolean multiple, boolean requeue)
requeue:true为将消息重返当前消息队列,还可以重新发送给消费者;
false:将消息丢弃
*/
channel.basicNack(tag, false, true);
}
}
}
生产者只发送了一条消息,没有被接收,所以会出现下列情况
消费端限流
MQ有一个特性就是削峰限流,限流机制如下:
- 确认consumer的接收机制为:手动接收
- 配置listener.perfetch:表示消费端每次从mq拉取n条消息来消费,直到手动确认消费完毕后,才会继续拉取下一条消息
消费者application.yml配置
spring:
rabbitmq:
host: 42.192.120.127
port: 5672
username: admin
password: admin
virtual-host: my_vhost
listener:
simple:
acknowledge-mode: manual # 手动
concurrency: 1 # 指定的最小的消费者数量
max-concurrency: 10 # 指定的最大的消费者数量
prefetch: 2 # 每次的消费数量
retry:
enabled: true # 是否允许重试
消费端JAVA代码
@Component
public class QosListener {
private static final Logger logger = LoggerFactory.getLogger(RabbitMqListener.class);
@RabbitListener(queues = "test_queue")
public void listener(Message message, Channel channel) throws InterruptedException {
long tag = message.getMessageProperties().getDeliveryTag();
// 观察效果
Thread.sleep(3000);
try {
String msg = new String(message.getBody());
// 接收转换消息
logger.info("收到消息:{},编号:{}", msg, tag);
channel.basicAck(tag, true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
观察控制台Unack的数量,代表未接收
TTL
TTL 全称 Time To Live(存活时间/过期时间)。
当消息到达存活时间后,还没有被消费,会被自动清除。
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。
TTL有两种过期设置
- 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。
- 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断这一消息是否过期。
- 如果两者都进行了设置,以时间短的为准。
队列统一过期
消费者端配置
@Configuration
public class RabbitMqConfig {
private static final String EXCHANGE_NAME = "test_exchange";
private static final String QUEUE_NAME = "test_queue";
private static final int TTL_EXPIRATION = 10000;
/**
* 交换机 Exchange
*/
@Bean("exchange")
public Exchange exchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
}
/**
* 队列 Queue
*/
@Bean("queue")
public Queue queue() {
return QueueBuilder.durable(QUEUE_NAME).withArgument("x-message-ttl", TTL_EXPIRATION).build();
}
/**
* 队列和交换机绑定关系 Binding
*/
@Bean
public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("#.test").noargs();
}
}
队列中有一个TTL的标识
消息单独过期
@Test
public void testExpiration() {
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
};
rabbitTemplate.convertAndSend("test_exchange", "hello.expire", "测试过期", messagePostProcessor);
}
死信队列
死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机)(因为交换机是RabbitMQ中的东西,其它的消息队列中直接叫死信队列),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信的三种情况:
- 队列消息长度到达限制;
- 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
- 原队列存在消息过期设置,消息到达超时时间未被消费;
队列如何绑定死信交换机?
给队列设置参数: x-dead-letter-exchange
和 x-dead-letter-routing-key
消息长时间未被消费
@Configuration
public class RabbitMqConfig {
private static final String EXCHANGE_NAME = "test_exchange";
private static final String QUEUE_NAME = "test_queue";
private static final String ROUTING_KEY = "test.dlx.#";
// 绑定的死信交换机的路由key,与下方的要符合
private static final String X_DEAD_LETTER_ROUTING_KEY = "dlx.haha";
// 死信队列参数
private static final String DLX_EXCHANGE = "dlx_exchange";
private static final String DLX_QUEUE = "dlx_queue";
private static final String DLX_ROUTING_KEY = "dlx.#";
private static final int TTL_EXPIRATION = 10000;
/**
* 交换机 Exchange
*/
@Bean("exchange")
public Exchange exchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
}
/**
* 队列 Queue
*/
@Bean("queue")
public Queue queue() {
Map<String, Object> map = new HashMap<>(8);
map.put("x-message-ttl", TTL_EXPIRATION);
map.put("x-dead-letter-exchange", DLX_EXCHANGE);
map.put("x-dead-letter-routing-key", X_DEAD_LETTER_ROUTING_KEY);
return QueueBuilder.durable(QUEUE_NAME).withArguments(map).build();
}
/**
* 队列和交换机绑定关系 Binding
*/
@Bean
public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();
}
@Bean(name = "dlxExchange")
public Exchange dlxExchange() {
return ExchangeBuilder.topicExchange(DLX_EXCHANGE).build();
}
@Bean(name = "dlxQueue")
public Queue dlxQueue() {
return QueueBuilder.durable(DLX_QUEUE).build();
}
@Bean
public Binding dlxBinding(@Qualifier("dlxQueue") Queue queue, @Qualifier("dlxExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(DLX_ROUTING_KEY).noargs();
}
}
生产者发送消息
@Test
public void testDLX() {
rabbitTemplate.convertAndSend("test_exchange", "test.dlx.hello", "测试死信队列");
}
可以看见,刚开始是在test_queue队列中,但是10s之后,过期了,就转而到死信队列中了
消息队列长度超出限制
RabbitMq配置
@Configuration
public class RabbitMqConfig {
private static final String EXCHANGE_NAME = "test_exchange";
private static final String QUEUE_NAME = "test_queue";
private static final String ROUTING_KEY = "test.dlx.#";
// 绑定的死信交换机的路由key,与下方的要符合
private static final String X_DEAD_LETTER_ROUTING_KEY = "dlx.haha";
// 死信队列参数
private static final String DLX_EXCHANGE = "dlx_exchange";
private static final String DLX_QUEUE = "dlx_queue";
private static final String DLX_ROUTING_KEY = "dlx.#";
private static final int TTL_EXPIRATION = 10000;
private static final int MAX_LENGTH = 10;
/**
* 交换机 Exchange
*/
@Bean("exchange")
public Exchange exchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
}
/**
* 队列 Queue
*/
@Bean("queue")
public Queue queue() {
Map<String, Object> map = new HashMap<>(8);
// map.put("x-message-ttl", TTL_EXPIRATION);
// 设置队列的最大长度为10
map.put("x-max-length", MAX_LENGTH);
map.put("x-dead-letter-exchange", DLX_EXCHANGE);
map.put("x-dead-letter-routing-key", X_DEAD_LETTER_ROUTING_KEY);
return QueueBuilder.durable(QUEUE_NAME).withArguments(map).build();
}
/**
* 队列和交换机绑定关系 Binding
*/
@Bean
public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();
}
@Bean(name = "dlxExchange")
public Exchange dlxExchange() {
return ExchangeBuilder.topicExchange(DLX_EXCHANGE).build();
}
@Bean(name = "dlxQueue")
public Queue dlxQueue() {
return QueueBuilder.durable(DLX_QUEUE).build();
}
@Bean
public Binding dlxBinding(@Qualifier("dlxQueue") Queue queue, @Qualifier("dlxExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(DLX_ROUTING_KEY).noargs();
}
}
生产者发送消息
@Test
void test() {
for (int i = 0; i < 50; i++) {
rabbitTemplate.convertAndSend("test_exchange", "test.dlx.hello", "测试死信队列2");
}
}
有40条信息进入了死信队列
消费者拒绝接收消息
生产者发送十条消息
消费者开启手动确认模式
spring:
rabbitmq:
host: 42.192.120.127
port: 5672
username: admin
password: admin
virtual-host: my_vhost
publisher-confirm-type: correlated # 开启确认模式
publisher-returns: true # 开启返回模式
listener:
simple:
acknowledge-mode: manual # 采用手动应答
concurrency: 1 # 指定最小的消费者数量
max-concurrency: 1 # 指定最大的消费者数量
retry:
enabled: false # 重试
# max-attempts: 3 # 重试次数
拒绝消息
@Component
@Slf4j
public class RabbitListener {
@org.springframework.amqp.rabbit.annotation.RabbitListener(queues = "test_queue")
public void listener(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
log.info("收到了一条消息...");
// 拒绝消息,且不重回队列
// basicNack(long deliveryTag, boolean multiple, boolean requeue)
// 设置requeue为false
channel.basicNack(deliveryTag, true, false);
} catch (IOException e) {
log.info("{}", e.getMessage());
}
}
}
消息进入死信队列
延迟队列(Todo)
通过TTL和死信队列来实现延迟队列
- TTL设置任务过期时间
- 任务过期后进入延迟队列