延时队列原理及其实现

本文介绍了如何使用Java的DelayQueue和Redis实现延迟队列,用于在特定时间后执行任务。Java实现基于PriorityQueue,但因JVM状态可能导致数据丢失,不保证高可用。Redis方案则提供了一种更可靠的解决方案,但需要考虑数据备份。文中还讨论了如何通过数据库备份来增强延迟队列的容灾能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

延迟队列实现及其容灾方案

兄弟们好,已经很久没有更新文章了呀,国庆结束以后会逐渐恢复更新的。今天我们来聊一下延迟队列。

在日常的业务开发中,相信大家都遇到过需要让一些任延迟执行的场景,比如与我们息息相关的:

  • 京东、美团和淘宝下单以后,若当时没有付款,此时会默认存在一个倒计时,一般是 30 分钟,如果在该时间内你没有付款,则订单取消。

可以发现这个需求本质上就是需要在创建订单以后的 30 分钟以后,释放商品库存等等操作。技术方案就是:实现可以在一定时间以后执行任务的功能。

今天我们要和兄弟们聊的就是可以实现这个功能的一个工具。来看一下今天的大纲

1. 主流实现

延迟队列,延迟一定的时间以后去执行一个任务。大白话来讲:我和一个女孩子表白,如果表白成功,我会带她去吃饭,看电影;如果表白失败,我不会带她去吃饭,看电影。当我表白的以后,我会给他 1 分钟的考虑时间,即在 1 分钟以后,我会根据她的选择,执行不同的任务。

好了,聊完了爱情,我们开始聊技术。(技术不会骗我,爱情会,兄弟们冲)

接下来我们来看目前几种常用的延迟队列的实现方式

1.1 Java 延时队列实现

java.util.current 包下提供了很多的并发类,其中就包括很多的同步队列,包括一个延时队列 DelayQueue

我们来看一下它的具体用法:(案例在 SpringBoot 环境下运行)

  • 实体类,实现了 Delayed 接口,存放了一个 time 属性
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OaTask implements Delayed {

    private String name;
    private Long time;


    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(time - now(), TimeUnit.MILLISECONDS);
    }

    private Long now() {
        return Instant.now().toEpochMilli();
    }


    /**
     * 根据剩余时间进行排序
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        long d = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        return (d == 0) ? 0 : ((d > 0) ? 1 : -1);
    }

}
  • 延时队列接口
public interface DelayQueueService<E> {

    /**
     * 向延迟队列中添加任务
     * @param e
     */
    void add(E e);

    /**
     * 消费任务
     */
    void consume(Consumer<E> consumer);

    /**
     * 移除任务
     */
    void remove(E e);

    /**
     * 停止延迟队列任务消费
     */
    void stop();
    
}
  • 具体实现类
@Component
public class DelayQueueJavaImpl<OaTask extends Delayed> implements DelayQueueService<OaTask> {

    @Resource
    private DelayQueue<OaTask> delayQueue;

    private AtomicBoolean flag = new AtomicBoolean(true);

    @Override
    public void add(OaTask e) {
        delayQueue.add(e);
    }

    @Override
    @SneakyThrows
    public void consume(Consumer<OaTask> consumer) {
       while (flag.get()) {
           // 获取不到任务的时候直接阻塞
           OaTask oaTask = delayQueue.take();

           consumer.accept(oaTask);
       }
    }

    @Override
    public void remove(OaTask oaTask) {
        delayQueue.remove(oaTask);
    }

    @Override
    public void stop() {
        flag.set(false);
    }
    
}
  • 测试用例
@Component
public class DelayQueueJavaTest {

    @Autowired
    private DelayQueueService<OaTask> queueService;

    @PostConstruct
    public void init() {
        for (int i = 0; i < 10; i++) {
            queueService.add(OaTask.builder()
                    .name("测试"+i+":")
                    .time(Instant.now().toEpochMilli()+ (i*2*1000))
                    .build());

        }
    }

}

简单来解释一下这个案例的原理: Java 对于延迟队列的实现基于 PriorityQueue 优先级队列,将 Delayed 中的 time 属性作为优先级存储在 PriorityQueue 队列中,实现延迟队列的作用。因此我们来使用的时候,需要重写实体类的compareTo 方法。

这种方式可以解决一些简单的业务问题,而且它很轻量,基于 JVM 实现,也正是因为基于 JVM 实现因此它存在一个问题, JVM 有状态。当 JVM 因为内存泄露或者溢出等情况导致程序挂掉,此时内部的数据也出现了丢失,因此给予 JVM 实现的延时队列无法保证数据的高可用。

解决方法不外乎于增加一个备份机制。如数据库。

1.2 基于 Redis 实现延迟队列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值