• RabbitMQ-----死信队列


    1.什么是TTL?

    a. time to live 消息存活时间
    
    b. 如果消息在存活时间内未被消费,则会被清除
    
    c. RabbitMQ支持两种ttl设置
      -单独消息进行配置ttl
      -整个队列进行配置ttl(居多)

    2.什么是rabbitmq的死信队列?

    没有被及时消费的消息存放的队列

    3.什么是rabbitmq的死信交换机?

    Dead Letter Exchange(死信交换机,缩写:DLX)当消息成为死信后,会被重新发送到另一个交换机,这个交换机就是DLX死信交换机

    4.消息有哪几种情况成为死信?

    a. 消费者拒收消息(basic.reject/ basic.nack),并且没有重新入队 requeue=false
    b. 消息在队列中未被消费,且超过队列或者消息本身的过期时间TTL(time-to-live)
    c. 队列的消息长度达到极限
    结果:消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列

    5.RabbitMQ管控台消息TTL测试

    a. 队列过期时间使用参数,对整个队列消息统一过期
    x-message-ttl
    单位ms(毫秒)
    b. 消息过期时间使用参数(如果队列头部消息未过期,队列中级消息已经过期,则消息会还在队列里面) expiration 单位ms(毫秒)
    c. 两者都配置的话,时间短的先触发

    6.如图

     

    7.什么是延迟队列?

    种带有延迟功能的消息队列,Producer 将消息发送到消息队列 服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到 Consumer 进行消费,该消息即定时消息

    8.使用场景

    1. 通过消息触发一些定时任务,比如在某一固定时间点向用户发送提醒消息
    b. 用户登录之后5分钟给用户做分类推送、用户多少天未登录给用户做召回推送;
    c. 消息生产和消费有时间窗口要求:比如在天猫电商交易中超时未支付关闭订单的场景,在订单创建时会发送一条延时消息。这条消息将会在30分钟以后投递给消费者,
    消费者收到此消息后需要判断对应的订单是否已完成支付。 如支付未完成,则关闭订单。如已完成支付则忽略

    9.业界的一些实现方式

    a. 定时任务高精度轮训
    
    b. 采用RocketMQ自带延迟消息功能
    
    c. RabbitMQ本身是不支持延迟队列的,怎么办?
    结合死信队列的特性,就可以做到延迟消息

    10.代码

    场景:

    客户提交商品订单后,需要在30分钟内完成支付,如未完成,则发送消息提醒订单失败

    a. rabbitmq配置类代码,配置普通/死信队列和交换机

    import org.springframework.amqp.core.*;
    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;
    
    /**
     * Rabbitmq配置类
     * 这里普通队列也可以叫它延时队列,是没有配置消费者(Listener)去监听的
     *
     * */
    @Configuration
    public class RabbitmqConfig {
    
        public static final String LOCK_MERCHANT_DEAD_EXCHANGE = "lock_merchant_dead_exchange";
        public static final String LOCK_MERCHANT_DEAD_QUEUE = "lock_merchant_dead_queue";
        public static final String LOCK_MERCHANT_DEAD_ROUTING_KEY = "lock_merchant_dead_routing_key";
        public static final String NEW_MERCHANT_EXCHANGE = "new_merchant_exchange";
        public static final String NEW_MERCHANT_QUEUE= "new_merchant_queue";
        public static final String NEW_MERCHANT_ROUTING_KEY = "new_merchant.#";
    
        /**
         * 死信交换机(topic模式)
         *
         * */
        @Bean
        public Exchange lockMerchantDeadExchange(){
            //durable: 是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,
            // 如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,
            // 当rabbitmq重启之后会读取该数据库
            return ExchangeBuilder.topicExchange(LOCK_MERCHANT_DEAD_EXCHANGE).durable(true).build();
        }
    
        /**
         * 死信队列
         *
         * */
        @Bean
        public Queue lockMerchantDeadQueue() {
            return QueueBuilder.durable(LOCK_MERCHANT_DEAD_QUEUE).build();
        }
    
        /**
         * 绑定死信交换机和死信队列
         * 这里不加@Qualifier的话会报错:there is more than one bean of “xxx” type
         * 因为死信交换机和普通交换机都配置了Exchange, 无法区分哪种作为参数
         * Queue同理
         *
         * */
        @Bean
        public Binding lockMerchantDeadBinding(@Qualifier("lockMerchantDeadExchange") Exchange exchange, @Qualifier("lockMerchantDeadQueue") Queue queue){
            return BindingBuilder.bind(queue).to(exchange).with(LOCK_MERCHANT_DEAD_ROUTING_KEY).noargs();
        }
    
        /**
         * 普通交换机(topic模式)
         *
         * */
        @Bean
        public Exchange newMerchantExchange(){
            //durable: 是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,
            // 如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,
            // 当rabbitmq重启之后会读取该数据库
            return ExchangeBuilder.topicExchange(NEW_MERCHANT_EXCHANGE).durable(true).build();
        }
    
        /**
         * 普通队列
         *
         * */
        @Bean
        public Queue newMerchantQueue() {
            Map<String, Object> args = new HashMap<>();
            //消息过期后,进入死信交换机
            args.put("x-dead-letter-exchange", LOCK_MERCHANT_DEAD_EXCHANGE);
            //消息过期后,进入死信交换机的路由键
            args.put("x-dead-letter-routing-key", LOCK_MERCHANT_DEAD_ROUTING_KEY);
            //消息过期时间 单位:毫秒 消息过期后,会从普通队列转入死信队列
            //这里方便测试设置10秒后消息过期
            args.put("x-message-ttl",10000);
            return QueueBuilder.durable(NEW_MERCHANT_QUEUE).withArguments(args).build();
        }
    
        /**
         * 绑定普通交换机和普通队列
         *
         * */
        @Bean
        public Binding newMerchantBinding(){
            return new Binding(NEW_MERCHANT_QUEUE, Binding.DestinationType.QUEUE,
                    NEW_MERCHANT_EXCHANGE, NEW_MERCHANT_ROUTING_KEY, null);
        }
    }
    View Code

    b. 监听死信队列代码

    import com.rabbitmq.client.Channel;
    import com.theng.shopuser.config.RabbitmqConfig;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.rabbit.annotation.RabbitHandler;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    /**
     * 消费者监听死信队列
     *
     */
    @Component
    @RabbitListener(queues = RabbitmqConfig.LOCK_MERCHANT_DEAD_QUEUE)
    public class OrderMQListener {
    
        /**
         * body: 接收convertAndSend(String exchange, String routingKey, Object object)的object消息
         *
         * */
        @RabbitHandler
        public void messageHandler(String body, Message message, Channel channel) throws IOException {
    
            long msgTag = message.getMessageProperties().getDeliveryTag();
            System.out.println("body: " + body);
            System.out.println("msgTag: " + msgTag);
            System.out.println("message: " + message.toString());
    
            //30分钟后,从body中获取买家信息再从数据库查询抢购到的商品订单是否处理 TODO
            //如果没有处理,则向商家发送提醒消息 TODO
    
            //告诉broker(消息队列服务器实体),消息已经被确认
            channel.basicAck(msgTag, false);
            //告诉broker,消息拒绝确认(可以拒绝多条,把比当前msgTag值小的也拒绝)
    //        channel.basicNack(msgTag, false, true);
            //告诉broker,消息拒绝确认(只能拒绝当前msgTag的这条)
    //        channel.basicReject(msgTag, true);
        }
    }
    View Code

    c. application.ym配置文件

    spring:
      rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        password: 123456
        #虚拟主机 可在http://localhost:15672管理平台进行配置
        virtual-host: /dev
        #开启消息二次确认ConfirmCallback配置
        publisher-confirms: true
        #开启ReturnCallback配置
        publisher-returns: true
        #修改交换机改投消息递到队列失败策略
        #true:交换机处理消息到队列失败,则返回给生产者
        #和publisher-returns配合使用
        template:
          mandatory: true
        #消息手工确认ack
        listener:
          simple:
            acknowledge-mode: manual
    View Code

    d. 控制器代码

    @RestController
    @RequestMapping("/user-info")
    public class UserInfoController {
    
        @Autowired
        public RedisTemplate redisTemplate;  
    
        //消息生产者
        @GetMapping("/send")
        public Object testSend(){
            //object可存储买家信息
            rabbitTemplate.convertAndSend(RabbitmqConfig.NEW_MERCHANT_EXCHANGE, "new_merchant.create", "买家抢购成功,请及时处理订单!");
    
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "买家抢购成功,请在30分钟内提交订单!");
            return "success";
        }
    }
    View Code

    结果:

    生产者发送消息10秒后,消息会进入死信交换机,通过死信队列将订单过期消息发送给消费者

  • 相关阅读:
    zTree根据json选中节点,并且设置其他节点不可选
    点击一次按钮,发生多次ajax请求
    sublimeText3 中配置sass环境,并将编译后文件保存到指定文件夹
    MD5加密
    c# Http请求之HttpClient
    xml与object 之间的ORM
    wpf和winform的那点区别
    c# http请求ajax页面
    c# https请求
    python爬虫框架Pyspider初次接触
  • 原文地址:https://www.cnblogs.com/tianhengblogs/p/15384506.html
Copyright © 2020-2023  润新知