项目中五种延迟队列的实现及优缺点

项目中五种延迟队列的实现及优缺点

一、基于Java Concurrent包下DelayQueue实现

1.1、优缺点

1.2、实现步骤

1、实现Delayed接口 重写两方法getDelay和compareTo
2、创建资源 借助DelayQueue延迟队列 生产、消费1的实现类
3、分别实现生产者多线程、消费者多线程
4、进行测试验证

1.3、实现代码

PolicyOrder

import org.jetbrains.annotations.NotNull;
import java.io.Serializable;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * @author :
 * @date :Created in 16:53 2022/8/26
 * @description :电子保单
一、基于Java Concurrent包下DelayQueue实现
1、实现Delayed接口 重写两方法getDelay和compareTo
2、创建资源  借助DelayQueue延迟队列  生产、消费1的实现类
3、分别实现生产者多线程、消费者多线程
4、进行测试验证
二、基于Quartz实现
缺点:不够及时,具有周期性
三、基于Redis数据类型ZSet实现
1、需要考虑原子性操作 一般结合lua脚本语言操作
四、基于MQ(RabbitMQ)实现
缺点:难度大
五、基于Redission实现
 * @version: 1.0
 */
public class PolicyOrder implements Delayed, Serializable {

    private String enquiryId;

    private long delayTimeMillis;

    private long createTimeMillis;

    @Override
    public long getDelay(@NotNull TimeUnit unit) {
        return unit.convert(getEndTimeMillis() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(@NotNull Delayed delayed) {
        PolicyOrder policyOrder = (PolicyOrder) delayed;
        return (int) ((getEndTimeMillis() - policyOrder.getEndTimeMillis()));
    }

    public void setEnquiryId(String enquiryId) {
        this.enquiryId = enquiryId;
    }

    public void setDelayTimeMillis(long delayTimeMillis) {
        this.delayTimeMillis = delayTimeMillis;
    }

    public void setCreateTimeMillis(long createTimeMillis) {
        this.createTimeMillis = createTimeMillis;
    }

    public PolicyOrder() {
    }

    public PolicyOrder(String enquiryId, long delayTimeMillis) {
        this.enquiryId = enquiryId;
        this.delayTimeMillis = delayTimeMillis;
        this.createTimeMillis = System.currentTimeMillis();
    }

    public long getDelayTimeMillis() {
        return delayTimeMillis;
    }

    public long getCreateTimeMillis() {
        return createTimeMillis;
    }

    public long getEndTimeMillis(){
        return this.createTimeMillis + this.delayTimeMillis;
    }

}
PolicyOrderDelayResource
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.DelayQueue;

/**
 * @author :
 * @date :Created in 18:12 2022/8/26
 * @description :
 * @version: 1.0
 */
public class PolicyOrderDelayResource {

    private static Logger logger = LoggerFactory.getLogger(PolicyOrderDelayResource.class);

    private DelayQueue<PolicyOrder> delayQueue = new DelayQueue<>();

    /**
     * 生产者向延迟队列中添加资源
     */
    public void produce() {
        try {
            //TODO 获取真实的订单 获取一条待处理订单(此处是简化版的代码,实际项目中可据实获取待处理订单)
            PolicyOrder queueOrder = new PolicyOrder();
            // 随便设置一个到期时间(毫秒),实际项目中可据不同场景设置不同的到期时间
            queueOrder.setDelayTimeMillis(3000);
            delayQueue.put(queueOrder);
        } catch (Exception e) {
            logger.error("订单生产出现异常", e);
        }
    }

    /**
     * 将延迟队列中的订单移除并处理
     */
    public void consume() {
        try {
            PolicyOrder policyOrder = delayQueue.take();
            logger.debug("延迟队列消费者处理订单:[{}]", JSON.toJSONString(policyOrder));
            //TODO 去做延迟的逻辑
        } catch (Exception e) {
            logger.error("异常", e);
        }
    }

}

ConsumerDelayQueueThread

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author :
 * @date :Created in 18:18 2022/8/26
 * @description :
 * @version: 1.0
 */
public class ConsumerDelayQueueThread extends Thread {

    private Logger logger = LoggerFactory.getLogger(ConsumerDelayQueueThread.class);

    private PolicyOrderDelayResource resource;

    public ConsumerDelayQueueThread(PolicyOrderDelayResource resource) {
        this.resource = resource;
    }

    public void run() {
        logger.info("延迟队列消费者线程启动");
        while (true) {
            try {
                resource.consume();
            } catch (Exception e) {
                logger.error("延迟队列消费异常", e);
            }
        }
    }

}
ProducerDelayQueueThread
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author :
 * @date :Created in 18:17 2022/8/26
 * @description :
 * @version: 1.0
 */
public class ProducerDelayQueueThread extends Thread {

    private Logger logger = LoggerFactory.getLogger(ProducerDelayQueueThread.class);

    private PolicyOrderDelayResource resource;

    public ProducerDelayQueueThread(PolicyOrderDelayResource resource) {
        this.resource = resource;
    }

    public void run() {
        logger.info("延迟队列生产者线程启动");
        while (true) {
            try {
                resource.produce();
            } catch (Exception e) {
                logger.error("延迟队列生产异常", e);
            }
        }
    }
}
PolicyOrderTest
/**
 * @author :
 * @date :Created in 18:18 2022/8/26
 * @description : 电子保单 延迟队列 测试类
 * @version: 1.0
 */
public class PolicyOrderTest {

    public static void main(String[] args) {

        PolicyOrderDelayResource resource = new PolicyOrderDelayResource();
        // 创建一个生产者线程
        ProducerDelayQueueThread p = new ProducerDelayQueueThread(resource);
        // 目前只开启一个生产者线程和一个消费者线程,后续可以改成线程池
        ConsumerDelayQueueThread c = new ConsumerDelayQueueThread(resource);
        // start 生产者和消费者线程,开始工作
        p.start();
        c.start();

    }

}

二、基于Quartz实现

缺点:不够及时,具有周期性

优点:实现简单,基本每个项目都会有定时任务模块,集成快。

2.1、XML配置文件版

2.1.1、配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--自动扫包,后续注入jedis到任务调度类-->
    <context:component-scan base-package="com.yanan.health"/>
<!--clearImgJob-->
    <!--注册一个定义任务对象-->
    <bean id = "clearImgJob" class="com.yanan.health.jobs.ClearImageJob"/>
        <!-- 注册JobDetail,作用是负责通过反射调用指定的Job -->
    <bean id="clearImgJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <!--注入对象-->
        <property name="targetObject" ref="clearImgJob"/>
        <!--注入方法-->
        <property name="targetMethod" value="clearImageJob"/>
    </bean>
    <!--注册一个触发器,指定任务触发的时间(间隔)-->
    <bean id="ImgTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="clearImgJobDetail"/>
        <property name="cronExpression">
            <!-- 每隔10秒执行一次任务 0/10 * * * * ? -->
            <!-- 每隔2分钟执行一次任务  0 0/2 * * * ? -->
            <!-- 每天凌晨2点执行一次任务 0 0 2 * * ?  -->
            <!--<value>0 0 2 * * ?</value>-->
            <value>0/10 * * * * ?</value>
        </property>
    </bean>
<!-- clearOrdersettingJOb -->
    <!--注册一个定义任务对象-->
    <bean id="clearOrdersettingJob" class="com.yanan.health.jobs.ClearOrdersetting"/>
    <!--注册JobDetails,用来负责通过反射嗲用指定的job-->
    <bean id="clearOrdersettingJobDetails" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="clearOrdersettingJob"/>
        <property name="targetMethod" value="clearOrdersettingJob"/>
    </bean>
    <bean id="OrdersettingTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="clearOrdersettingJobDetails"/>
        <!--设置定时时间-->
        <property name="cronExpression">
            <!-- 每隔10秒执行一次任务 0/10 * * * * ? -->
            <!-- 每隔2分钟执行一次任务  0 0/2 * * * ? -->
            <!-- 每天凌晨2点执行一次任务 0 0 2 * * ?  -->
            <value>0/10 * * * * ?</value>
        </property>
    </bean>



    <!--注册一个统一调用工厂,通过这个调度工厂调度任务-->
    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="ImgTrigger"/>
                <ref bean="OrdersettingTrigger"/>
            </list>
        </property>
    </bean>
</beans>
2.1.2、定时任务实现类
/**
 * @author :
 * @date :Created in 2019/11/4
 * @description :定时清理图片
 * @version: 1.0
 */
@Slf4j
public class ClearImageJob {

    @Autowired
    private JedisPool jedisPool;

    public void clearImageJob(){
        log.info("[定时清理图片]开始...");
        try (Jedis jedis = jedisPool.getResource()) {
            //计算redis两个集合中的差值,得到一个集合 里面存放的是 前面有的后面没有的
            Set<String> set = jedis.sdiff(RedisConst.SETMEAL_PIC_RESOURCES, RedisConst.SETMEAL_PIC_DB_RESOURCES);
            //遍历set
            for (String img : set) {
                log.info("[清理垃圾图片]target:{}", img);
                //从七牛云删除
                QiniuUtils.deleteFileFromQiniu(img);
                //从redis中删除
                jedis.srem(RedisConst.SETMEAL_PIC_RESOURCES, img);
            }
        } catch (QiniuException e) {
            log.error("[清理垃圾图片异常]",e);
        }finally {
            log.info("[清理垃圾图片]完成...");
        }

    }
}

2.2、Springboot配置文件版

2.2.1、quartz配置类
/**
 * @author :
 * @date :Created in 18:49 2020/8/12
 * @description :
 * @version: 1.0
 */
@Configuration
public class QuartzConfig {


    /**
     * 1.创建Job对象
     */
    @Bean
    public JobDetailFactoryBean startOfDayJobDetailFactoryBean() {
        JobDetailFactoryBean factory = new JobDetailFactoryBean();
        //关联我们自己的Job类
        factory.setJobClass(StartOfDayJob.class);
        return factory;
    }

    /**
     * 2.创建Trigger对象
     * 简单的Trigger
     */
    /**
     * Cron Trigger
     */
    @Bean
    public CronTriggerFactoryBean startOfDayCronTriggerFactoryBean() {
        CronTriggerFactoryBean factory = new CronTriggerFactoryBean();
        factory.setJobDetail(startOfDayJobDetailFactoryBean().getObject());
        //这里涉及到Cron表达式 可以去看我写的Cron表达式!!! 0/10 * * * * ?此处代表每10秒钟 调用一次0 15 10 15 * ?
        //0/10 * * * * ?
        //0 15 10 15 * ?
        //0 15 10 ? * MON-FRI
//        factory.setCronExpression("0 15 10 ? * MON-FRI");
        factory.setCronExpression("0 0 */1 * * ?");
        return factory;
    }
    /**
     * 3.创建Scheduler对象
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(MyAdaptableJobFactory myAdaptableJobFactory) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        //关联trigger
        factory.setTriggers(startOfDayCronTriggerFactoryBean().getObject());
        factory.setJobFactory(myAdaptableJobFactory);
        return factory;
    }
}

/**
 * @author :
 * @date :Created in 19:18 2020/8/12
 * @description :
 * @version: 1.0
 */
@Component
public class MyAdaptableJobFactory extends AdaptableJobFactory {

    //AutowireCapableBeanFactory 可以将一个对象添加到SpringIOC容器中,并且完成该对象注入
    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;

    /**
     * 该方法需要将实例化的任务对象手动的添加到springIOC容器中并且完成对象的注入
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object obj = super.createJobInstance(bundle);
        //将obj对象添加Spring IOC容器中,并完成注入
        this.autowireCapableBeanFactory.autowireBean(obj);
        return obj;
    }

}
2.2.2、定时任务实现类
/**
 * @author 
 * @date :Created in 18:54 2020/8/12
 * @description :
 * @version: 1.0
 */
@Component
public class StartOfDayJob extends QuartzJobBean {

    @Autowired
    private ProductDao productDao;
    @Autowired
    private OrderDao orderDao;


    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // 任务的具体逻辑
        /*System.out.println("1");*/
        String startTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        System.out.println(startTime + ",定时任务开始执行");
        List<Product> productBySku = productDao.findProductBySku(Global.SKU_INSUFFICIENT);
        for (Product product : productBySku) {
            System.out.println(product.toString());
        }
        Jedis jedis = null;
        try {
            jedis = JedisUtils.getJedis();
            String jsonCategories = new Gson().toJson(productBySku);
            jedis.set("webShop_productSkuInsufficient", jsonCategories);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }



        /*String nowDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        List<Orders> orderAndBeforeDate = orderDao.findOrderAndBeforeDate(nowDate);
        for (Orders orders : orderAndBeforeDate) {

        }*/

        String endTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        System.out.println(endTime + ",定时任务执行结束");
    }
}

三、基于Redis数据类型ZSet实现

1、需要考虑原子性操作 一般结合lua脚本语言操作

3.1、优缺点

3.2、实现代码

import groovy.util.logging.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Set;
import java.util.concurrent.Executors;

/**
 * @author :YangYaNan
 * @date :Created in 16:43 2022/8/30
 * @description :
 * @version: 1.0
 */
@Slf4j
@Service
public class RedisDelayQueueUtil {

    private static final Logger log = org.slf4j.LoggerFactory.getLogger(RedisDelayQueueUtil.class);
    private static final long delayTime = 30000;

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void productionQueue(String ruleId){
        long expireTime = System.currentTimeMillis() + delayTime;
        log.info("延迟队列添加数据ruleId:{},expireTime{}", ruleId, expireTime);
        redisTemplate.opsForZSet().add(RedisConstants.BELAY_QUEUE_KEY, ruleId, expireTime);
    }

    @PostConstruct
    public void consumeRuleIdQueue() {
        log.info("订单延时队列扫描已启动.....");
        Executors.newSingleThreadExecutor().execute(() -> {
            while (true) {
                //每次最多取出一个
                Set<String> ruleIds = redisTemplate.opsForZSet().rangeByScore(RedisConstants.BELAY_QUEUE_KEY, 0, System.currentTimeMillis(),0,1);
                // 如果没有需要消费的消息,则间隔一段时间再扫描
                if (CollectionUtils.isEmpty(ruleIds)) {
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                for (String ruleId : ruleIds) {
                    //如果抢到了就删除
                    boolean deleteFlag = redisTemplate.opsForZSet().remove(RedisConstants.BELAY_QUEUE_KEY, ruleId) > 0;
                    if (deleteFlag) {
                        // TODO 消费规则信息
                        log.info("订单延时消息已成功消费,ruleId:{},currentTimeMillis:{}", ruleId, System.currentTimeMillis());
                    }
                }
            }
        });

    }

}

四、基于MQ(RabbitMQ)实现

优点: 高效,可以利用rabbitmq的分布式特性轻易的进行横向扩展,消息支持持久化增加了可靠性。

缺点:难度大

4.1、基于RabbitMQ延时插件实现

4.1.1、实现步骤
  1. 导包
  2. 配置yml文件
  3. 配置交换机等信息
  4. 创建消息队列枚举类、常量类
  5. 创建发送者、监听者
  6. 验证
  7. 扩展关于spring.factories
4.1.2、代码实现
4.1.2.1、导包
'org.springframework:spring-web',
'org.springframework.boot:spring-boot-starter-amqp'
4.1.2.2、配置yml文件
spring:
  rabbitmq:
    host: ******
    port: 5672
    virtual-host: ******
    username: ******
    password: ******
    uid: ******
    #如果对异步消息需要回调必须设置为true
    publisher-confirms: false
    listener:
      simple:
        acknowledge-mode: auto  # 消息确认方式,其有三种配置方式,分别是none、manual(手动ack) 和auto(自动ack) 默认auto
        retry:
          enabled: true
          max-attempts: 3
          max-interval: 600000   # 重试最大间隔时间10分钟
          initial-interval: 600000  # 重试初始间隔时间10分钟
          multiplier: 1
        default-requeue-rejected: false
4.1.2.3、配置交换机等信息
/**
 * RabbitMQ Direct交换机配置类
 */
@Configuration
public class MQDirectConfig {

    @Autowired
    private RabbitAdmin rabbitAdmin;

    /**
     * 创建初始化RabbitAdmin对象
     *
     * @param connectionFactory connectionFactory
     * @return rabbitAdmin
     */
    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }

    /**
     * 默认交换机
     *
     * @return DirectExchange
     */
    @Bean
    public DirectExchange defaultExchange() {
        return new DirectExchange(DIRECT_DEFAULT, true, false);
    }

    /**
     * 延时交换机
     *
     * @return CustomExchange
     */
    @Bean
    public CustomExchange delayedExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange(DIRECT_DELAYED, "x-delayed-message", true, false, args);
    }

    /**
     * 读取创建交换机和对列
     */
    @PostConstruct
    public void declareQueueByConfig() {
        for (MessageQueueEnum mqBinding : MessageQueueEnum.values()) {
            Queue queue = new Queue(mqBinding.getQueueName(), true);
            rabbitAdmin.declareQueue(queue);
            rabbitAdmin.declareBinding(
                    BindingBuilder.bind(queue)
                            .to(mqBinding.isDelayed() ? delayedExchange() : defaultExchange())
                            .with(mqBinding.getRoutingKey())
                            .noargs()
            );
        }
    }
}
4.1.2.4、创建消息队列枚举类、常量类
/**
 * 消息队列枚举
 */
public enum MessageQueueEnum {

    NANMU_USER(QUEUE_NAME_NANMU_USER, ROUTING_KEY_NANMU_USER, true, 5 * 60 * 1000);

    // 队列名称
    private final String queueName;
    // 绑定key
    private final String routingKey;
    // 是否为延时队列
    private final boolean delayed;
    // 延时时长(单位:毫秒)
    private final long delayTime;

    MessageQueueEnum(String queueName, String routingKey) {
        this.queueName = queueName;
        this.routingKey = routingKey;
        this.delayed = false;
        this.delayTime = 0;
    }

    MessageQueueEnum(String queueName, String routingKey, boolean delayed, long delayTime) {
        this.queueName = queueName;
        this.routingKey = routingKey;
        this.delayed = delayed;
        this.delayTime = delayTime;
    }

    public String getQueueName() {
        return queueName;
    }

    public String getRoutingKey() {
        return routingKey;
    }

    public boolean isDelayed() {
        return delayed;
    }

    public long getDelayTime() {
        return delayTime;
    }

}



public class MQConstants {

    /**
     * 交换器
     */
    public static final String DIRECT_DEFAULT = "direct.default";
    public static final String DIRECT_DELAYED = "direct.delayed";

    /**
     * 队列名称
     */
    public static final String QUEUE_NAME_NANMU_USER = "message.queue.nanmu.user";

    /**
     * routingKey
     */
    public static final String ROUTING_KEY_NANMU_USER = "message.routing.nanmu.user";

}
4.1.2.5、创建发送者、监听者
/**
 * 消费发送服务
 */
@Slf4j
@Service
@AllArgsConstructor
public class MessageSender {

    private RabbitTemplate rabbitTemplate;

    /**
     * 发送保单下载消息
     */
    public void sendNanMuUserMsg(String userId) {
        sendMessage(NANMU_USER, userId);
    }

    /**
     * 向队列发送消息
     */
    public void sendMessage(MessageQueueEnum queue, String message) {
        log.info("MessageSender, sendMessage start, queue:{}, message:{}", queue, message);
        try {
            rabbitTemplate.convertAndSend(queue.isDelayed() ? DIRECT_DELAYED : DIRECT_DEFAULT,
                    queue.getRoutingKey(),
                    message,
                    msg -> {
                        if (queue.isDelayed()) {
                            msg.getMessageProperties().setHeader("x-delay", queue.getDelayTime());
                        }
                        return msg;
                    });
        } catch (Exception e) {
            log.error("MessageSender, sendMessage fail, queue:{}, message:{}", queue, message);
        }
    }

}


@Slf4j
@Service
@AllArgsConstructor
public class MessageListener {

    private final IUserService userService;

    @RabbitHandler
    @RabbitListener(queues = QUEUE_NAME_NANMU_USER)
    public void userUpdate(String orderNo) {
        log.info("MessageListener userUpdate, received message :{}", orderNo);
        userService.userUpdate(orderNo);
    }

}

五、基于Redission实现方案

5.1、优缺点

优点:集成简单,实现了集群环境下延迟队列。

缺点:随着业务规模的发展,越来越多的延时操作会导致线程数目不断增加,这里可能会有性能影响。另一方面如果某个时间点的数据突增,可能会产生一种情况,实际发送消息的时间比定好的延迟时间会更加久。

5.2、实现代码

/**
 * @param <T>
 * @author 
 */
public interface IDelayQueueService<T> {

    /**
     * 将消息推入延迟队列,延迟时间以秒为单位
     *
     * @param data
     * @param time
     * @param queueName
     */
    void offerSeconds(T data, long time, String queueName);

    /**
     * 将消息推入延迟队列
     *
     * @param data
     * @param time
     * @param timeUnit
     * @param queueName
     */
    void offer(T data, long time, TimeUnit timeUnit, String queueName);

    /**
     * 取出队列中数据
     *
     * @param queueName
     * @return
     * @throws InterruptedException
     */
    T take(String queueName) throws InterruptedException;

    /**
     * 查找阻塞队列
     *
     * @param queueName
     * @return
     */
    RBlockingDeque<T> findBlockingQueue(String queueName);

    /**
     * 查找延时队列
     *
     * @param queueName
     * @return
     */
    RDelayedQueue<T> findDelayQueue(String queueName);

    /**
     * 查找延时队列
     *
     * @param blockingQueue
     * @return
     */
    RDelayedQueue<T> findDelayQueue(RBlockingDeque<T> blockingQueue);

    /**
     * 队列中是否存在这个任务
     *
     * @param queueName 队列名
     * @param t         任务
     * @return
     */
    boolean contains(String queueName, T t);

    /**
     * 清除队列中指定元素
     *
     * @param queueName
     * @param t
     * @return
     */
    boolean remove(String queueName, T t);

}



/**
 * @param <T>
 * @author 
 */
@Service
@AllArgsConstructor
public class DelayQueueServiceImpl<T> implements IDelayQueueService<T> {

    private final RedissonClient redissonClient;

    @Override
    public void offerSeconds(T data, long time, String queueName) {
        offer(data, time, TimeUnit.SECONDS, queueName);
    }

    @Override
    public void offer(T data, long time, TimeUnit timeUnit, String queueName) {
        findDelayQueue(queueName).offerAsync(data, time < 0 ? 0 : time, timeUnit);
    }

    @Override
    public T take(String queueName) throws InterruptedException {
        RBlockingDeque<T> blockingQueue = findBlockingQueue(queueName);
        RDelayedQueue<T> delayQueue = findDelayQueue(blockingQueue);
        return blockingQueue.take();
    }

    @Override
    public RBlockingDeque<T> findBlockingQueue(String queueName) {
        return redissonClient.getBlockingDeque(queueName);
    }

    @Override
    public RDelayedQueue<T> findDelayQueue(String queueName) {
        return redissonClient.getDelayedQueue(findBlockingQueue(queueName));
    }

    @Override
    public RDelayedQueue<T> findDelayQueue(RBlockingDeque<T> blockingQueue) {
        return redissonClient.getDelayedQueue(blockingQueue);
    }

    @Override
    public boolean contains(String queueName, T t) {
        return findDelayQueue(queueName).contains(t);
    }

    @Override
    public boolean remove(String queueName, T t) {
        return findDelayQueue(queueName).remove(t);
    }

}
e) throws InterruptedException {
        RBlockingDeque<T> blockingQueue = findBlockingQueue(queueName);
        RDelayedQueue<T> delayQueue = findDelayQueue(blockingQueue);
        return blockingQueue.take();
    }

    @Override
    public RBlockingDeque<T> findBlockingQueue(String queueName) {
        return redissonClient.getBlockingDeque(queueName);
    }

    @Override
    public RDelayedQueue<T> findDelayQueue(String queueName) {
        return redissonClient.getDelayedQueue(findBlockingQueue(queueName));
    }

    @Override
    public RDelayedQueue<T> findDelayQueue(RBlockingDeque<T> blockingQueue) {
        return redissonClient.getDelayedQueue(blockingQueue);
    }

    @Override
    public boolean contains(String queueName, T t) {
        return findDelayQueue(queueName).contains(t);
    }

    @Override
    public boolean remove(String queueName, T t) {
        return findDelayQueue(queueName).remove(t);
    }

}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Redis延迟队列可以通过利用Redis的zset和list数据结构来实现。下面是一个简单的实现方式: 1. 创建一个JobPool,用来存放所有Job的元信息。可以使用Hash数据结构来存储每个Job的详细信息,例如Job的ID、执行时间、参数等。 2. 创建一组以时间为维度的DelayBucket,用来存放所有需要延迟的Job。可以使用zset数据结构来存储Job的ID,并将Job的执行时间作为分数,以便按照时间顺序进行排序。 3. 创建一个Timer,负责实时扫描各个Bucket,并将延迟时间到达的Job从DelayBucket取出,放入ReadyQueue等待执行。 4. 创建一个ReadyQueue,用来存放已经到达执行时间的Job。可以使用list数据结构来存储Job的ID,按照先进先出的顺序进行执行。 5. 当需要添加一个延迟Job时,将Job的元信息存入JobPool,并将Job的ID添加到对应的DelayBucket。 6. Timer定时扫描各个Bucket,将延迟时间到达的Job从DelayBucket取出,放入ReadyQueue。 7. 从ReadyQueue取出Job的ID,根据Job的ID从JobPool获取Job的详细信息,并执行相应的操作。 下面是一个简单的Python代码示例,演示了如何使用Redis实现延迟队列: ```python import redis import time # 连接Redis r = redis.Redis(host='localhost', port=6379, db=0) # 添加延迟Job def add_delayed_job(job_id, execute_time): # 将Job的元信息存入JobPool r.hset('JobPool', job_id, execute_time) # 将Job的ID添加到对应的DelayBucket r.zadd('DelayBucket', {job_id: execute_time}) # Timer定时扫描Bucket def timer_scan(): while True: # 获取当前时间 current_time = int(time.time()) # 扫描DelayBucket,将延迟时间到达的Job放入ReadyQueue r.zrangebyscore('DelayBucket', 0, current_time, start=0, num=10).pipe( lambda x: x if x else None, lambda x: r.lpush('ReadyQueue', *x), lambda x: r.zremrangebyscore('DelayBucket', 0, current_time) ).execute() # 休眠1秒 time.sleep(1) # 从ReadyQueue取出Job并执行 def process_ready_queue(): while True: # 从ReadyQueue取出Job的ID job_id = r.brpop('ReadyQueue')[1] # 根据Job的ID从JobPool获取Job的详细信息 job_info = r.hget('JobPool', job_id) # 执行相应的操作 print(f"Executing Job: {job_id}, Info: {job_info}") # 启动Timer和处理ReadyQueue的线程 timer_thread = threading.Thread(target=timer_scan) process_thread = threading.Thread(target=process_ready_queue) timer_thread.start() process_thread.start() ``` 请注意,上述代码只是一个简单的示例,实际的实现可能需要根据具体需求进行调整和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白de成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值