1定时任务Netty、Quartz、Kafka、Linux都有定时任务功能。JDK自带的java.util.Timer和DelayedQueue可以实现简单的定时任务。底层使用堆,访问复杂度为O(nlog(n)),但不能支持海量的定时任务。在任务量大、性能要求高的场景下,为了将任务获取和取消操作的时间复杂度降低到O(1),会使用时间轮算法。2时间轮模型及其应用一种高效批量管理定时任务的调度模型。一般会实现成环形结构,类似于时钟,分成很多槽,每个槽代表一个时间间隔,每个槽用一个双向链表来存储定时任务。指针周期性跳转,当跳到一个slot时,执行slot的定时任务。HashedTimingWheelSchematicSc??hematicSc??hematicFailureRecoveryFlowControlSchedulingAlgorithmControlPacketLifetime如果处理器在每个时钟滴答都被中断,则网络中的计时器的维护成本很高使用细粒度计时器许多出色的计时器需要高效计时器算法可减少整体中断高架。单层时间轮的容量和精度都是有限的。对于精度要求特别高,时间跨度特别大,或者需要调度的定时任务数量多的场景,通常会采用多级时间轮以及持久化存储和时间轮相结合的方式。.模型中的规则和性能指标模型客户端调用:START_TIMER(TimeInterval,Request_ID,Expiry_Action)STOP_TIMER(Request_ID)TimertickCalls:PER_TICK_BOOKKEEPINGEXPIRY_PROCESSING性能指标SpatialDataStructureMemoryUsedLatencyRequiredtostartandendanyofaboveroutinesTime3Dubbo's时间轮结构Dubbo时间轮实现了位于dubbo-common模块中的org.apache.dubbo.common.timer包。下面我们就来分析一下时间轮涉及到的核心接口和实现。核心接口TimerTask在Dubbo中,所有的定时任务都必须实现TimerTask接口。只定义了一个run()方法,入参是一个Timeout接口对象。TimeoutTimeout对象与TimerTask对象一一对应,类似于线程池返回的Future对象和提交给线程池的任务对象的关系。通过Timeout对象,不仅可以查看定时任务的状态,还可以对定时任务进行操作(比如取消关联的定时任务)。Timeout接口中的方法:Timer接口定义了定时器的基本行为,核心是newTimeout():提交一个定时任务(TimerTask)并返回关联的Timeout对象,类似于向线程池提交任务。HashedWheelTimeoutHashedWheelTimeout是Timeout接口的唯一实现,是HashedWheelTimer的内部类。HashedWheelTimeout有两个作用:时间轮中双向链表的节点,即HashedWheelTimer中的TimerTask容器定时任务TimerTask提交给HashedWheelTimer后返回的句柄(Handle),用于查看和控制核心时间轮外的定时任务Fieldsprev,next。双向链表用于链接HashedWheelTimerBucket中的超时(定时任务)。由于只作用于WorkerThread,所以不需要synchronization/volatile。task,实际调度的任务deadline,调度任务的执行时间。指定创建HashedWheelTimeout时的计算公式:currentTime(创建HashedWheelTimeout的时间)+delay(任务延迟时间)-startTime(HashedWheelTimer的开始时间),nsstate,定时任务的当前状态可选state:STATE_UPDATER用于实现state状态改变原子性。remainingRounds,当前任务剩余的时钟周期数。时间轮所能代表的时间长度是有限的。当任务的到期时间与当前时刻的时间差超过时间轮转动一圈所能代表的时长时,就会出现循环。该字段的值需要代表剩余的时钟周期。核心APIisCancelled()isExpired()state()检查当前HashedWheelTimeout状态cancel()方法expire()方法remove()HashedWheelBucket超时轮中的一个槽。时间轮中的槽其实就是一个缓存和管理双向链表的容器。双向链表中的每个节点都是一个HashedWheelTimeout对象,关联一个TimerTask定时任务。HashedWheelBucket保存双向链表的第一个和最后一个节点——头和尾,加上每个HashedWheelTimeout节点保存前驱和后继引用,整个链表可以正向和反向遍历。核心APIaddTimeout()pollTimeout()remove()从双向链表中移除指定的HashedWheelTimeout节点。clearTimeouts()循环调用pollTimeout()方法处理整个双向链表,返回所有没有超时或者取消的任务。expireTimeouts()遍历双向链表中的所有HashedWheelTimeout节点。在处理过期的定时任务时,会通过remove()方法取出,调用其expire()方法执行;对于取消的任务,会通过remove()方法取出,直接丢弃;对于未到期的任务,它将remainingRounds字段(剩余的时钟周期数)减一。HashedWheelTimerTimer接口的实现通过时间轮算法实现了一个定时器。函数根据当前时间轮指针选择对应的HashedWheelBucket槽,从链表头部开始迭代,计算每个HashedWheelTimeout定时任务:如果属于当前时钟周期,如果不属于则取出运行it,然后将剩余时钟周期数减少一个核心域workerState时间轮当前状态,三个可选值,由AtomicIntegerFieldUpdater原子修改。startTime是当前时间轮的开始时间,提交到这个时间轮的定时任务的deadline字段的值就是从这个时间戳计算出来的。轮时间轮环队列,每个元素是一个slot。当时间轮槽数为n时,取值为timeouts和canceledTimeoutsHashedWheelTimer,在处理HashedWheelBucket的双向链表之前,会处理这两个队列的数据:timeouts队列缓存外部提交时间轮中的超时ScheduledtaskcancelledTimeoutsQueuecancelledscheduledtasktick位于HashedWheelTimer$Worker中,时间轮的指针,步长为1的单调递增计数器maskmask,mask=wheel.length-1,执行ticks&mask定位到对应的实际时间所代表的时钟槽的ticksDuration时间指针的每次增量,以纳秒为单位。pendingTimeouts当前时间轮剩余的计划任务总数。workerThread时间轮内部真正执行定时任务的线程。worker真正执行定时任务的逻辑就封装在这个Runnable对象中。newTimeout()提交一个定时任务。定时任务在进入超时队列之前,会调用start()方法启动时间轮。将完成以下两个关键步骤:确定时间轮的startTime字段启动workerThread线程,开始执行worker任务。然后根据startTime计算出定时任务的deadline,最后将定时任务封装到HashedWheelTimeout中加入到timeouts队列中。4时间轮指针旋转一圈的执行过程HashedWheelTimer$Worker.run():时间轮指针旋转一圈,开始时间轮循环。清理用户取消的计划任务。当用户取消这些计划任务时,它们将被记录在cancelledTimeouts队列中。指针每转动一次,时间轮就会清理队列,将缓存在timeouts队列中的定时任务转移到时间轮中对应的槽中。根据当前指针定位到对应的slot,处理slot双向链表中的定时任务。检查时轮的状态。如果时间轮处于运行状态,以上步骤会循环执行,定时任务会一直执行。如果时间轮处于停止状态,执行以下步骤获取未处理的定时任务,并将其加入到unprocessedTimeouts队列中:遍历时间轮中的每个槽,调用clearTimeouts()方法;如果timeouts队列还没有加入slot,循环调用poll(),最后再次清理cancelTimeouts队列中用户取消的定时任务。5定时任务应用不直接用于周期性操作,只是向时间轮提交并执行单个定时任务。当上一个任务执行完成后,调用newTimeout()方法再次提交当前任务,使其在下一个任务中执行。循环执行任务。即使在任务执行过程中发生GC、I/O阻塞等,造成任务延迟或卡顿,同样的任务也不会连续提交,造成任务堆积。Dubbo时间轮的应用主要有以下几个方面:失败重试,例如Provider向注册中心注册失败时的重试操作,或者Consumer订阅注册时的失败重试等周期性定时任务center,比如周期性发送Heartbeat请求,请求超时的处理,或者网络断开后的重连机制参考https://zhuanlan.zhihu.com/p/32906730
