go语言定时任务时间到期,继续go time
Go语言基于Etcd实现的定时任务
利用 Etcd 的Lease租约特性来实现定时功能,同时通过Watch机制来实现多节点情况下只有一个节点执行该任务。通过定时任务库 Cron 的时间字符串解析器Parser来解析任务执行时间。
创新互联是一家集网站建设,德惠企业网站建设,德惠品牌网站建设,网站定制,德惠网站建设报价,网络营销,网络优化,德惠网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。
Etcd
Cron
源码链接
计时器中四叉堆的变迁
在我们编码过程中,经常会用到与时间相关的需求。而关于时间转换之类的比较简单,那么计时器经过了以下几个版本的迭代:
Go1.10之前的计时器的结构体如下所示
注意这个结构体是用var变量定义的,它会存储所有的计时器。t就是最小四叉堆,运行时创建的所有计时器都会加入到四叉堆中。
由一个独立的timerproc通过最小四叉堆和futexsleep来管理定时任务。
但是全局四叉堆共用一把锁对性能的影响非常大,所以Go1.10之后将全局四叉堆分割成了64个更小的四叉堆。
在理想情况下,四叉堆的数量应该等于处理器的数量GOMAXPROCS,但是需要动态获取处理的数量,所以经过权衡初始化64个四叉堆,如果当前机器的处理器P的个数超过了64个,多个处理器的计时器可能会存储在同一个桶中。
将全局计时器分片,虽然能够降低锁的粒度,但是timerproc造成处理器和线程之间频繁的上下文切换却成为了影响计时器的瓶颈。
在Go1.14版本之后,计时器桶timersBucket已经被移除了,所有的计时器都以最小四叉堆的形式存储在P中。
在p结构体中有以下字段与计时器关联:
而计时器的结构体为:
这个仅仅只是runtime/time.go运行时内部处理的结构,而真正对外暴露的计时器的结构体是:
通过channel来通知计时器时间
在runtime/time.go文件下,我们可以看到下面几个方法:
当通过time.NewTimer方法增加新的计时器时,会执行startTimer来增加计时器
状态从timerNoStatus-timerWaiting,其他状态会抛出异常
1、调用cleantimers清除处理器P中的计时器,可以加快创建和删除计时器的程序速度
2、调用doaddtimer将当前计时器加入到处理器P的四叉堆timers中
3、调用wakeNetPoller唤醒网络轮询器中休眠的线程,检查timer被唤醒的时间when是否在当前轮询预期的运行时间内,如果是就唤醒。
当通过调用timer.Stop停止计时器时,会执行stopTimer来停止计时器
deltimer会标记需要删除的计时器。在删除计时器的过程中,可能会遇到其他处理器P的计时器,所以我们仅仅只是将状态标记为删除,处理器P执行删除操作。
当通过调用timer.Reset重置定时器时,会执行resetTimer来重置定时器
modtimer会修改已经存在的计时器,会根据以下规则处理计时器状态
状态为timerNoStatus, timerRemoved会被标记为已删除wasRemoved,就会调用doaddtimer新创建一个计时器。
而在正常情况下会根据修改后的时间进行不同的处理:
会根据状态清除处理器P的最小四叉堆队头的计时器
在GPM调度的时候检查计时器
与cleantimers不同的是,adjusttimers会遍历处理器P转给你所有的计时器
会检查四叉堆堆顶的计时器,根据状态处理计时器
1、状态是timerDeleted,状态变为timerDeleted,然后删除计时器,再变更状态为timerRemoved
2、状态是timerModifiedXXX
3、状态是timerWaiting,如果计时器没有到达触发时间,直接返回,否则状态变为timerRunning,调用runOneTimer运行堆顶的计时器
根据period字段是否大于0判断,如果大于0
如果小于等于0:
更新完状态后,回调函数f(arg, seq)执行方法。
在adjesttimers中提到过
checkTimers是调度器用来运行处理器P中定时器的函数,会在以下几种情况被触发:
1、先通过处理器P字段中updateTimer0When判断是否有需要执行的计时器,如果没有直接返回
2、如果下一个计时器没有到期但是需要删除的计时器较少时会直接返回
3、加锁
4、需要处理的timer,根据时间将timers切片中的timer重新排序,调用adjusttimers
5、会通过runtimer依次查找运行计时器
6、处理器中已删除的timer大于p上的timer数量的1/4,对标记为timerDeleted的timer进行清理
7、解锁
go1.10最多可以创建GOMAXPROCS数量的timerproc协程,当然不超过64。但我们要知道timerproc自身就是协程,也需要runtime pmg的调度。到go 1.14把检查到期定时任务的工作交给了网络轮询器,不需要额外的调度,每次runtime.schedule和findrunable时直接运行到期的定时任务。
Golang-基于TimeingWheel定时器
在linux下实现定时器主要有如下方式
在这当中 基于时间轮方式实现的定时器 时间复杂度最小,效率最高,然而我们可以通过 优先队列 实现时间轮定时器。
优先队列的实现可以使用最大堆和最小堆,因此在队列中所有的数据都可以定义排序规则自动排序。我们直接通过队列中 pop 函数获取数据,就是我们按照自定义排序规则想要的数据。
在 Golang 中实现一个优先队列异常简单,在 container/head 包中已经帮我们封装了,实现的细节,我们只需要实现特定的接口就可以。
下面是官方提供的例子
因为优先队列底层数据结构是由二叉树构建的,所以我们可以通过数组来保存二叉树上的每一个节点。
改数组需要实现 Go 预先定义的接口 Len , Less , Swap , Push , Pop 和 update 。
timerType结构是定时任务抽象结构
首先的 start 函数,当创建一个 TimeingWheel 时,通过一个 goroutine 来执行 start ,在start中for循环和select来监控不同的channel的状态
通过for循环从队列中取数据,直到该队列为空或者是遇见第一个当前时间比任务开始时间大的任务, append 到 expired 中。因为优先队列中是根据 expiration 来排序的,
所以当取到第一个定时任务未到的任务时,表示该定时任务以后的任务都未到时间。
当 getExpired 函数取出队列中要执行的任务时,当有的定时任务需要不断执行,所以就需要判断是否该定时任务需要重新放回优先队列中。 isRepeat 是通过判断任务中 interval 是否大于 0 判断,
如果大于0 则,表示永久就生效。
防止外部滥用,阻塞定时器协程,框架又一次封装了timer这个包,名为 timer_wapper 这个包,它提供了两种调用方式。
参数和上面的参数一样,只是在第三个参数中使用了任务池,将定时任务放入了任务池中。定时任务的本身执行就是一个 put 操作。
至于put以后,那就是 workers 这个包管理的了。在 worker 包中, 也就是维护了一个任务池,任务池中的任务会有序的执行,方便管理。
当前文章:go语言定时任务时间到期,继续go time
浏览路径:http://scyanting.com/article/hojici.html