今日内容
1. RabbitMQ-交换机确认机制
RabbitMQ-交换机确认机制
什么是消息队列的确认机制
-理解:就是消息从生产者到消息的队列到消费者,需要确认收到的机制
-默认是不开启的,默认不抛出异常就算成功收到,消息队列自动去除该消息
-实际:如果我们捕获异常,尽管是报错了,但是消息队列默认没有错误,直接自动去除消息
所以
消息队列的手动确认机制是很有必要的
-每次有消息投递到交换机,就会触发提前配置好的回调接口,来告诉我们,消息是否接收成功,如果不成功,是因为什么不成功
在基本的路由队列中加入消息确认机制
消息队列的配置类要实现Rabbitmq的确认回调的确认接口
RabbitTemplate.ConfigCallback
重写里面唯一的方法
public void confirm(CorrelationData correlationData, boolean b, String s)
correlationData - 消息队列唯一标识
boolean b - 是否成功投递消息
String s - 投递失败的原因
将confirm放置到RabbitTemplate里面去
-通过@PostConstruct构造器注解,类一加载,先执行构造器里面的内容
2. RabbitMQ-队列消息确认机制
RabbitMQ-队列消息确认机制
解决了交换机出错的确认,交换机到队列也有消息确认
3. RabbitMQ-消息重新投递
利用定时任务类
-redis存储消息和消息队列配合
-如果当前信息的状态是失败
创建定时任务,从redis中获取所有信息,然后判断状态为失败的信息
重新将状态改为发送,然后通过消息队列重新发送
定时任务,可以重复发送,直到消息成功发送
package com.szr.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.szr.po.MessageVo;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class RabbitConfirmConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
@Autowired
RabbitTemplate rabbitTemplate ;
//注入redis
@Autowired
RedisTemplate redisTemplate ;
/**
* 构造器注解,类一加载,将confirm装配到rabbitTemplate中,完成消息确认机制的开启
*/
@PostConstruct
public void rabbitTemplate(){
//配置交换机确认
rabbitTemplate.setConfirmCallback(this::confirm);
//配置队列确认
rabbitTemplate.setReturnCallback(this::returnedMessage);
}
/**
* 交换机消息确认机制
* @param correlationData 消息业务的唯一标识
* @param b 是否成功投递消息
* @param s 投递失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
//加入判断
if (b!=true) {
//调用方法
updateMessage(correlationData.getId().toString(),s);
}
}
/**
* 队列消息确认机制
* @param message 消息
* @param i 状态码
* @param s 错误原因
* @param s1 交换机名称
* @param s2 路由键名称
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
//调用复用方法-从头部获取信息
updateMessage(message.getMessageProperties().getHeader("spring_returned_message_correlation").toString(),s);
}
/**
* 复用方法,完成对队列消息确认的业务操作
* @param messageID 唯一标识id
* @param cause 失败原因
*/
public void updateMessage(String messageID,String cause){
//通过id获取redis中的数据
Object obj = redisTemplate.opsForHash().get("message", messageID);
//通过json来转换
MessageVo message = JSONObject.parseObject(JSON.toJSONString(obj), MessageVo.class);
//修改对应的信息
message.setStatus("FAIL");//将发送状态改成失败状态
message.setCause(cause);//存入失败原因
//再将修改后信息存回redis
redisTemplate.boundHashOps("message").put(messageID,message);
}
}
4. RabbitMQ-监听队列手动确认机制
RabbitMQ-监听队列手动确认机制
发送端的问题解决了,现在消费者端是否正常拿到消息我们不得而知,所以开启消息确认
package com.szr.listen;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.impl.AMQImpl;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 监听队列
*/
@Component
public class RabbitListen {
@Autowired
RedisTemplate redisTemplate ;
/**
* 监听名称为confirm-queue的队列
* @param message 接收到的信息
*/
@RabbitListener(queues = "confirm-queue")
public void confirmQueue(String msg, Message message, Channel channel){
try {
System.out.println(msg);
Object uid = message.getMessageProperties().getHeader("spring_returned_message_correlation");
redisTemplate.boundHashOps("message").delete(uid.toString());
//消息被消费掉
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
5. RabbitMQ-死信队列
RabbitMQ-死信队列
延迟队列
流程
生产者发送消息-死信交换机-到死信队列-正常交换机-正常队列-消费者接收信息
额外属性列表
1、name: 队列的名称;
2、durable: 是否持久化;
3、exclusive: 是否独享、排外的;
4、autoDelete: 是否自动删除;
5.arguments:队列的其他属性参数,
(1)x-message-ttl:消息的过期时间,单位:毫秒;
(2)x-expires:队列过期时间,队列在多长时间未被访问将被删除,单位:毫秒;
(3)x-max-length:队列最大长度,超过该最大值,则将从队列头部开始删除消息;
(4)x-max-length-bytes:队列消息内容占用最大空间,受限于内存大小,超过该阈值则从队列头部开始删除消息
(5)x-overflow:设置队列溢出行为。这决定了当达到队列的最大长度时消息会发生什么。有效值是drop-head、reject-publish或reject-publish-dlx。仲裁队列类型仅支持drop-head
(6)x-dead-letter-exchange:死信交换器名称,过期或被删除(因队列长度超长或因空间超出阈值)的消息可指定发送到该交换器中
(7)x-dead-letter-routing-key:死信消息路由键,在消息发送到死信交换器时会使用该路由键,如果不设置,则使用消息的原来的路由键值
(8)x-single-active-consumer:表示队列是否是单一活动消费者,true时,注册的消费组内只有一个消费者消费消息,其他被忽略,false时消息循环分发给所有消费者(默认false)
(9)x-max-priority:队列要支持的最大优先级数;如果未设置,队列将不支持消息优先级;
(10)x-queue-mode(Lazy mode):将队列设置为延迟模式,在磁盘上保留尽可能多的消息,以减少RAM的使用;如果未设置,队列将保留内存缓存以尽可能快地传递消息
(11)x-queue-master-locator:在集群模式下设置镜像队列的主节点信息
package com.szr.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 死信队列-延迟队列
* 按照流程需要一个正常队列,需要一个延迟队列
*/
@Configuration
public class RabbitDeadConfig {
/**
* 声明一个正常队列
* @return
*/
@Bean(name = "normalqueue")
public Queue normalQueue(){
return new Queue("normal-queue");
}
/**
* 声明一个延迟队列
* @return
*/
@Bean(name = "deadqueue")
public Queue deadQueue(){
/*
声明延迟队列的额外属性,存活时间,存活时间一到-立马发送到正常交换机,实现延迟发送
额外属性列表
1、name: 队列的名称;
2、durable: 是否持久化;
3、exclusive: 是否独享、排外的;
4、autoDelete: 是否自动删除;
5.arguments:队列的其他属性参数,
(1)x-message-ttl:消息的过期时间,单位:毫秒;
(2)x-expires:队列过期时间,队列在多长时间未被访问将被删除,单位:毫秒;
(3)x-max-length:队列最大长度,超过该最大值,则将从队列头部开始删除消息;
(4)x-max-length-bytes:队列消息内容占用最大空间,受限于内存大小,超过该阈值则从队列头部开始删除消息;
(5)x-overflow:设置队列溢出行为。这决定了当达到队列的最大长度时消息会发生什么。有效值是drop-head、reject-publish或reject-publish-dlx。仲裁队列类型仅支持drop-head;
(6)x-dead-letter-exchange:死信交换器名称,过期或被删除(因队列长度超长或因空间超出阈值)的消息可指定发送到该交换器中;
(7)x-dead-letter-routing-key:死信消息路由键,在消息发送到死信交换器时会使用该路由键,如果不设置,则使用消息的原来的路由键值
(8)x-single-active-consumer:表示队列是否是单一活动消费者,true时,注册的消费组内只有一个消费者消费消息,其他被忽略,false时消息循环分发给所有消费者(默认false)
(9)x-max-priority:队列要支持的最大优先级数;如果未设置,队列将不支持消息优先级;
(10)x-queue-mode(Lazy mode):将队列设置为延迟模式,在磁盘上保留尽可能多的消息,以减少RAM的使用;如果未设置,队列将保留内存缓存以尽可能快地传递消息;
(11)x-queue-master-locator:在集群模式下设置镜像队列的主节点信息。
*/
Map map = new HashMap<>();
map.put("x-dead-letter-exchange","normal-exchange");
map.put("x-dead-letter-routing-key","normal");
return new Queue("dead-queue",true,false,false,map);
}
/**
* 声明一个正常交换机
* @return
*/
@Bean(name = "normalExchange")
public DirectExchange normaldirectExchange(){
return new DirectExchange("normal-exchange");
}
/**
* 声明一个延迟交换机
* @return
*/
@Bean(name = "deadExchange")
public DirectExchange deaddirectExchange(){
return new DirectExchange("dead-exchange");
}
/**
* 将正常队列绑定到正常交换机
* @param queue 正常队列
* @param directExchange 正常交换机
* @return
*/
@Bean
public Binding bindnormalqueuetonormalexchange(
@Qualifier(value = "normalqueue")Queue queue,
@Qualifier(value = "normalExchange")DirectExchange directExchange
){
return BindingBuilder.bind(queue).to(directExchange).with("normal");
}
/**
* 将等待队列绑定到等待交换机上
* @param queue 等待队列
* @param directExchange 等待交换机
* @return
*/
@Bean
public Binding binddeadqueuetodeadexchange(
@Qualifier(value = "deadqueue")Queue queue,
@Qualifier(value = "deadExchange")DirectExchange directExchange
){
return BindingBuilder.bind(queue).to(directExchange).with("dead");
}
}
package com.szr.controller;
import com.szr.po.MessageVo;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.UUID;
/**
* 监听的前端控制器
*/
@RestController
public class RabbitController {
//注入消息队列类
@Autowired
RabbitTemplate rabbitTemplate ;
//注入redis
@Autowired
RedisTemplate redisTemplate ;
@RequestMapping("/deadsend/{exchange}/{routingkey}/{message}")
public String deadconfirmSend(
@PathVariable("exchange")String exchange,
@PathVariable("routingkey")String routingkey,
@PathVariable("message")String message
){
//给里面加入唯一标识
String s = UUID.randomUUID().toString();
//创建数据信息对象
MessageVo messageVo = new MessageVo();
messageVo.setId(s); //UUid生成的唯一标识
messageVo.setExchange(exchange); //交换机名称
messageVo.setRoutingkey(routingkey); //路由键,约定信息
messageVo.setData(message); //发送内容
messageVo.setStatus("SENDER"); //发送状态
messageVo.setNum(1); //发送次数
/*
利用redis来存储信息实体,将消息队列和redis结合在一起
利用hash结构,双重键来确定一个value
第一个key是大名称,用来确定是什么内容
第二个key是唯一标识id,用来确定每一个具体内容
--存的是消息队列信息,存的是id为s的队列的信息
*/
redisTemplate.boundHashOps("message").put(s,messageVo);
CorrelationData correlationData = new CorrelationData();
correlationData.setId(s);
//这里设置消息的过期时间
rabbitTemplate.convertAndSend(exchange, routingkey, message, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置消息的过期时间-毫秒值-10秒
message.getMessageProperties().setExpiration("10000");
return message;
}
}, correlationData);
return "ok" ;
}
}