关于定时器:定时任务原理方案综述-京东云技术团队

本文次要介绍目前存在的定时工作解决解决方案。业务零碎中存在泛滥的工作须要定时或定期执行,并且针对不同的零碎架构也须要提供不同的解决方案。京东外部也提供了泛滥定时工作中间件来反对,总结以后各种定时工作原理,从定时工作根底原理、单机定时工作(单线程、多线程)、分布式定时工作介绍目前支流的定时工作的基本原理组成、优缺点等。心愿能帮忙读者深刻了解定时工作具体的算法和实现计划。 一、背景概述定时工作,顾名思义,就是指定工夫点进行执行相应的工作。业务场景中包含: 每天晚上12点,将当日的销售数据发送给各个VP;订单下单十分钟未付款将主动勾销订单;用户下单后发短信;定时的清理零碎中生效的数据;心跳检测、session、申请是否timeout。二、定时工作根底原理2.1 小顶堆算法每个节点是对应的定时工作,定时工作的执行程序通过利用堆化进行排序,循环判断每秒是否堆顶的工作是否应该执行,每次插入工作、删除工作须要从新堆化; 图1 利用小顶堆来获取须要最新执行的工作 为什么用优先队列(小顶堆)而不是有序的数组或者链表? 因为优先队列只须要确保部分有序,它的插入、删除操作的复杂度都是O(log n);而有序数组的插入和删除复杂度为O(n);链表的插入复杂度为O(n),删除复杂度为O(1)。总体而言优先队列性能最好。 2.2 工夫轮算法链表或者数组实现工夫轮: 图2 利用链表+数组实现工夫轮算法 round工夫轮: 工夫轮其实就是一种环型的数据结构,能够把它设想成一个时钟,分成了许多格子,每个格子代表肯定的工夫,在这个格子上用一个链表来保留要执行的超时工作,同时有一个指针一格一格的走,走到那个格子时就执行格子对应的提早工作。 图3 环形数据结构的round工夫轮 2.3 分层工夫轮就是将月、周、天分成不同的工夫轮层级,各自的工夫轮进行定义: 图4 按工夫维度分层的工夫轮 三、单机定时工作3.1 单线程任务调度3.1.1 有限循环创立thread,在while中始终执行,通过sleep来达到定时工作的成果。 3.1.2 JDK提供了TimerTimer位于java.util包下,其外部蕴含且仅蕴含一个后盾线程(TimeThread)对多个业务工作(TimeTask)进行定时定频率的调度。 图5 JDK中Timer反对的调度办法 每个Timer中蕴含一个TaskQueue对象,这个队列存储了所有将被调度的task, 该队列是一个依据task下一次运行工夫排序造成的最小优先队列,该最小优先队列的是一个二叉堆,所以能够在log(n)的工夫内实现减少task,删除task等操作,并且能够在常数工夫内取得下次运行工夫最小的task对象。 原理: TimerTask是按nextExecutionTime进行堆排序的。每次取堆中nextExecutionTime和以后零碎工夫进行比拟,如果以后工夫大于nextExecutionTime则执行,如果是单次工作,会将工作从最小堆,移除。否则,更新nextExecutionTime的值。 图6 TimerTask中依照工夫的堆排序 工作追赶个性: schedule在执行的时候,如果Date过了,也就是Date是小于当初工夫,那么会立刻执行一次,而后从这个执行工夫开始每隔间隔时间执行一次; scheduleAtFixedRate在执行的时候,如果Date过了。还会执行,而后才是每隔一段时间执行。 Timer问题: 工作执行工夫长影响其余工作:如果TimerTask抛出未查看的异样,Timer将会产生无奈意料的行为。Timer线程并不捕捉异样,所以 TimerTask抛出的未查看的异样会终止timer线程。此时,曾经被安顿但尚未执行的TimerTask永远不会再执行了,新的工作也不能被调度了。工作异样影响其余工作:Timer外面的工作如果执行工夫太长,会独占Timer对象,使得前面的工作无奈几时的执行 ,ScheduledExecutorService不会呈现Timer的问题(除非你只搞一个单线程池的任务区)。3.1.3 DelayQueue DelayQueue 是一个反对延时获取元素的无界阻塞队列,DelayQueue 其实就是在每次往优先级队列中增加元素,而后以元素的delay过期值作为排序的因素,以此来达到先过期的元素会拍在队首,每次从队列里取出来都是最先要过期的元素。 delayed是一个具备过期工夫的元素PriorityQueue是一个依据队列里元素某些属性排列先后的程序队列(外围还是基于小顶堆)队列中的元素必须实现 Delayed 接口,并重写 getDelay(TimeUnit) 和 compareTo(Delayed) 办法。 CompareTo(Delayed o):Delayed接口继承了Comparable接口,因而有了这个办法。getDelay(TimeUnit unit):这个办法返回到激活日期的剩余时间,工夫单位由单位参数指定。队列入队出队办法: offer():入队的逻辑综合了PriorityBlockingQueue的均衡二叉堆冒泡插入以及DelayQueue的生产线程唤醒与leader领导权剥夺take():出队的逻辑一样综合了PriorityBlockingQueue的均衡二叉堆向下降级以及DelayQueue的Leader-Follower线程期待唤醒模式在ScheduledExecutorService中推出了DelayedWorkQueue,DelayQueue队列元素必须是实现了Delayed接口的实例,而DelayedWorkQueue寄存的是线程运行时代码RunnableScheduledFuture,该延时队列灵便的退出定时工作特有的办法调用。 图7 定时工作中的延时队列类图 leader follower模式: 所有线程会有三种身份中的一种:leader和follower,以及一个工作中的状态:proccesser。它的根本准则就是,永远最多只有一个leader。而所有follower都在期待成为leader。线程池启动时会主动产生一个Leader负责期待网络IO事件,当有一个事件产生时,Leader线程首先告诉一个Follower线程将其提拔为新的Leader,而后本人就去干活了,去解决这个网络事件,处理完毕后退出Follower线程期待队列,期待下次成为Leader。这种办法能够加强CPU高速缓存相似性,及打消动态内存调配和线程间的数据交换。 3.1.4 Netty 实现提早工作-HashedWheel能够应用 Netty 提供的工具类 HashedWheelTimer 来实现提早工作。 ...

June 9, 2023 · 4 min · jiezi

SpringBoot20-基础案例04定时任务和异步任务的使用方式

一、定时任务1、基本概念按照指定时间执行的程序。 2、使用场景数据分析数据清理系统服务监控二、同步和异步1、基本概念同步调用程序按照代码顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用顺序执行时,不等待异步调用的代码块返回结果就执行后面的程序。 2、使用场景短信通知邮件发送批量数据入缓存三、SpringBoot2.0使用定时器1、定时器执行规则注解@Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行@Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行@Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次@Scheduled(cron="/5") :通过cron表达式定义规则2、定义时间打印定时器import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;import java.util.Date;/** * 时间定时任务 */@Componentpublic class TimeTask { Logger LOG = LoggerFactory.getLogger(TimeTask.class.getName()) ; private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ; /** * 每3秒打印一次系统时间 */ @Scheduled(fixedDelay = 3000) public void systemDate (){ LOG.info("当前时间::::"+format.format(new Date())); }}3、启动类开启定时器注解@EnableScheduling // 启用定时任务@SpringBootApplicationpublic class TaskApplication { public static void main(String[] args) { SpringApplication.run(TaskApplication.class,args) ; }}四、SpringBoot2.0使用异步任务1、编写异步任务类import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;@Componentpublic class AsyncTask { private static final Logger LOGGER = LoggerFactory.getLogger(AsyncTask.class) ; /* * [ asyncTask1-2] com.boot.task.config.AsyncTask : ======异步任务结束1====== * [ asyncTask1-1] com.boot.task.config.AsyncTask : ======异步任务结束0====== */ // 只配置了一个 asyncExecutor1 不指定也会默认使用 @Async public void asyncTask0 () { try{ Thread.sleep(5000); }catch (Exception e){ e.printStackTrace(); } LOGGER.info("======异步任务结束0======"); } @Async("asyncExecutor1") public void asyncTask1 () { try{ Thread.sleep(5000); }catch (Exception e){ e.printStackTrace(); } LOGGER.info("======异步任务结束1======"); }}2、指定异步任务执行的线程池这里可以不指定,指定执行的线城池,可以更加方便的监控和管理异步任务的执行。 ...

July 10, 2019 · 2 min · jiezi

记录 - 在vue中使用setTimeout,离开当前路由setTimeout未销毁的问题

问题:从第一个页面跳转到第二个页面后,如果停留在第二个页面,定时器还在运行。如果在两个页面之间来回跳转,跳转时间小于定时器的间隔时间时,也会出现重复创建setTimeout的情况。原因:当我们刷新页面时,会将当前页面之前创建的setTimeout以及其他定时器都清除掉,但是仅仅是路由切换是不会清除的。解决:具体代码:data () { return { overtimer: null }},methods: { stoptime () { clearTimeout(this.overtimer); }},created () { this.overtimer = setTimeout(() => { this.$Message.error('读取卡片超时'); }, 10000) this.$once('hook:beforeDestroy', () => { clearInterval(this.overtimer); this.overtimer = null; })}

April 9, 2019 · 1 min · jiezi

记一次微信小程序在安卓的白屏问题

在做小程序的时候,做到了一个限时商品售卖,用到了倒计时,因为这个原因导致了安卓手机上使用小程序时,将小程序放入后台运行一段时间后,再次进入小程序后出现了页面白屏或者点击事件失效的情况,这里记录下1.相关代码文件我这里是使用了自定义组件的形式来渲染的外部的引用的自定义组件的wxml文件/* limitCommodity是一个数组,返回的是商品对象,包含商品价格、商品结束时间、商品图片等 /<block wx:for="{{limitCommodity}}" wx:key="{{item.id}}"> <commodityItem class=“specialContent” goods="{{item}}" /></block>自定义组件的js文件Component({ properties: { goods: Object }, data: { }, timer: null, / 在组件实例进入页面节点树时执行,开始定时器 / attached: function() { if(this.timer) { clearInterval(this.timer); } this.filterTime(); let that = this; this.timer = setInterval(function () { that.filterTime(); }, 1000) }, / 在组件实例被从页面节点树移除时执行,将定时器清除 / detached: function() { clearInterval(this.timer); this.timer = null; }, methods: { / 用于将时间戳转换成自定义的时间格式 / filterTime() { let totalTime = new Date(parseInt(this.data.goods.endtime) * 1000) - new Date(); let days = parseInt(totalTime / 1000 / 60 / 60 / 24, 10); let hours = parseInt(totalTime / 1000 / 60 / 60 % 24, 10); let minutes = parseInt(totalTime / 1000 / 60 % 60, 10); let seconds = parseInt(totalTime / 1000 % 60, 10); let day = days >= 10 ? days : ‘0’ + days; day = day == 0 ? ’’ : day + ‘天’; let hour = hours >= 10 ? hours : ‘0’ + hours; let minute = minutes >= 10 ? minutes : ‘0’ + minutes; let second = seconds >= 10 ? seconds : ‘0’ + seconds; this.setData({ limitTime: day + hour + “:” + minute + “:” + second }) }, }})2.引起的原因因为在外部引入自定义的组件时,直接就是调用了定时器并且进行了setData操作,这就导致了当在外部引用这个组件时,如果传入的商品数组长度较大时,定时器增多的同时,setData操作也不断的增多setData多了就会导致内存占用多3.改进方法改进方法就是减少setData操作可以再自定义一个组件,用于将整个数组传入然后对商品数组里的时间先进行计算改进后的js文件Component({ properties: { limitCommodity:Array }, data: { }, timeOut:null, / 在组件实例进入页面节点树时执行 / attached(){ this.calculate(); }, / 在组件实例被从页面节点树移除时执行,将定时器清除 */ detached(){ clearTimeout(this.timeOut); this.timeOut = null; }, methods: { filterTime(endtime) { let totalTime = new Date(parseInt(endtime) * 1000) - new Date(); let days = parseInt(totalTime / 1000 / 60 / 60 / 24, 10); let hours = parseInt(totalTime / 1000 / 60 / 60 % 24, 10); let minutes = parseInt(totalTime / 1000 / 60 % 60, 10); let seconds = parseInt(totalTime / 1000 % 60, 10); let day = days >= 10 ? days : ‘0’ + days; day = day == 0 ? ’’ : day + ‘天’; let hour = hours >= 10 ? hours : ‘0’ + hours; let minute = minutes >= 10 ? minutes : ‘0’ + minutes; let second = seconds >= 10 ? seconds : ‘0’ + seconds; return day + hour + “:” + minute + “:” + second }, calculate(){ let limitCommodity = this.data.limitCommodity; for (let i = 0; i < limitCommodity.length;i++){ limitCommodity[i][’endtime_date’] = this.filterTime(limitCommodity[i][’endtime’]) } this.setData({ limitCommodity }) this.timeOut = setTimeout(()=>{ this.calculate(); },1000); } }})改进就是计算时间后再返回时间,而setData的是整个商品列表数组,这样就减少了setData次数正在努力学习中,若对你的学习有帮助,留下你的印记呗(点个赞咯^_^)往期好文推荐:判断iOS和Android及PC端纯css实现瀑布流(multi-column多列及flex布局)实现单行及多行文字超出后加省略号微信小程序之购物车和父子组件传值及calc的注意事项 ...

December 22, 2018 · 2 min · jiezi