应用的场景:主要的就是生产者的生产速度大于消费速度,如果低于那么优先就没有任何的意义了
优先级队列的实现主要有两个方面:队列的优先级 发送消息时的优先这两个问题
代码是在spriingboot整合rabbitmq基础上改造过来的,创建队列时,给队列设置一个优先级
/**
* 直连的队列名称
* @return
*/
@Bean
public Queue testDirectQueue(){
/**
* dureable:是否持久化,默认是false, 持久化队列: 会被存储在磁盘上,当消息代理重启时仍然存在
* exclusive: 默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列会被关闭
* autoDelete:是否自动删除,当没有生产者或者消费者使用队列,该队列会自动删除
* return new Queue("testDirectQueue", true, true, fasle,)
*/
Map<String, Object> map = new HashMap<>();
//在这里配置好队列的优先级0-10 默认的是5 注意这里Map中的key需要注意下,是否正确的
map.put("x-max-priority", 10);
return new Queue("firstQueue", true, false,false,map);
}
在发送消息时,给消息设置下优先级:
@RequestMapping(value = "sendMessage", method = RequestMethod.GET)
public String sendDirectMessage(){
//现在发送一个map的消息过去
String messageId = UUID.randomUUID().toString();
String messageData = "test message, hello";
//将lcoaldateTime转换为string类型的
String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", format);
rabbitTemplate.convertAndSend("exchange", "testDirectRouting", map, new MessagePostProcessor() {
//这里给消息设置优先级
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置消息的优先级别为最大的
message.getMessageProperties().setPriority(10);
return message;
}
});
return "ok";
}
死信队列:
死信队列的场景主要是用来确保重要的业务数据能够记录下来,同时不需要再次入队操作
实现死信队列的流程比较简单:首先就是定义好队列,交换机(包括死信队列,死信交换机) 这里说明下死信队列就是平常的队列并不特殊,死信交换也是如此
@SpringBootConfiguration
public class DeadLetterConfig {
/**
* 声明一个业务的交换机
* @return
*/
@Bean("businessExchange")
public FanoutExchange businessExchange(){
return new FanoutExchange("businessExcahnge");
}
/**
* 声明一个死信交换机
* @return
*/
@Bean("deadLetterExchange")
public FanoutExchange deadLetterExchange(){
return new FanoutExchange("deadLetterExchange", false, false );
}
/**
* 声明一个业务队列
* @return
*/
@Bean("businessQueueA")
public Queue businessQueueA(){
Map<String, Object> map = new HashMap(2);
//队列绑定一个死信交换机属性
map.put("x-dead-letter-exchange", "deadLetterExchange");
//给队列中的死信路由键设置一个值
map.put("x-dead-letter-routing-key", "deadLeeterA");
//声明一个队列
return new Queue("businessQueueA", true, false, false, map);
}
/**
* 声明另外一个队列,不同于上面的队列
* 他们之间的区别是,设置同意死信交换机
* 但是设置不同的routyKey值
* @return
*/
@Bean("businessQueueB")
public Queue businessQueueB(){
Map<String, Object> map = new HashMap(2);
//同上面那个队列一样,声明并且设置一些属性,绑定同一个死信交换机,设置不同的routyKey
//队列绑定一个死信交换机属性,注意这是以个交换机名称
map.put("x-dead-letter-exchange", "deadLetterExchange");
//给队列中的死信路由键设置一个值
//其实这个路由值是没有任何用的,因为是Fanout类型的交换机
map.put("x-dead-letter-routing-key", "deadLeeterB");
return new Queue("businessQueueB", true, false, false, map);
}
/**
* 设置一个死信队列,但是它并没有其他的属性
* @return
*/
@Bean("deadLetterQueueA")
public Queue deadLetterQueueA(){
return new Queue("deadLetterQueueA", true, false, false);
}
/**
* 设置好另外一个死信队列
* @return
*/
@Bean("deadLetterQueueB")
public Queue deadLetterQueueB(){
return new Queue("deadLetterQueueB", true, false,false);
}
/**
* 绑定一个业务队列与业务交换机的绑定
* 这里是只能设置一个routyKey值的
* @return
*/
@Bean("bindBusinessA")
Binding bindbusinessQueueA(){
return BindingBuilder.bind(businessQueueA()).to(businessExchange());
}
/**
* 绑定queueB与交换
* @return
*/
@Bean("bindBusinessB")
Binding bindBusinessQueueB(){
return BindingBuilder.bind(businessQueueB()).to(businessExchange());
}
/**
* 指定队列与Fanout交换机之间的绑定
* @return
*/
@Bean
Binding bindingDeadWithExchange(){
return BindingBuilder.bind(deadLetterQueueA()).to(deadLetterExchange());
}
/**
* 将死信队列与死信交换机进行绑定
* @return
*/
@Bean
Binding bindingDeadB(){
return BindingBuilder.bind(deadLetterQueueB()).to(deadLetterExchange());
}
第二步就是在消费队列时进行的代码:
@Slf4j
@Component
public class DeadLetterLlistener {
/**
* 监听队列名
* 接收到业务队列的消息
*/
@RabbitListener(queues = "businessQueueA")
public void receive(Message message, Channel channel) throws IOException {
/**
* 死信消息的原理:
* 如果队列配置了参数 x-dead-letter-routing-key 的话,“死信”的路由key将会被替换成该参数对应的值。如果没有设置,则保留该消息原有的路由key。
* 举例:如果原有消息的路由key是testA,被发送到业务Exchage中,然后被投递到业务队列QueueA中,
* 如果该队列没有配置参数x-dead-letter-routing-key,则该消息成为死信后,将保留原有的路由keytestA,如果配置了该参数,
* 并且值设置为testB,那么该消息成为死信后,路由key将会被替换为testB,然后被抛到死信交换机中。
*/
String msg = new String(message.getBody());
log.info("接收到的消息为:{}", msg);
boolean ack = true;
Exception exception = null;
try {
if (msg.contains("deadletter")){
throw new RuntimeException("dead letter exception");
}
}catch (Exception e){
ack = false;
exception = e;
}
/**
* 消息成为死信的几个先决条件:
* 1:消息是被否定确认(具体就是指:basicNack()或者是basicReject)
* 2:消息中的requeue属性是被确认为false的
* 3:具体的成为死信的原因有如下几个:过期消息, 超过规定的队列长度 消费者在进行消费时出现异常
*/
if (!ack){
log.info("消息消费发生异常,error msg:{}", exception.getStackTrace());
//这个方法为不确认该tag对应的消息,b 代表的是确认多条数据信息 b1代表的requeue重新入列
//这个方法代表的就是将消息丢弃到死信队列中去处理
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
}else{
//肯定消息消费的方法,第二个参数代表的就是确认多个消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
/**
* 接收到业务队列的方法B
* @param message
* @param channel
* @throws IOException
*/
@RabbitListener(queues = "businessQueueB")
public void receiveB(Message message, Channel channel) throws IOException {
System.out.println("接收到的信息数据为:" + new String(message.getBody()));
//此方法代表的就只是将消息做手动确认处理
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
/**
* 接收到死信队列的信息方法
* @param message
* @param channel
* @throws IOException
*/
@RabbitListener(queues = "deadLetterQueueA")
public void receiveDead(Message message, Channel channel) throws IOException {
System.out.println("接收到死信队列的消息数据为:" + new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
@RabbitListener(queues = "deadLetterQueueB")
public void receiveDeadB(Message message, Channel channel) throws IOException {
System.out.println("queueB接收到信息数据为:" + new String(message.getBody()));
/**
* x-first-death-exchange:第一次死信所在的交换机名称
* x-first-death-reason: 成为死信的原因
* x-first-death-queue: 第一次成为死信所在的队列
*/
log.info("死信队列中的信息为:{}", message.getMessageProperties());
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}