关于TimingWheel(时间轮)算法的任务定时器网上有很多文章,但是却找不到基于java成系统的文章,所以今天把我在公司做的且稳定运行半年多的TimingWheel系统分享给大家。
1 TimingWheel基本原理:
众所周知寻常的定时器大概有两种,一种是开阻塞线程,另一种是开一个任务队列然后定期扫描。显而易见这两种方式的弊端很明显,前者对线程消耗过大,后者对时间消耗过大(很多未到时间的任务会被多次重复扫描消耗性能)
为了解决以上两个问题就可以使用TimingWheel数据结构。
请先看下图:
很明显时间轮算法是基于循环链表数据结构。那么他的工作原理具体是怎样的呢?
能看到图中有个指针,我们假设指针每跳动一下需要10秒,然后现在我们有一个50秒后执行的任务A,由此推断在当前指针指向2的时候,任务A会被存放在槽格7中。当指针跳动到7后取出槽格中的任务队列,此时任务A将会被执行。但是当如果有100秒后的任务需要执行,依然超出了槽格数量是该怎么办呢?很简单,我们为任务实例添加一个属性“圈数”就可以解决。就是说每次装载任务时算出此任务需要指针转动几圈才能够被执行。
以上就是时间轮算法的最基本的原理了,此外还有升级版复合时间轮算法用来解决巨大量的定时任务的一种解决方案。但这个不是本文重点,且网上有很多教学,感兴趣的同学可以自行去查看。
好了核心算法做完简介后,进入本文重点,以时间轮算法为核心的分布式任务调度系统实现。
请看下图:
能看出来,大致分为了三个部分
任务存储层:
这里选择了Redis用来做任务的存储,好处很明显,一个是支持分布式,另一个是性能可以得到保障以及一个以时间轮算法的数据结构的循环链表组成任务存储层。
任务发布订阅层:
这里核心是使用redis的监听队列来完成的
1 客户端发布任务到redis
2 发布订阅层监听到任务并通过时间轮算法概念解析任务并装载到redis中
3 时间轮任务扫描器根据当前指针获取需要扫描的队列
4 取出到时间需要执行的任务队列向应用层发出任务执行请求
5 获取应用层任务执行反馈。成功:释放队列中的任务。失败:再次重复执行,当失败次数超出上限后,进入失败队列
6 失败队列每隔一段时间取后X个任务重复执行。
7 队列中X前的任务作废,永不自动执行但可以触发执行
应用层:
这层主要是发布任务到redis监听队列,和监听队列里需要执行的任务。
总结:
1 以上就是大致的实现过程,还有很多细节以及优化点。比如扫描器在获取队列时需要分布式锁将指针对应的槽格锁住,很消耗性能,如果能再存储结构上粒度更细些就能解决这个问题。以及在执行时等待执行结果时执行线程处于阻塞状态,彼此本毫无关系的任务需要等待前一个任务执行完毕后才能执行,显然这样是不合理的,如果能使用消息队列多管道的概念就能解决这个问题,多个监听队列并发执行任务,这样性能将会成倍提升;以及redis的数据持久安全问题等都需要解决。
本文来源:知乎,转载请注明出处!
来源地址:https://zhuanlan.zhihu.com/p/43038631
最新内容
© 2016 - 2024 chengxuzhixin.com All Rights Reserved.