延时队列原理及其实现

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

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

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

  • 京东、美团和淘宝下单以后,若当时没有付款,此时会默认存在一个倒计时,一般是 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 实现延迟队列
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值