定时器方案(二)之时间轮原理 详解

img

时间轮

类似于时钟,秒针走一圈,分针前进一格
skynet、netty与kafka用的是时间轮

单层级的时间轮

用于实现时间窗口(如tcp滑动窗口)的限流与熔断
假设检测5秒内是否有100次操作
限流: 每秒都查看最近五秒是否有100次操作
熔断:每过五秒查看这五秒有没有100次操作
显而易见的,限流更加准确,但是很耗费时间,熔断没那么准确,但是相对来说没那么耗时间
熔断的应用:
 DDos攻击:
  客户端不断发送大量数据给服务器的过程为DDos攻击
 解决办法:
  在网络底层用DPDK判断
  在应用层用熔断机制判断规定时间内客户端发送的数据包是否大于最大上限

单层级时间轮应用场景(以下例子为简单假设)

场景:
 客户端每五秒发送心跳包,服务端10秒内没收到心跳数据就清除连接。假设使用map<int,conn*>来存储所有连接数,每秒遍历map,检测出失效的连接。但如果有上万条数据,那么很容易做出很多无效操作,如:刚添加连接的客户端,刚好遇到遍历map,那么就是无效的,正确操作应该是10秒后检测。
时间轮应用:
考虑的点:时间轮的大小,时间精度
 在上述场景中实现如下图的时间轮,数组长度为8,数组中的元素为链表,链表指向客户端连接,假设现在处理的是第四组客户端链表,新加进来的客户端放在第6((4+10)%8)组客户端链表中,等处理到数组下标为6的客户端链表是进行心跳包检测。
在这里插入图片描述

 时间轮大小即数组大小,时间精度即数组元素占的时间大小(上面是一秒处理一个)。
 很显然时间轮大小应该大于目标大小,最优选为2的n次幂刚好大于目标大小,在本例中应该选取16。因为x%16 = x&(16-1);,x为当前执行的数组位置,这样子做位操作效率更高。

空推进

 当时间轮设置过大或时间精度过高,很容易出现空推进的问题,即处理对应数组元素是,里面是空的,没有客户端连接。
 解决方法:
最小堆
降低时间精度(以2s为单位或者更低的精度)
使用多层级时间轮

多层级时间轮

用于处理时间跨度大的时间窗口,如有些任务几秒钟要执行,有些任务几个小时甚至几天执行一遍。

例子

在这里插入图片描述

 假定我们设计一个如上图所示的多层级时间轮。用户是针对sec进行操作的,即最高层的时间轮。
 在插入时间轮时出现以下几种情况(tick为当前秒数,i为多少秒后触发即定时长度):
1、0<=i<=59时,sec[(tick+i)%60].push_back(task);
2、60<=i<=3599时,min[((tick+i)/60)%60].push_back(task);
3、3600<=i<43199时,hour[((tick+i)/60/60)%12].push_back(task);
4、i>=43200时,i= i%43200,然后在代入到上面的情况
注意:
上面进行整除的时候,需要记录下被多除掉的数
 在时间轮前进的时候:
1、当秒针指针指向下标为59的元素并且执行完当前操作,向后移动一格时。分针对应的指针向后移动一格,并且将分针中的数据恢复到秒针数组中,秒针指针指向下标为0的元素。
2、当分针指针指向下标为59的元素并且向后移动一格,操作如上所示。
注意:
1、将下一级的数据赋到上一级的数组中时,具体给到放到哪一个格子中,是根据之前多除掉的数来判断的。
2、分针指针与时针指针都可以根据tick算出来的。
3、时针数组的第0个位置为什么会有数据?当i=4200-2并且tick=3时,就会出现时针数组第0个位置有值。因此在从时针数组第11个位置取数据时,需要将第0位的数据也取出来给到分针数组。只有最后一层和第一层才会出现第0个位置有值。

时间复杂度

O(1),适用于多线程,因为加锁之后,锁的力度很小,时间复杂度很低,相较于红黑树的O(logn)更优。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值