当前位置: 首页 > Web前端 > JavaScript

延时任务——基于netty时间轮算法实现

时间:2023-03-26 20:10:25 JavaScript

一、时间轮算法简介为了让大家看懂下面的代码,我们先简单了解一下netty时间轮算法的核心原理时间轮算法名副其实,时间轮是一个圆形的数据结构,类似于表盘,将时间轮分成多个桶(例如:0-8)。假设tickDuration=1s为每个时间轮片的分离周期(即指针经过每个格子需要1s),当前时间bucket=3,则18之后需要执行的任务seconds需要在5号桶上落入((3+18)%8=5取余运算)。如果这个时间段内有多个任务需要执行,就会形成一个双向链表。另外,我们对时间轮要有如下认知:时间轮指针是一个Worker线程,当时间轮指向整点时,会执行双向链表中的任务。时间轮算法并不是精确的延迟。其执行精度取决于每个时间轮片的分离时间段。所以我们的延时任务必须做成异步任务,否则会影响时间轮后续任务的执行时间。2.时间轮hello-world是实现延迟任务的例子。要求还是很简单:你买了一张火车票,必须在30分钟内付款,否则订单会自动取消。30分钟内未付款,订单将自动取消。此任务是延迟任务。我们的火车票订单取消任务从需求的角度来说不需要非常精确的延迟,所以我们可以使用时间轮算法来完成这个任务。首先通过maven坐标引入nettyio.nettynetty-all4.1.45.Final然后我们创建一个时间圆,如果是Spring开发环境,我们可以这样做。下面我们新建了一个有512个桶的时间轮,每个时间轮的时间间隔为100毫秒。@Bean("hashedWheelTimer")publicHashedWheelTimerhashedWheelTimer(){returnnewHashedWheelTimer(100,TimeUnit.MILLISECONDS,512);}例子:用户购买火车票下单时,在时间上增加30分钟的延迟轮任务。延迟任务将在30分钟后执行。下面的lambda表达式实现了一个TimerTask(task)延迟任务。在这个延迟任务的函数体中,请使用异步任务,即单独开启一个线程或者使用SpringBoot异步任务线程池。因为Worker线程是单线程的,你的任务处理时间超过tickDuration会阻碍后续时间轮切片上任务的执行。//订单订单操作voidorder(StringorderInfo){//下单时,在时间轮中加入一个30分钟延时的任务hashedWheelTimer.newTimeout(task->{//注意异步任务线程池或者启动threadtoprocesstheordercancellationtaskcancelOrder(orderInfo);},30,TimeUnit.MINUTES);}3.异步任务线程池上面我们已经多次强调,时间轮任务TimerTask的执行内容应该是异步的。最简单的方法是在收到一个任务后,启动一个线程来处理这个任务。在Spring环境下,我们其实还有一个更好的选择,就是使用Spring的线程池,可以自定义。比如:下面的用法是我预先定义了一个名为test的线程池,然后通过@Async来使用。@Async("test")publicvoidcancelOrder(StringorderInfo){//查询订单支付信息,如果用户没有支付,则关闭订单}4.时间轮的优缺点JDK的DelayQueue在算法上有优势,并且它的执行性能相对较好。缺点是所有延迟任务和延迟触发管理都在单个应用服务的内存中进行。一旦应用服务失败并重启服务,所有时间轮任务数据都会丢失。这个缺点和DelayQueue是一样的。为了解决这个问题,我们可以使用redis、RocketMQ等分布式中间件来管理延迟任务的消息来实现延迟任务,我会在后续的文章中给大家介绍。对文章内容感兴趣的朋友可以微信搜索公众号:打码的老贾,接收相应信息