java 轮训设计_延迟队列设计

4321ea8cc788454da110fa1dc7d28c06.png

背景:

当订单⼀一直处于未⽀支付状态时,如何及时的关闭订单,并退还库存?
定时的爆款商品自动上下架、广告的定时更新如何做到?

注:文中架构设计以及实际开发为我司野菱大哥所设计,现入职网易考拉。本文不过是拾人牙慧,将其设计以文字的形式呈现出来而已。

目标:

可靠性:消息进⼊入到延迟队列列后, 保证⾄至少被消费⼀一次。

高可⽤用性:至少得⽀支持多实例例部署。挂掉一 个实例例后,还有后备实例例继续提供服务。

实时性:允许存在⼀一定的时间误 差,希望在秒级。

支持消息删除:业务使⽤用⽅方,可 以随时删除指定消息。

设计方案对比

1. 轮训+DB

原理:定时任务扫描扫描db。 优点:实现简单,保证了可靠性,高可用性,实时性。 缺点:很难实现到细粒度,否则数据库压力比较大,程序CPU很大浪费。

2. RocketMQ

原理:mq自带延迟消费。 优点:实现简单,保证了可靠性,高可用性,实时性,不需要轮训任务,MQ自带实现。 缺点:比较死的粒度,18个level,最大2小时。

3. Redis键过期通知

原理:Redis通过订阅过期的消息,做任务监听。 优点:高可用性,实时性。 缺点:大量键同一时间过期,对redis来说负载大;消息只会发送一次,没有确认机制,不能保证可靠性。

4. 有赞的延时消息:Redis(zset)+轮训

原理:Redis-Zset,无限循环扫描任务Bucket。 优点:高可用性,实时性,持久性。 缺点:独立线程的无限循环,CPU的浪费;如果单点任务多,是否会影响后面其他点的任务准时性;针对超长时间(30天以上)的延迟任务,如果继续用redis,想必是很浪费的。

消息结构

每个Job必须包含一下几个属性:

  • Topic:消息的Topic
  • Id:Job的唯一标识。用来检索和删除指定的Job信息。
  • Delay:Job需要延迟的时间。单位:秒。(服务端会将其转换为绝对时间)
  • tag(消息 标签
  • msgJson:的内容,供消费者做具体的业务处理,以json格式存储。

具体结构如下图表示:

e3943f26ccfecbf05332a04cc5f0f6d4.png

消息状态转换

每个Job只会处于某一个状态下:

  • ready:可执行状态,等待消费。
  • delay:不可执行状态,等待时钟周期。
  • reserved:已被消费者读取,但还未得到消费者的响应(delete、finish)。
  • deleted:已被消费完成或者已被删除。

容错机制:

当前节点挂了如何确保任务依然能够无误的执行。

  • 采用zookeeper做任务节点集群,当前节点的任务放入redis缓存,key采用『ip:pid』的设计,保证在节点挂掉的情况,有其他节点接手任务,保证了可靠性和可用性。
  • 极端情况下,可能多发,需要业务方,保证幂等性。
  • 优化:10分钟的业务任务,放入redis缓存List。

附录:

1:JDK Timer(使用案例如HashedWheelTime)

java.util.Timer是一个单线程的定时器,定时调度所拥有的TimerTask任务,TimerTask类是一个定时任务类,实现了Runnable接口,并且是一个抽象类,需要定时执行的任务都需要重写他的run方法

2458d9dcbb5b39410421078065ad4eeb.png

TaskQueue是一个由平衡二叉树堆实现的优先级队列,每个Timer对象内部都有一个TaskQueue队列,用户线程调用Timer的Schedule方法就是把TimerTask任务添加到TaskQueue队列,在调用schedule方法时,long delay参数用来指明该任务延迟多少时间执行。

TimerThread是具体执行任务的线程,他从TaskQueue队列里面获取优先级最高的任务进行执行,只有执行了当前的任务才会从队列里获取下一个任务,而不管队列是否有任务已经达到了设置的delay时间,一个Timer只有一个TimerThrea线程,因此内部实现为多生产者单消费者模型

1:构建一个定时器

public Timer() {
        this("Timer-" + serialNumber());
    }

    /**
     * Creates a new timer whose associated thread may be specified to
     * {@linkplain Thread#setDaemon run as a daemon}.
     * A daemon thread is called for if the timer will be used to
     * schedule repeating "maintenance activities", which must be
     * performed as long as the application is running, but should not
     * prolong the lifetime of the application.
     *
     * @param isDaemon true if the associated thread should run as a daemon.
     */
    public Timer(boolean isDaemon) {
        this("Timer-" + serialNumber(), isDaemon);
    }

    /**
     * Creates a new timer whose 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值