Springboot-Rabbitmq 高级部分
文章目录
消息可靠性投递
生产者 --> 交换机 : 通过confirmCallback
交换机 --> 队列 : 通过returnCallback
注意: 可靠投递会使rabbitmq吞吐量严重下降,非重要消息,不建议开启
confirmCallback 生产者-> 交换机
生产者消息发送到 Broker 后,返回给生产者一个ACK来确定消息收到。
实现confirmCallBack接口版本
spring.rabbitmq.publisher-returns: true2.4 以后的配置
spring.rabbitmq.publisher-confirm-type: correlated
代码实现
配置类
@Configuration
public class RabbitMQConfig {
// 消息可靠性
public final static String EXCHANGE_TO_EXCHANGE = "reliable_exchange";
public final static String QUEUE_RELIABLE = "reliable_queue";
@Bean(EXCHANGE_TO_EXCHANGE)
public Exchange createExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_TO_EXCHANGE).durable(true).build();
}
@Bean(QUEUE_RELIABLE)
public Queue createQueue(){
return QueueBuilder.durable(QUEUE_RELIABLE).build();
}
@Bean
public Binding binding1(@Qualifier(QUEUE_RELIABLE) Queue queue,
@Qualifier(EXCHANGE_TO_EXCHANGE)Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("reliable.*").noargs();
}
}
测试代码
@Autowired
private RabbitTemplate template;
// 消息可靠性投递 生产者到交换机
@Test
public void test1() {
template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 配置
* @param b 交换机是否收到消息,true 成功
* @param s 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("ConfirmCallback==>");
System.out.println("correlationDate="+correlationData);
System.out.println("ack=" + b);
System.out.println("causer="+ s);
if(b) System.out.println("成功收到消息");
else System.out.println("消息接收失败");
}
});
template.convertAndSend(RabbitMQConfig.EXCHANGE_TO_EXCHANGE,"reliable.do","测试消息");
}
结果
# 成功
ConfirmCallback==>
correlationDate=null
ack=true
causer=null
成功收到消息
#失败 这里修改了交换机名
ConfirmCallback==>
correlationDate=null
ack=false
causer=channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'reliable_exchangess' in vhost '/study', class-id=60, method-id=40)
消息接收失败
returnCallback 交换机-> 队列
- 交换机到队列不成功,丢弃消息(默认)
- 交换机到队列不成功,返回给消息生产者 returnCallback
#开启 returnCallback
spring.rabbitmq.publisher-returns=true
# 设置失败消息返回 生产者
spring.rabbitmq.template.mandatory=tru
测试代码
// 消息可靠性 交换机 到 队列
@Test
void test2() {
template.setReturnsCallback(new RabbitTemplate.ReturnsCallback(){
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
int replyCode = returnedMessage.getReplyCode();
System.out.println("code"+replyCode);
System.out.println("消息==="+ returnedMessage.toString());
}
});
//成功
//template.convertAndSend(RabbitMQConfig.EXCHANGE_TO_EXCHANGE,"reliable.do","测试交换机到队列");
//失败 queue 不存在
template.convertAndSend(RabbitMQConfig.EXCHANGE_TO_EXCHANGE,"x.reliable.do","测试交换机到队列");
}
结果
成功匹配没有任何返回
# 失败 发送匹配不到key
code312
消息===ReturnedMessage [message=(Body:'测试交换机到队列' MessageProperties [headers={}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0]), replyCode=312, replyText=NO_ROUTE, exchange=reliable_exchange, routingKey=x.reliable.do]
ACK mq -> 消费者
消费者取走消息,处理完成后,队列删除消息
消费者未处理完消息,消息会重新返回队列
消费者返回ack 后mq 才会确认消息被消费
ACK默认是开启的,消息被取走会进入 Unacked 状态
# 手动开启
spring.rabbitmq.listener.simple.acknowledge-mode.manual
package com.example.demo.lestener;
import com.example.demo.config.RabbitMQConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author LFweixiao
* @create 2022-04-30 11:19 上午
*
* 开启 ACK 手动提交 需要去配置文件 开启配置
*/
@Component
public class RabbitMqListener {
@RabbitListener(queues = {RabbitMQConfig.QUEUE_RELIABLE})
public void messageHandler(String body, Message message, Channel channel) throws IOException {
// deliveryTag 投递序号,每次消费消息或者重新投递消息,deliveryTag 都会增加
long msgTag = message.getMessageProperties().getDeliveryTag();
System.out.println("消息目标=="+ msgTag);
System.out.println("message=="+ message.toString());
System.out.println("body=="+ body);
//业务逻辑
// broker 消息拒绝确认 basiNack 可以一次拒绝 0到多个消息拒收 可以设置 requeue
//channel.basicNack(msgTag,false,true);
// broker 确认消息
channel.basicAck(msgTag,false);
//一次拒收一条消息
//channel.basicReject(msgTag,true);
}
}
死信队列+TTL
TTL = time to live
消息存活时间rabbitmq 两种ttl设置
- 单独消息进行配置ttl
- 队列设置ttl(推荐)
DLX = Dead Letter Excahnge
死信交换机
- 消费者拒收消息basic.reject/basic.nack
- 消息在队列中超时未被消费
- 队列容量满,被淘汰出来的消息(最先进入队列的消息)
消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
控制台创建 交换机——死信交换机和普通交换机一样
控制台创建 死信队列 ——死信队列和普通队一样
记得绑定交换机
创建普通队列 结合 死信交换机
x-message-ttl
对整个队列设置统一的超时时间 毫秒 单位队列超时时间和 消息超时时间都设置是,时间短的会触发
代码实现创建 ‼️
package com.example.demo.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
/**
* @author LFweixiao
* @create 2022-05-01 10:55 上午
* 配置 死信 队列,交换机
*/
//@Configuration
public class DieMessageConfig {
//死信队列
public static final String DEAD_QUEUE = "dead_queue";
//死信交换机
public static final String DEAD_EXCHANGE = "dead_exchange";
//死信队列的 路由key
public static final String DEAD_ROUTING_KEY = "dead_routing_key";
@Bean(DieMessageConfig.DEAD_EXCHANGE)
public Exchange createExchange() {
return new TopicExchange(DEAD_EXCHANGE,true,false);
}
@Bean(DieMessageConfig.DEAD_QUEUE)
public Queue createQueue() {
return QueueBuilder.durable(DEAD_QUEUE).build();
}
@Bean
public Binding deadBinding() {
return new Binding(DEAD_QUEUE, Binding.DestinationType.QUEUE,
DEAD_EXCHANGE,DEAD_ROUTING_KEY,null);
}
//设置 普通队列,交换机, 完成普通队列,和死信交换机的绑定
//普通队列
public static final String NEW_TEXT_TTL_QUEUE = "new_text_ttl_queue";
// topic 交换机
public static final String NEW_TEXT_TTL_EXCHANGE = "new_text_ttl_exchange";
// 路由key
public static final String NEW_TEXT_TTL_ROUTING_KEY = "new_text_ttl_routing_key";
@Bean(DieMessageConfig.NEW_TEXT_TTL_EXCHANGE)
public Exchange createNewTextTTL() {
return new TopicExchange(NEW_TEXT_TTL_EXCHANGE,true,false);
}
//普通队列绑定 私信交换机
@Bean(DieMessageConfig.NEW_TEXT_TTL_QUEUE)
public Queue createQueueNew() {
HashMap<String, Object> args = new HashMap<>();
//设置创建参数
//队列消息过期时间
args.put("x-message-ttl",10000);
//消息过期后进入 私信交换机的 routing key
args.put("x-dead-letter-routing-key",NEW_TEXT_TTL_ROUTING_KEY);
// 绑定死信交换机
args.put("x-dead-letter-exchange",NEW_TEXT_TTL_EXCHANGE);
return QueueBuilder.durable(NEW_TEXT_TTL_QUEUE).withArguments(args).build();
}
@Bean
public Binding createBindingNew(){
return new Binding(NEW_TEXT_TTL_QUEUE,Binding.DestinationType.QUEUE,
NEW_TEXT_TTL_EXCHANGE,NEW_TEXT_TTL_ROUTING_KEY,null);
}
}