rabbitMQ高级特性

	1.消息可靠性投递
	2.Consumer ACK
	3.消费端限流
	4. TTL
	5. 死信队列
	6. 延迟队列
	7. 消息的幂等性
消息可靠性传递
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。

消息投递步骤:

生产者(channel)---->交换机------>队列中
保证消息的可靠性:
1. 保证消息从发送者到交换机的可靠性: 使用Confirm确认机制。
2. 保证消息从交换机到队列的可靠性; 使用return回退机制。
3. 消息在队列中的可靠性。  设置队列和消息的持久化。
4. 保证消息从队列到消费者的可靠性。  使用消费端的手动确认机制。

为了确保消息的可靠性投递,提供了如下两种方式:

confirm 确认模式
return  退回模式
product的confirm模式

(1)在配置文件中开启rabbitmq生产方式

spring:
  rabbitmq:
    host: 192.168.31.243
    #开启rabbitMQ的生产方确认模式
    #correlated :发布消息成功到交换机后触发回调方法
    #none:禁用发布确认模式(默认)
    publisher-confirm-type: correlated

(2) 代码实现

@SpringBootTest
public class RabbitLevel {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void productConfirm(){
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            /*设置rabbitTemplate的确认回调函数。如果消息到达交换机则返回true,如果消息没有到达交换机则返回一个false*/
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if (ack){ //消息到达交换机
                    System.out.println("成功-->"+ack);
                }else {//消息未到达交换机
                    System.out.println("失败-->"+cause+","+ack);
                }
            }
        });
        //发送消息
        rabbitTemplate.convertAndSend("exchange1","","hello confirm");
    }
}

在这里插入图片描述

product的退回模式(returns)

(1)在配置文件中开启rabbitmq生产方式

 #开启发布者退回模式
    publisher-returns: true

(2)代码实现

@Test
    public void returnConfirm(){
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            //只要交换机到队列失败时才会触发该方法
            //可以继续发送也可以取消相应的业务功能
            public void returnedMessage(ReturnedMessage returned) {
                System.out.println("消息从交换机到队列发生了失败,错误是--->"+returned.getReplyText());
            }
        });
        // 发送消息                                                        //错误的路由
        rabbitTemplate.convertAndSend("exchangeDirct","qwertasdf","hello returns");
    }

在这里插入图片描述

Consumer ACK
表示消费端收到消息后的确认方式。
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息队列中移除。但是在实际业务处理中,很可	能	消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

(1)消费端配置手动开启确认模式

server:
  port: 8081
spring:
  rabbitmq:
    host: 192.168.31.243
    listener:
      simple:
        #manual:用户必须通过具有频道意识的侦听器进行确认
        #none:自动提交 默认为ture
        #auto:容器将根据侦听器是否正常返回或引发异常来发出确认
        acknowledge-mode: manual

代码实现

@Component
public class ListenerConsumer {


    @RabbitListener(queues = "queue_Dirct02")
    public void consumerAck(Message message,Channel channel) throws Exception{

        //获取信息的唯一标识
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        //消费的消息
        byte[] body = message.getBody();
        String str = new String(body);
        System.out.println(str);

        try{
//            int c = 10/0;
            System.out.println("处理业务逻辑"); //此时如果这里出现一个异常,消息接收到,业务处理出现异常,那么该消息就会丢失
            // long deliveryTag, 表示的标识
            // boolean multiple:是否允许多确认
            channel.basicAck(deliveryTag,true);//处理完业务再消费掉
        }catch (Exception e){
            // long deliveryTag, 表示的标识
            // boolean multiple:是否允许多确认
            // requeue: 是否让队列再次发送该消息
            channel.basicNack(deliveryTag,true,true);
        }
    }
}

消费端限流

(1) 在配置文件中配置读取个数

 #prefetch:每个使用者可以处理的未确认消息的最大数量
        prefetch:5

(2)代码实现

@RabbitListener(queues = "queue_Dirct02")
    public void consumerAck(Message message,Channel channel) throws Exception{

        //获取信息的唯一标识
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        //消费的消息
        byte[] body = message.getBody();
        String str = new String(body);
        System.out.println(str);

        try{
//            int c = 10/0;
            System.out.println("处理业务逻辑"); //此时如果这里出现一个异常,消息接收到,业务处理出现异常,那么该消息就会丢失
            // long deliveryTag, 表示的标识
            // boolean multiple:是否允许多确认
//            channel.basicAck(deliveryTag,true);//处理完业务再消费掉
        }catch (Exception e){
            // long deliveryTag, 表示的标识
            // boolean multiple:是否允许多确认
            // requeue: 是否让队列再次发送该消息
            channel.basicNack(deliveryTag,true,true);
        }
    }
TTL 设置过期时间

(1) 设置消息的过期;该消息必须在队列的头部时才会被移除
(2) 代码实现

 @Test
    public void setTTl(){

        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //设置消息过期时间
                message.getMessageProperties().setExpiration("20000");
                return message;
            }
        };
        //exchange:交换机
        //routingKey:路由 key
        //message:发消息
        //messagePostProcessor :消息后处理器
        rabbitTemplate.convertAndSend("myexchange","","hello returns",messagePostProcessor);

    }

增加

通过代码创建队列和交换机以及绑定

(1) 代码实现

@Configuration
public class RabbitConfig {
    private final String exchange_name="myExchange";
    private final String queue_name="myQueue";

    //交换机
    @Bean
    public Exchange exchange(){
        return new FanoutExchange(exchange_name,true,false);
    }

    //队列
    @Bean
    public Queue queue(){
                                                            //队列消息 过期时间
        return QueueBuilder.durable(queue_name).withArgument("x-message-ttl",20000).build();
    }

    //绑定
    @Bean
    public Binding binding(Exchange exchange,Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("").noargs();
    }
}

死信队列
死信队列,英文缩写:DLX  。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以
被重新发送到另一个交换机,这个交换机就是DLX。

在这里插入图片描述
消息成为死信的三种情况:

	1. 队列消息长度到达限制;
	2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
	3. 原队列存在消息过期设置,消息到达超时时间未被消费;

队列绑定死信交换机:

给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key

在这里插入图片描述(1)创建队列\交换机和死信队列和死信交换机

@Configuration
public class RabbitConfiguration {

    private final String EXCHANGE="exchange";
    private final String DEAD_EXCHANGE="dead_exchange";
    private final String QUEUE="queue";
    private final String DEAD_QUEUE="dead_queue";

    //交换机
    @Bean
    public Exchange exchange(){
        return ExchangeBuilder.directExchange(EXCHANGE).build();
    }
    //死信交换机
    @Bean
    public Exchange deadExchange(){
        return ExchangeBuilder.directExchange(DEAD_EXCHANGE).build();
    }

    //队列
    @Bean
    public Queue queue(){
        return QueueBuilder.durable(QUEUE)
                .withArgument("x-message-ttl",10000) //设置队列消息超时时间
                .withArgument("x-max-length",10)  //队列最大长度
                .withArgument("x-dead-letter-exchange",DEAD_EXCHANGE) //队列连接的死信交换机
                .withArgument("x-dead-letter-routing-key","error") //与私信交换机的路由key
                .build();
    }

    //死信队列
    @Bean
    public Queue deadQueue(){
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }

    //绑定
    @Bean
    public Binding binding(Exchange exchange,Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("error").noargs();
    }

    @Bean
    public Binding deadBinding(Exchange deadExchange,Queue deadQueue){
        return BindingBuilder.bind(deadQueue).to(deadExchange).with("error").noargs();
    }


}

第一种 超时未处理到达死信队列(测试)
@SpringBootTest
public class TestDead {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testDead(){
       for (int i = 0; i < 10; i++) {
            //发送消息                    //此时 因为未消费 加上队列有设置超时时间  所以 在10s之后进入到死信队列中
            rabbitTemplate.convertAndSend("exchange","error","hello 超市未消费滴");
        }
}

在这里插入图片描述过了10s钟

在这里插入图片描述用视图化界面读取一下
在这里插入图片描述

第二种 队列消息长度到达限制

(1.) 发送消息

@SpringBootTest
public class TestDead {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testDead(){
        //这里循环了15次  设置的最大长度为10  所以超出的5个会到死信队列里
        for (int i = 0; i < 15; i++) {
            //发送消息                    //此时 因为未消费 加上队列有设置超时时间  所以 在10s之后进入到死信队列中
            rabbitTemplate.convertAndSend("exchange","error","hello 超市未消费滴");
        }
    }
}

在这里插入图片描述

第三种 消费者拒接消费消息
basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;

(1) 配置文件中配置手动接收

listener:
      simple:
        #设置手动接收
        acknowledge-mode: manual

(2) producer发送消息

@SpringBootTest
public class TestDead {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testDead(){
        //这里循环了15次  设置的最大长度为10  所以超出的5个会到死信队列里
        for (int i = 0; i < 20; i++) {
            //发送消息                    //此时 因为未消费 加上队列有设置超时时间  所以 在10s之后进入到死信队列中
            rabbitTemplate.convertAndSend("exchange","error","hello 超市未消费滴");
        }
    }
}

(2)消费者代码

@Component
public class ConsumerL {

    @RabbitListener(queues = "queue")
    public void send(Message message , Channel channel){
        //唯一标识
        long tag = message.getMessageProperties().getDeliveryTag();
        //获取消息
        byte[] body = message.getBody();
        String s = new String(body);
        System.out.println(s);

        try {
            //拒绝接收消息
            channel.basicNack(tag,false,false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
延迟队列
虽然rabbitmq未提供延迟队列功能,但可以使用TTL+死信队列完成

在这里插入图片描述

@SpringBootTest
public class TestDead {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testDead(){
            //发送消息                    //此时 因为未消费 加上队列有设置超时时间  所以 在10s之后进入到死信队列中
            rabbitTemplate.convertAndSend("exchange","error","下单成功");
    }
}

在这里插入图片描述
过三十分钟后(比喻)
在这里插入图片描述

消息幂等性保障
幂等性:用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用

让每个消息携带一个全局的唯一ID,即可保证消息的幂等性,具体消费过程为:

1.消费者获取到消息后先根据id去查询redis/db是否存在该消息
2.如果不存在,则正常消费,消费完毕后写入redis/db
3.如果存在,则证明消息被消费过,直接丢弃。

在这里插入图片描述
使用redis的缓存

<dependency> 
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

代码实现

@Component
public class ConsumerL {

    @Autowired
    private RedisTemplate redisTemplate;

    @RabbitListener(queues = "dead_queue")
    public void send(Message message , Channel channel){
        //得到信息的唯一标识
        long id = message.getMessageProperties().getDeliveryTag();
        //获取是否有id
        Object o = redisTemplate.opsForValue().get(id);
        //判断是否为空
        if (o==null){
            System.out.println("处理业务,完成业务功能");
            redisTemplate.opsForValue().set(id,"obj"); //设置到缓存 设置后 确认返回队列出现异常,再次消费时,已经有唯一id 则让队列直接删除
            try {
                channel.basicAck(id,true);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else {
            try {
                channel.basicAck(id,true);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值