使用RabbitMQ的延时交换机实现定时发送

应用场景,用户选择一个时间段定时发送信息到相应平台或者设备

首先使用普通的死信交换机通过设置ttl只能是延后一定时间发送的无法完成定时

public static final String DELAY_QUEUEA_NAME = "delay.queue";
    
     public static final String DEAD_LETTER_EXCHANGE = "dead.letter.exchange";
    
    // 声明死信Exchange
    @Bean("deadLetterExchange")
    public DirectExchange deadLetterExchange(){
        return new DirectExchange(DEAD_LETTER_EXCHANGE);
    }

    // 声明延时队列A 延时10s
    // 并绑定到对应的死信交换机
    @Bean("delayQueueA")
    public Queue delayQueueA(){
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEA_ROUTING_KEY);
        // x-message-ttl  声明队列的TTL 延时10s
        args.put("x-message-ttl", 6000);
        return QueueBuilder.durable(DELAY_QUEUEA_NAME).withArguments(args).build();
    }

设置消息的Expiration时可以完成单个消息的定时但是会有一个问题,如果这个队列的前面的消息没有过期而后面消息先过期是无法进入到死型交换机的。

所以要使用一个插件https://github.com/rabbitmq/rabbitmq-delayed-message-exchange

下载对应的版本的ez后缀文件,放到RabbitMq下的plugins中

在sbin下cmd控制台下输入

 开启插件
   rabbitmq-plugins enable rabbitmq_delayed_message_exchange
   关闭插件
   rabbitmq-plugins disable rabbitmq_delayed_message_exchange
   查看插件
   rabbitmq-plugins list

这里注意如果版本不一致会报错开启插件不成功,后续连接RabbitMq的时候也会报错,找不到x-delayed-message

插件安装成功后,正式开发(spring-rabbit+yml配置):

常量定义 MQConstant:

public class MQConstant {
    //死信队列
    public static final String DEAD_LETTER_NAME = "dead.letter.queue";
    //死信交换机(应该叫做延时交换机了)
    public static final String DEAD_LETTER_EXCHANGE = "dead.letter.exchange";
    //死信交换机路由key
    public static final String DEAD_LETTER_QUEUE_ROUTING_KEY = "delay.queue.routing.key";
}

声明定义死信交换机和死信队列 DelayMQConfig:

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;

@Configuration
public class DelayMQConfig {


    // 声明死信交换机
    @Bean("deadLetterExchange")
    public CustomExchange  deadLetterExchange(){
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange(MQConstant.DEAD_LETTER_EXCHANGE,"x-delayed-message",true,false,args);
    }
    //声明死信队列
    @Bean("deadLetterQueue")
    public Queue deadLetterQueue(){
        return QueueBuilder.durable(MQConstant.DEAD_LETTER_NAME).build();
    }

    // 声明死信队列绑定关系
    @Bean
    public Binding deadLetterBinding(@Qualifier("deadLetterQueue") Queue queue,
                                                                       @Qualifier("deadLetterExchange") CustomExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(MQConstant.DEAD_LETTER_QUEUE_ROUTING_KEY).noargs();
    }

}

结合业务应用我将消费者和生产者放入到了ServiceImpl中了,然后BizException和baseMapper都是我使用的框架自定义的类,但是也不影响理解。

这里的Plan就是一个分发计划其中distributeStatus就是分发状态(0-未分发,1-已分发)这里只修改了分发状态后续接入第三方接口实现分发。

 @Override
    @Transactional
    public Long add(PlanCreateDTO planCreateDTO) {
        Plan plan = BeanUtil.toBean(planCreateDTO, Plan.class);
        if (planCreateDTO.getDistributeType()==0){
            plan.setDistributeStatus(1);
            //todo 后续调用第三方接口实现分发
            this.baseMapper.insert(plan);
        }else{
            plan.setDistributeStatus(0);
            Duration duration = Duration.between(LocalDateTime.now(),plan.getDistributeTime());
            long difference = duration.toMillis();
            //现在时间和发布时间的差值为负数
            if (difference<=0){
                throw new BizException("定时时间已过期");
            }
            int delayTime = (int) difference;
            //由于MessageProperties的delay属性是毫秒且是Integer类型,用long型转换会出现负值,大概最长期限是24.8551天
            if (delayTime<=0){
                throw new BizException("超出定时发布的最长期限");
            }
            this.baseMapper.insert(plan);
            //生产者,将plan序列化的json字符串发送给死信交换机,并给消息设置延时时间
            rabbitTemplate.convertAndSend(MQConstant.DEAD_LETTER_EXCHANGE, MQConstant.DEAD_LETTER_QUEUE_ROUTING_KEY, JSON.toJSONString(plan), message -> {
                message.getMessageProperties().setDelay(delayTime);
//                message.getMessageProperties().setExpiration(String.valueOf(duration.toMillis()));//设置过期时间是无效的他不能应用于这个自定义交换机
                return message;
            } );
        }

        return plan.getId();
    }

    //消费者,监听死信队列
    @RabbitListener(queues = MQConstant.DEAD_LETTER_NAME)
    public void receive(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        Plan plan = JSON.parseObject(msg,Plan.class);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        plan.setDistributeStatus(1);
        //todo 后续调用第三方接口实现分发
        this.baseMapper.updateById(plan);
    }

发送定时发布的请求如果Rabbit的Exchange出现这样即代表成功。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值