延迟消息与死信消息

延迟消息

当消息发送到服务器时,该消息不能直接被放在队列里面,而是在 MQ 服务器里面建立一个定时任务,在服务器到达延时时间后再执行投递的操作

延迟消息的使用场景

淘宝七天自动确认收货。在我们签收商品后,物流系统会在七天后延时发送一个消息给支付系统, 通知支付系统将款打给商家,这个过程持续七天,就是使用了消息中间件的延迟推送功能。12306 购票支付确认页面。我们在选好票点击确定跳转的页面中往往都会有倒计时,代表着 30 分钟内订单不确认的话将会自动取消订单。其实在下订单那一刻开始购票业务系统就会发送一个延时消息给订单系统,延时30分钟,告诉订单系统订单未完成,如果我们在30分钟内完成了订单,则可以通过逻辑代码判断来忽略掉收到的消息。在上面两种场景中,如果我们使用下面两种传统解决 1 对于单机版而已,我们的策略有3种定时删除、定期删除、惰性删除。

对于分布式项目而言呢,我们最好选择 MQ

插件安装:https://www.cnblogs.com/geekdc/p/13549613.html

RabbitMQ 的延时消息实现

RabbitMQ 本身没有提供一个队列的机制,但是它里面有个延迟队列的机制

延迟队列的机制及创建项目 rabbitmq-springboot-dealy

application.properties 修改为 application.yml 格式的,然后修改其中的内如如下所示:

server:
  port: 8001
spring:
  application:
    name: rabbitmq-springboot-dealy
  rabbitmq:
    host: 139.196.183.130
    port: 5672
    username: user
    password: 123456
    virtual-host: v-it6666
    # 这个是老版本的用法
    # publisher-confirms: true
    # 开启消息到达交换机的确认机制
    publisher-confirm-type: correlated
    # 消息由交换机到达队列失败时触发
    publisher-returns: true
    listener:
      simple:
        # 自动签收,这个是默认行为
        # acknowledge-mode: auto
        # 手动签收
        acknowledge-mode: manual
      direct:
        # 设置直连交换机的签收类型
        acknowledge-mode: manual

代码实现延迟消息

/**
 * @author BNTang
 */
@Configuration
public class DealyMessageConfig {

    @Bean
    public Queue dealyQueue() {
        Map<String, Object> args = new HashMap<>();

        // 把一个队列修改为延迟队列
        // 消息的最大存活时间
        args.put("x-message-ttl", 10 * 1000);

        // 该队列里面的消息死了,去那个交换机
        args.put("x-dead-letter-exchange", "DeadLetter.exc");

        // 该队列里面的消息死了,去那个交换机,由那个路由 key 来路由他
        args.put("x-dead-letter-routing-key", "DeadLetter.key");

        return new Queue("dealy", true, false, false, args);
    }

    /**
     * 死信交换机
     *
     * @return 交换机
     */
    @Bean
    public DirectExchange deadLetterExchange() {
        return new DirectExchange("DeadLetter.exc");
    }

    /**
     * 新生队列
     *
     * @return 队列
     */
    @Bean
    public Queue newQueue() {
        return new Queue("new.queue");
    }

    /**
     * 绑定
     *
     * @return 绑定结果
     */
    @Bean
    public Binding newAndDeadLetterExchange() {
        return BindingBuilder.bind(newQueue()).to(deadLetterExchange()).with("DeadLetter.key");
    }
}

监听新的世界

/**
 * @author BNTang
 */
@Component
@RabbitListener(queues = {"new.queue"})
public class MessageReceive {

    @RabbitHandler
    public void onMessage(String content, Message message, Channel channel) {
        System.out.println("来到了新的世界,正在消费中...");

        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            channel.basicAck(deliveryTag, false);

            System.out.println("签收成功:" + content);

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println("签收成功时间为:" + sdf.format(new Date()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试

我们往延迟队列发送消息,启动项目

@SneakyThrows
@Test
public void sendDealy() {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println("消息发送时间:" + sdf.format(new Date()));
    rabbitTemplate.convertAndSend("dealy", "我是一个延时消息");
    System.out.println("消息发送成功");
    System.in.read();
}

发消息指定时间

我们的这个队列里面让你活 10s,你想活11s那是不行的,但是你活5s是可以的,它是以那个最小为主的!

@SneakyThrows
@Test
public void sendDealy() {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println("消息发送时间:" + sdf.format(new Date()));
    rabbitTemplate.convertAndSend("dealy", (Object) "我是一个延时消息", new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            // 我们延时队列里面设置的为 10 秒,但这个消息它只想活 5 秒
            message.getMessageProperties().setExpiration("5000");
            return message;
        }
    });
    System.out.println("消息发送成功");
    System.in.read();
}

测试方式同之前的一样

posted @ 2020-11-13 13:20  BNTang  阅读(302)  评论(13编辑  收藏  举报