JAVA03_22学习总结(RabbitMQ消息队列扩展)

今日内容

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" ;
    }
}

你可能感兴趣的:(Java学习--四阶段,java-rabbitmq,rabbitmq,java,学习,分布式)