这段时间研究了一个基于ringbuffer的定时器设计方案,思路设计上还是相当有趣的!打算在即时通信实现心跳定时器。以前一聊高性能定时器不外乎就那么几种方法,不是二叉堆,就是epoll timerfd。 我个人感觉来说但后者更适合监听就近到期的任务,而使用ringbuffer适合任务很密集的场景,当然从技术上你完全可以互用的。 就时间精度来说,二叉堆和timerfd比ringbuffer定时器精度高不少的。另外二叉堆实现的定时器方便实时矫正当前时间做对比,而ringbuffer可能会出现时间偏移。
当然ringbuffer也可以按照每个槽位一毫秒的单位来设计,这样可以避免上面的精度问题。但因为ringbuffer定时器会像手表那样,一直按照单位前进,如果你后面的槽位都没有任务,那就白白浪费很多的系统调用了。
性能上来说 ringbuffer > 二叉堆 > timefd , 精度来说 二叉堆 = timefd > ringbuffer . 所谓的性能对比也只是相对来说的,对于系统总的消耗真的很少很少。
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新.
http://xiaorui.cc/?p=4387
我这边用python简单写了一个模子,大家可以看看。ringbuffer其实就是个数组,python里没有数组的概念,索性我直接用计数加dict的方式来实现类ringbuffer,毕竟dict的效率也是O(1) 。 ringbuffer一般用来构建无锁框架的,这里会通过cas的逻辑来避开锁的消耗。更多的ringbuffer可以参考其他的文章。
代码已经推送到github了,有兴趣的朋友可以看看。 demo代码是python实现的,结构上写的很简单,主要是设计思路大家理解就好了。https://github.com/rfyiamcool/RingTimer
设计ringtimer定时器我们还要考虑几个问题:
1、 timer和增改有可能会并发触发,这样不是线程安全,ringbuffer无锁编程推荐使用cas方法解决,这样是spinlock自旋锁的意思。 当我们在同一个位置点时,那么我就做自选操作,一直到你跑别的位置上,我才进行修改数据,这样就不需要因为共同修改同一个区域而加锁了。
2、 如果你的任务会产生block ,那么timer在获取过期任务后,应该推送到一个队列里。 不能因为某个点的block,影响时间的偏移。
3、 定期做时间纠正
那么可能会有人问我,在什么地方会用到定时器?
1. 我有几万的定时任务,我如何渐进地把到期的任务推出去,或者是执行。
2. 大家知道队列一pop,消息就没了? 我如何把丢失的任务重新放到队列里面? 也是定时器
3. 即时通信的心跳,我如何快速的感知已经超过timeout的连接对象?
4. more
RingTimer项目一共涉及到四个部分:
1. 一个环形队列
2. 每个slot对应一个任务集合
3. 任务跟slot的对应关系
4. 一个可以围着队列转的线程
这里讲一下原理,比如我们想设立心跳超时时间为15秒的定时器,那么如何搞? 首先创建一个数组或者计数器。这15个数字可以想成slot槽位,每个slot都有一个映射表,用来表示这个slot有哪些uid,还需要一个uid跟slot的映射,这样我们可以通过uid知道他在哪个槽位上。 这样几个数据结构有了,那么我们只需要运行一个线程按照每秒next()一次的效率,一直不停的围着ringbuffer转,主要看到有任务就说明 该任务映射表是超时状态的。
当一个用户的心跳包到达服务端…
如果没有该任务记录,那么我们找到ringtimer当前在ringbuffer的位置,然后放到他前一个位置就好了,当然你也可以放在当前位置往后15个点? 其实是一样的.
如果有记录,那么会通过uid_slot映射找到slot位置,然后再从slot里清空uid,接着把uid的状态放到当前位置的上一个槽位。
那么你会问费这么大工夫实现这个心跳定时器为了什么? 避免客户端依赖轮询的方式获取好友在线状态,这样我们可以做更多的优化,当用户好友少的时候,我们可以拉起tcp对象,直接发送用户状态,这样做到了推送实时,这样给与即时通信服务端多了一种策略优化。
总结,你会发现这个定时器的设计不会消耗cpu的,只会消耗一些内存而已。二叉堆实现的小顶堆还会因为平衡而有所消耗。
参考了沈剑大大的文章: http://www.tuicool.com/articles/BFJF7j