springBoot之延时队列

本文介绍了如何使用Java创建延时队列,通过`DelayTaskQueue`和`DelayTask`类实现任务的存储和按需执行。生产者`DelayTaskProducer`负责将消息按照指定时间投递,消费者`DelayTaskConsumer`则按类型执行不同业务操作。关键步骤包括单例队列创建、`Delayed`接口的实现和消息的异步处理。

1、创建延时队列

package com.example.demo.utils;
import lombok.Data;
import java.util.concurrent.DelayQueue;
/**
 * 延时队列
 * 需要保证队列单例
 */
@Data
public class DelayTaskQueue {
    private static class Holder{
         static DelayQueue<DelayTask> instance = new DelayQueue(); //单例保证队列唯一
    }
    public static DelayQueue<DelayTask> getInstance() {
        return Holder.instance;
    }
}

2、创建消息任务(可根据自己业务来)

package com.example.demo.utils;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
 * 延时任务
 * 需要实现 Delayed 接口 重写getDelay和compareTo指定相应的规则
 */
@Data
@Accessors(chain = true)//链式调用注解
public class DelayTask implements Delayed {
    private String id;
    private Long time;
    private Integer type;
    @Override
    public long getDelay(TimeUnit unit) {
        // 计算该任务距离过期还剩多少时间
        long remaining = time - System.currentTimeMillis();
        return unit.convert(remaining, TimeUnit.MILLISECONDS);
    }
    @Override
    public int compareTo(Delayed o) {
        // 比较、排序:对任务的延时大小进行排序,将延时时间最小的任务放到队列头部
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    }
}

3、消息生产者(根据业务需要投递消息进入队列)

package com.example.demo.utils;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.DelayQueue;
/**
 * 在需要使用到延时队列的业务进行投递任务(消息)
 */
@Slf4j
public class DelayTaskProducer {
    /**
     *
     * @param id  业务id
     * @param time 消费时间  单位:毫秒
     * @param type 业务类型
     */
    public static void delayTask(String id,Long time,Integer type){
        DelayQueue<DelayTask> delayQueue = DelayTaskQueue.getInstance();//创建队列 1
        DelayTask delayTask = new DelayTask();//创建任务
        delayTask.setId(id)
                .setTime(time)
                .setType(type);
        log.info("=============入延时队列,{}",delayTask);
        boolean offer = delayQueue.offer(delayTask);//任务入队
        if(offer){
            log.info("=============入延时队列成功,{}",delayQueue);
        }else{
            log.info("=============入延时队列失败");
        }
    }
}

4、消息消费者(规定自己相应的业务操作)

package com.example.demo.utils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.DelayQueue;
@Data
@Slf4j
@Component
public class DelayTaskConsumer implements CommandLineRunner {
  /*  @Autowired
    private IProPatMeetingService meetingService;
    @Autowired
    private ParActivityService activityService;*/
    @Override
    public void run(String ...args) throws Exception {
        DelayQueue<DelayTask> delayQueue = DelayTaskQueue.getInstance();//获取同一个put进去任务的队列
        new Thread(() -> {
            while (true) {
                try {
                    // 从延迟队列的头部获取已经过期的消息
                    // 如果暂时没有过期消息或者队列为空,则take()方法会被阻塞,直到有过期的消息为止
                    DelayTask delayTask = delayQueue.take();//阻塞
                    switch (delayTask.getType()) {//判断业务类型,执行对应的业务操作
                        case 1:
                            log.info("====================会议消费,{}",delayTask.getType());
                            //ParMeeting meeting = meetingService.getById(delayTask.getId());
                            //meetingService.pushWxMessageAndMail(meeting,true,null);
                            break;
                        case 2:
                            log.info("====================活动报名消费,{}",delayTask.getType());
                            //ParActivity activityApply = activityService.getById(delayTask.getId());
                            //activityService.pushWxAndMailToActivity(activityApply, PartyActivityPushMessageType.apply_activity_type.getCode());
                            break;
                        case 3:
                            log.info("====================活动开始消费,{}",delayTask.getType());
                            //ParActivity activityStart = activityService.getById(delayTask.getId());
                            //activityService.pushWxAndMailToActivity(activityStart,PartyActivityPushMessageType.activity_start_type.getCode());
                            break;
                        default:
                            log.info("====================未找到消费类型,{}",delayTask.getType());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

Spring Boot中使用RabbitMQ实现延迟队列,可以通过以下两种主要方法来完成: ### 1. 利用TTL(Time To Live)和死信队列(Dead Letter Exchange, DLX) 这种方法是通过设置消息的TTL属性和队列的TTL属性,结合死信队列的机制来实现延迟队列的效果。具体步骤如下: - **定义交换机和队列**:首先需要定义一个普通交换机和一个死信交换机,以及一个普通队列和一个死信队列。普通队列需要配置其TTL值,并且设置死信交换机和死信路由键。 - **发送消息**:向普通交换机发送消息,并设置消息的TTL值。 - **处理消息**:当消息在普通队列中的生存时间超过设定的TTL值后,会被自动转发到死信交换机,进而被投递到死信队列中,由监听死信队列的消费者进行处理。 示例代码片段展示了如何监听延迟队列和死信队列: ```java private static final String DEAD_QUEUE_NAME = "test_dead_ttl_springboot_queue"; @RabbitListener(queues = "test_delay_ttl_springboot_queue") public void delay(Message message, Channel channel) throws IOException { String msg = new String(message.getBody(), "UTF-8"); System.out.println("msg : " + msg); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } @RabbitListener(queues = DEAD_QUEUE_NAME) public void delay2(Message message, Channel channel) throws IOException { String msg = new String(message.getBody(), "UTF-8"); System.out.println("msg : " + msg); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } ``` ### 2. 使用RabbitMQ的延迟插件 从RabbitMQ 3.5.7版本开始,可以使用`rabbitmq-delayed-message-exchange`插件来实现延迟队列。这种方法提供了更灵活的配置选项,并且可能提供更高的时间精度和可靠性。具体步骤如下: - **安装插件**:首先需要下载并安装`rabbitmq-delayed-message-exchange`插件。 - **声明自定义交换机**:在应用程序中声明一个带有`x-delayed-type`参数的自定义交换机。 - **发送消息**:发送消息时设置`x-delay`属性来指定延迟时间。 示例代码片段展示了如何声明一个带有延迟特性的交换机: ```java @Bean public CustomExchange delayedExchange() { return new CustomExchange("delayed_exchange", "x-delayed-message", true, false); } @Bean public Queue delayedQueue() { return QueueBuilder.durable("delayed_queue").build(); } @Bean public Binding bindingDelayedExchange(@Qualifier("delayedQueue") Queue queue, @Qualifier("delayedExchange") CustomExchange exchange) { return BindingBuilder.bind(queue).to(exchange).with("delayed_key").noargs(); } ``` 在发送消息时,可以通过设置`x-delay`属性来指定延迟时间: ```java MessageProperties props = new MessageProperties(); props.setDelay(5000); // 设置延迟时间为5秒 channel.basicPublish("delayed_exchange", "delayed_key", props, "Hello World".getBytes()); ``` 这两种方法各有优缺点,选择哪种方法取决于具体的业务需求和技术环境。如果需要更高精度的延迟控制和更复杂的延迟逻辑,推荐使用RabbitMQ的延迟插件;而对于简单的延迟需求,使用TTL和死信队列的方法更为简便。[^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值