caffeine 时间轮的实现

Caffeine内部实现了一个时间轮。

时间轮的具体实现为一个二维数组,其数组的具体位置存放的则为一个待执行节点的链表。

时间轮的二维数组的第一个维度则是具体的时间间隔,分别是秒,分钟,小时,天,4天,但但并没有严格按照时间单位来区分单位,而是根据以上单位最接近的2的整数次幂作为时间间隔,因此在其第一个维度的时间间隔分别是1.07s,1.14m,1.22h,1.63d,6.5d。

static final long[] SPANS = {
    ceilingPowerOfTwo(TimeUnit.SECONDS.toNanos(1)), // 1.07s
    ceilingPowerOfTwo(TimeUnit.MINUTES.toNanos(1)), // 1.14m
    ceilingPowerOfTwo(TimeUnit.HOURS.toNanos(1)),   // 1.22h
    ceilingPowerOfTwo(TimeUnit.DAYS.toNanos(1)),    // 1.63d
    BUCKETS[3] * ceilingPowerOfTwo(TimeUnit.DAYS.toNanos(1)), // 6.5d
    BUCKETS[3] * ceilingPowerOfTwo(TimeUnit.DAYS.toNanos(1)), // 6.5d
};

当具体的时间事件要加入到时间轮时,将会根据该事件距离当前时间的最接近的单位,首先定位到二维数组的第一个维度,具体后面会解释。

为什么要选择最接近的2的整数而不是选择具体的时间整数,是为了可以通过移位比较快速得到时间滚动在二维数组的第一个维度的变动。

在时间轮中,具体记录了以上时间间隔的偏移量,在时间轮中,将当前时间与上一次时间求差并不断右移SHIFT的位数,便可以快速定位到时间的变动。

static final long[] SHIFT = {
    Long.SIZE - Long.numberOfLeadingZeros(SPANS[0] - 1),
    Long.SIZE - Long.numberOfLeadingZeros(SPANS[1] - 1),
    Long.SIZE - Long.numberOfLeadingZeros(SPANS[2] - 1),
    Long.SIZE - Long.numberOfLeadingZeros(SPANS[3] - 1),
    Long.SIZE - Long.numberOfLeadingZeros(SPANS[4] - 1),
};

当具体的时间事件进入,得到当前时间与发生时间的差,不断与SPAN数组比较,那么确定当前时间事件的发生事件在其间隔中,完成第一个维度的定位。

 

用简单的数字举个例子,第一个维度分别为1s,10s,100s,1000s,那么具体的第一维存在四个槽位,分别存放10s以内的,10s到100s以内的,100s到1000s以内的,1000s以后的,当一个300s之后发生的时间事件进入后,首先得到差值300,依次比较,显然300大于100小于1000,那么这个300s以后发生的事件在时间轮上的二维数组的第一个维度的第三个位置上。其中1s则是用来标志过期操作之间的最小的时间刻度,并没有参与到时间事件的定位中,类比时间轮中的1.07s。

 

在二维数组的第二个维度,被根据时间的具体粗细分成了若干个数组。

static final int[] BUCKETS = { 64, 64, 32, 4, 1 };

其中0到1.14m和1.14m和1.22h之间分别被分割成了64个位置,具体在这几个时间范围内的事件将会根据时间hash到具体的位置上的链表上,而6.5d之后的事件则只有1个链表,这个数字究竟是如何选取的?举个例子,1.07s需要右移的位数为30位,1.14m的位数则为36位,那么显然6位可以表达的位数为64,同理,32是根据1.63d和1.22h右移位数差为5位得出来的。这样,在1.14m内发生的事件,根据其发生顺序依次存放到了第二个维度上,当发生过期时,两者间隔小于1.14m的时候,只需要根据两者移位后与相应时间戳相与的结果,便可以定位到该段时间内第二个维度的处理的链表范围,而不用全部遍历第二个维度的所有链表。

 

以上是时间轮的构成,当时间轮的advance()方法被调用时,将会把时间轮中的过期事件执行过期。

public void advance(long currentTimeNanos) {
  long previousTimeNanos = nanos;
  try {
    nanos = currentTimeNanos;
    for (int i = 0; i < SHIFT.length; i++) {
      long previousTicks = (previousTimeNanos >> SHIFT[i]);
      long currentTicks = (currentTimeNanos >> SHIFT[i]);
      if ((currentTicks - previousTicks) <= 0) {
        break;
      }
      expire(i, previousTicks, currentTicks, previousTimeNanos, currentTimeNanos);
    }
  } catch (Throwable t) {
    nanos = previousTimeNanos;
    throw t;
  }
}

将当前时间与上次时间不断右移相应的位数,直到两者右移后的结果小于等于0,因为时间戳的特性,通过高位的不同可以感知到时间单位的变化,由于本身时间选取的都是2的整数次幂,这里可以快速感知,并对时间范围内的数据根据两者移位后的结果与对应时间相与的结果快速定位链表进行过期操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值