Time Wheel (the most complete in history)

In Caffeine, the king of caching, when it comes to the expiration of 100W, 1000W, or even billion-level elements, how to perform high-performance timing scheduling is a difficult problem.

Note: This article makes a systematic and penetrating introduction of the high-performance time-wheel algorithm in the scenario of massive scheduling tasks, helping everyone to thoroughly grasp this high-performance algorithm.
In addition
, this article is continuously updated in PDF, and the PDF files of the latest Nien architecture notes and interview questions can be obtained from the following link: Yuque or Code Cloud

insert image description here

Mass timing task management problem

The following questions, from the Internet:

For a large-scale content review, usually, the time for passing the content is set in the operation setting. After this time, the relevant content will be automatically approved .

This is a small requirement, but considering that there are many things that need to be reviewed regularly, this way:

Mass scheduled task scheduling brings a series of performance problems.

There are many scenarios for the management of massive scheduled tasks. In actual projects, there are a large number of tasks that need to be triggered by timing or delay.

For example, in e-commerce, the delay needs to check whether the order is paid successfully, whether the delivery is successful, and regularly push reminders to users, etc.

Scheme one single timer scheme

Description :

Put all the resources that need to be audited regularly into redis, such as sorted set, and the time that needs to be audited is used as the score value.

A timer is started in the background to regularly poll sortedSet. When the score value is less than the current time, the running task is approved.

question

This scheme has no problem in the case of small batches of data.

However, there will be problems in the case of large batches of tasks, because the full amount of data must be polled each time to determine whether it needs to be executed one by one.

Once the polling task is executed for a long time, there will be a problem that the task cannot be executed according to the scheduled time.

Scheme 2 One task and one timer scheme

describe

Each task that needs to be completed regularly starts a timed task, and then waits for completion and then destroys it

question

The problem brought by this solution is obvious. When there are many scheduled tasks, many threads will be started, so the server will not be able to bear it and then crash.

Basically, this option will not be adopted.

Option 3 redis expiration notification function

describe

Similar to Solution 1, set an expiration time for each task that requires regular review,

The expiration time is the time when the audit is passed. Subscribe to the expiration event of redis. When this event occurs, the corresponding auditing task will be executed.

question

For this solution, we borrow the middleware of redis to realize our function, which is actually part of the publish and subscribe function of redis.

For the redis publish and subscribe function, it is not recommended for us to do business operations in the production environment.

Usually within redis (for example, redis cluster nodes go online and offline, elections, etc.), the event that our business system uses it will cause the following two problems

1. The instability of redis publish and subscribe

2. The reliability of redid publish and subscribe

For details, please refer to https://my.oschina.net/u/2457218/blog/3065021 (publishing and subscribing defects of redis)

Scheme Four Hierarchical Time Wheel Scheme

This thing is designed for mass timing task management.

See references for specific papers

http://www.cs.columbia.edu/~nahum/w6998/papers/sosp87-timing-wheels.pdf

The time wheel in Caffeine, the king of caching

In Caffeine, the king of caching, it involves the expiration of 100w, 1000w, or even 100 million elements.

In Caffeine, the king of caching, when it comes to the expiration of 100W, 1000W, or even billion-level elements, how to perform high-performance timing scheduling is a difficult problem.

Caffeine uses a time wheel to solve this problem,

The general structure of the time wheel:

insert image description here
Caffeine's implementation of the time wheel is TimerWheel, which is a kind of hierarchical timing wheels.

Caffeine's time wheel has five levels.

insert image description here

Take a look at the schedule method for adding elements to the time wheel:

/**
 * Schedules a timer event for the node.
 *
 * @param node the entry in the cache
 */
public void schedule(@NonNull Node<K, V> node) {
  Node<K, V> sentinel = findBucket(node.getVariableTime());
  link(sentinel, node);
}

/**
 * Determines the bucket that the timer event should be added to.
 *
 * @param time the time when the event fires
 * @return the sentinel at the head of the bucket
 */
Node<K, V> findBucket(long time) {
  long duration = time - nanos;
  int length = wheel.length - 1;
  for (int i = 0; i < length; i++) {
    if (duration < SPANS[i + 1]) {
      long ticks = (time >>> SHIFT[i]);
      int index = (int) (ticks & (wheel[i].length - 1));
      return wheel[i][index];
    }
  }
  return wheel[length][0];
}

/** Adds the entry at the tail of the bucket's list. */
void link(Node<K, V> sentinel, Node<K, V> node) {
  node.setPreviousInVariableOrder(sentinel.getPreviousInVariableOrder());
  node.setNextInVariableOrder(sentinel);

  sentinel.getPreviousInVariableOrder().setNextInVariableOrder(node);
  sentinel.setPreviousInVariableOrder(node);
}

If you don't understand these source codes, it doesn't matter, let's go from the shallower to the deeper, slowly.

The basic concept of time wheel

The time wheel technology has actually been around for a long time. It is used in high-performance components such as kafka, zookeeper, Netty, and Dubbo.

As shown in the picture, the time wheel, from the picture, is the same as the watch bezel, so it is called the time wheel.

img

The time wheel is actually a circular data structure, and its design refers to the thinking of clock rotation.

It can be imagined as a clock, divided into many grids, and each grid represents a period of time

The time wheel is composed of multiple time grids. There are 8 time grids in the figure below. Each time grid represents the basic time span (tickDuration) of the current time wheel. The number of time grids in the time wheel is fixed.

insert image description here

In the figure, there are 8 time grids (slots), assuming that the unit of each time grid is 100ms, then it takes 800ms for the entire time wheel to complete a circle.

Every 100ms the pointer will move clockwise for one time unit,

This unit can represent time precision

This unit can be set,

For example, in seconds, or in hours.

Move the pointer to obtain the task list in each time grid, and then traverse the doubly-linked list in this time grid to execute the tasks, and thus cycle.

The time wheel is a circular queue composed of time as the scale. This circular queue is implemented by an array.

Each element of the array is called a slot Bucket,

insert image description here

Each slot can hold a scheduled task list, called HashedWheelBucket,

Each slot can be a doubly linked list , in which a sentinel sentinel node can be set as the starting node for adding tasks and deleting tasks.

Each item in the slot linked list represents a timed task item (HashedWhellTimeout), which encapsulates the real timed task TimerTask.

simply put:

The time wheel is a scheduling model that efficiently utilizes thread resources for batch scheduling.

Bind a large number of scheduled tasks to the same scheduler, and use this scheduler to manage, trigger, and run all tasks.

img

The time wheel model can efficiently manage various tasks:

  • delayed task,
  • periodic tasks,
  • Notification tasks.

The time wheel algorithm is used in many frameworks, such as Dubbo, Netty, Kafka, etc.

The time wheel algorithm is also a relatively classic design.

The analogy between time wheel and hashmap

In a more general situation, multiple tasks may need to be performed at the same time, such as:

  • Every day at 9 am, in addition to generating reports,
  • Execute the task of sending mail,
  • Execute the task of creating the file,
  • Perform data analysis tasks
  • etc,

In order to store these tasks, what if there are multiple tasks that need to be executed?

A slot can point to an array or a linked list, which is used to store the tasks to be performed by the scale.

So, essentially,

The data structure of the time wheel is actually similar to a hashmap .

Each time scale of the time wheel can be understood as a slot, and there are multiple tasks at the same time, which are placed in a doubly linked list .

As shown below:

3f150aada5a445a6bd9dd6c09f4c8ebb-image.png

Unlike hashmap, the key of the time wheel is the time scale value, and the time wheel does not perform hash operations

When there are multiple tasks in the time wheel at the same time, you only need to traverse all the linked lists corresponding to the scale and execute the tasks in it.

Of course, the thread scheduled by the clock and the thread executing the task generally need to be decoupled.

Therefore, generally speaking, specific tasks will be thrown into the thread pool for asynchronous execution.

What if the time scale is not enough?

What if the task is not limited to one day?

For example, I have a task that needs to be executed at 9:00 am every Monday, and I have another task that needs to be executed at 9:00 am every Wednesday.

The approximate solution is:

  • Increase the scale of the time wheel
  • Add the round attribute to the tasks in the list
  • Layered time wheel

Increase the scale of the time wheel

One scale is one hour, 24 hours a day, 168 hours a week, in order to solve the problem of insufficient time scales ,

I can increase the ticks (slots) of the time wheel from 12 to 168,

In this way, all the time of the week can be managed with one time wheel.

For example, it's 10:00 AM on Tuesday,

Then, next Monday at 9:00 am is the 9th mark of the time wheel,

Then, next Wednesday at 9:00 am is the 57th mark of the time wheel,

The schematic diagram is as follows:

7499ddb7fa9144d3b5dd6ab9e253256f-image.png

Problems with single-stage timewheels

If you think about it carefully, you will find that there are several flaws in the single-stage time wheel approach:

  • Too many time scales will cause no tasks to be executed on most scales reached by the time wheel.

    For example, there are only 2 tasks in a month, and I have to move 720 times, of which 718 times are useless.

  • Too many time scales will lead to larger storage space and lower utilization

    For example, there are only 2 tasks in a month. I need an array with a size of 720. If the granularity of my execution time is accurate to the second, it will be even more terrifying.

Therefore, in practical applications, the general single time wheel cannot meet the demand.

For example, we need second-level precision, and the maximum delay may be 10 days, so our time wheel needs at least 864,000 grids, which leads to the following problems:

  1. Taking up too much storage
  2. The utilization rate is too low. For example, we only have one task per second, but there are several tasks in seven days. Most of the time, the entire wheel is idling.

How to solve the problem of single-level time wheel?

Solution 1: Add the round attribute to the task

Solution 2: Multi-level time wheel

Add the round attribute to the task

This time, instead of increasing the scale of the time wheel, the scale of the time wheel is still 24. Instead, add a round attribute to the task according to the time interval of the task.

A unit of round, the interval of a single table is a round.

Assumption: Now there are three tasks that need to be performed:

  1. Task one is every Tuesday at 9:00 am.

  2. Task two is every Thursday at 9:00 am.

  3. Task three is at 9 am on the 12th of every month.

For example, it is now 10:00 am on Tuesday, September 11th, and the time cycle is 24 hours. When the task is executed next time (9:00 am next Tuesday),

After 6 laps of the required time wheel, the execution starts at the 9th scale on the 7th lap.

The second task is to execute the 9th scale of the 3rd circle next time, and the task 3 is the 9th scale of the 2nd circle.

The schematic diagram is as follows:

673c2b26f2c94f5ba7b18c2f22d9cf59-image.png

Every time the time wheel moves to a scale, it traverses the task list and processes each task separately:

  • Put the round value -1,
  • If round=0, the task is executed and removed from the list.

Doing this can solve the waste of space caused by the excessive scale range of the time wheel, but it brings another problem:

  • The time wheel needs to traverse the task list every time, and the time consumption increases. When the granularity of the time wheel is small (second level or even millisecond level),
  • When the task list is extremely long, this method of traversal is unacceptable.

Of course, for most scenarios, this method is still applicable.

Is there a way to save both space and time?

The answer is yes, as mentioned in the title of "Hashed and Hierarchical Timing Wheels", there is a hierarchical time wheel that can be solved to save both space and time:

insert image description here

Layered time wheel

Hierarchical time wheel is such an idea:

  1. For time complexity issues:

    No traversal calculation round is performed, all tasks in the task list should be executed, and all tasks are taken out and executed directly.

  2. For the problem of space complexity:

    Hierarchical, each time granularity corresponds to a time round, cascading collaboration between multiple time rounds.

The first point is easy to understand, and the second point needs to be illustrated with an example.

For example, there are three tasks:

  • Task 1: Interval 30s.
  • Task 2: Interval 1 minute 30s.
  • Task 3: The interval is 1 hour, 1 minute and 30 seconds.

Three tasks involve three time units: seconds, minutes, hours.

According to the design of the hierarchical time wheel, we can set three time wheels: the second wheel, the minute wheel, and the hour wheel.

The time scale must first reach the day of the 12th, and then you need to pay attention to its finer time unit: 9 am.

Based on this idea, we can set up three time wheels: the moon wheel, the week wheel, and the sky wheel.

  • The tick duration of the second wheel is seconds. The span span is one minute, 60 seconds.
  • The tick duration of the minute wheel is minutes. The span span is one hour, 60 minutes.
  • The time scale of the hour wheel is tick duration is hours. The span span is one day, 24 hours.

insert image description here

When initially adding tasks:

  • Task one is added to the second wheel,
  • Mission 2 added to the sub-wheel
  • Mission three added to the time wheel.

The three time wheels circulate continuously with their respective time scales.

When the time wheel moves to scale 2 (1st hour), take out the task three under this scale , throw it on the sub-wheel, and the sub-wheel takes over the task.

When the sub-wheel moves to scale 2 (1st minute), take out task 2 under this scale , throw it on the second wheel, and the second wheel takes over the task.

When the second wheel moves to scale 30 (the 30th second), take out task one under this scale , remove this task, and then execute this task.

The overall schematic diagram is as follows:

insert image description here

Time Complexity Analysis of Hierarchical Time Wheel

The hierarchical time wheel algorithm is a data format designed to implement timers more efficiently.

The core requirements of the timer

  1. Added (initialize a timed task)
  2. remove (overdue tasks)
  3. Task expiration detection

What are the possible implementation methods of the timer?

Method 1: PriorityQueue based on small top heap implementation

The internal structure, according to the expiration time, maintains ** a priority queue PriorityQueue **,

The time complexity of insertion and deletion of the priority queue is O(logn) ,

When the amount of data is large, frequent heap entry and exit, the overall performance needs to be considered.

Timer、DelayQueue 和 ScheduledThreadPool

Method 2: Single-level time wheel with round attribute

Since the time span is relatively long, a single-level time wheel generally has a round attribute. A single-level time wheel without a round attribute is basically not used in production scenarios, and time complexity is not considered here.

The essence of a single-level time wheel with a round attribute is an array, and its time span is a time loop

At the same time, each slot will maintain a task list, and each task has a round quantity

The time wheel will take the tick duration interval of the time scale as the unit, start each minimum time interval step by one unit, and then check whether there is a task on the current time wheel node

  • If there is a task, execute it directly
  • If there is no task, wait for the next time interval to step 1 and repeat the detection

Every time the time wheel moves to a scale, it traverses the task list and processes each task separately:

  • Subtract 1 from the round value,
  • If round=0, the task is executed and removed from the list.

Time complexity: pure time wheel - add O(1), remove O(1), detect O(N)

Overall, the time complexity is O(N)

Disadvantage: high time complexity

Method 3: Hierarchical time wheel implements timer

image-20220413174046371

The essence is that multiple time wheels work together, divided into time levels!

Take the above picture as an example, when there is a task on the time wheel, then transfer the task to the corresponding minute time wheel;

When there is a task on the sub-wheel, then the task is transferred to the corresponding second wheel;

When there is a task on the second wheel, then remove the task;

Time complexity: add O(1), remove O(1), detect O(1)

Use of TimerWheel in Caffeine

In addition to supporting expireAfterAccess and expireAfterWrite (Guava Cache also supports these two features), Caffeine also supports expireAfter.

Because both expireAfterAccess and expireAfterWrite can only be a fixed expiration time, in general, this is enough.

However, there are still some special scenarios, such as the expiration time of records, which need to be different according to certain conditions, which requires users to customize the expiration time.

First look at the usage of expireAfter

package com.github.benmanes.caffeine.demo;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.concurrent.TimeUnit;

public class ExpireAfterDemo {
    static System.Logger logger = System.getLogger(ExpireAfterDemo.class.getName());

    public static void hello(String[] args) {
        System.out.println("args = " + args);
    }


    public static void main(String... args) throws Exception {
        Cache<String, String> cache =  Caffeine.newBuilder()
                //最大个数限制
                //最大容量1024个,超过会自动清理空间
                .maximumSize(1024)
                //初始化容量
                .initialCapacity(1)
                //访问后过期(包括读和写)
                //5秒没有读写自动删除
//                .expireAfterAccess(5, TimeUnit.SECONDS)
                //写后过期
//                .expireAfterWrite(2, TimeUnit.HOURS)
                //写后自动异步刷新
//                .refreshAfterWrite(1, TimeUnit.HOURS)
                //记录下缓存的一些统计数据,例如命中率等
                .recordStats()
                .removalListener(((key, value, cause) -> {
                    //清理通知 key,value ==> 键值对   cause ==> 清理原因
                  System.out.println("removed key="+ key);
                }))
                .expireAfter(new Expiry<String, String>() {
                    //返回创建后的过期时间
                    @Override
                    public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) {
                        System.out.println("1. expireAfterCreate key="+ key);
                        return 0;
                    }

                    //返回更新后的过期时间
                    @Override
                    public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
                        System.out.println("2. expireAfterUpdate key="+ key);
                        return 0;
                    }

                    //返回读取后的过期时间
                    @Override
                    public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
                        System.out.println("3. expireAfterRead key="+ key);
                        return 0;
                    }
                })
                .recordStats()
                //使用CacheLoader创建一个LoadingCache
                .build(new CacheLoader<String, String>() {
                    //同步加载数据
                    @Nullable
                    @Override
                    public String load(@NonNull String key) throws Exception {
                        System.out.println("loading  key="+ key);
                        return "value_" + key;
                    }

                    //异步加载数据
                    @Nullable
                    @Override
                    public String reload(@NonNull String key, @NonNull String oldValue) throws Exception {
                        System.out.println("reloading  key="+ key);
                        return "value_" + key;
                    }
                });

        //添加值
        cache.put("name", "疯狂创客圈");
        cache.put("key", "一个高并发 研究社群");

        //获取值
        @Nullable String value = cache.getIfPresent("name");
        System.out.println("value = " + value);
        //remove
        cache.invalidate("name");
        value = cache.getIfPresent("name");
        System.out.println("value = " + value);
    }

}

By customizing the expiration time, different keys can dynamically obtain different expiration times.

You need to annotate expireAfterAccess and expireAfterWrite, because these two features cannot be used together with expireAfter. You can only choose one of the two. From the source code, you can also see this:

insert image description here

When the expireAfter feature is used, Caffeine will enable an algorithm called "time wheel" to achieve this function.

Why Caffeine uses the time wheel

Well, here comes the point, why use the time wheel?

The implementation of expireAfterAccess and expireAfterWrite uses an AccessOrderDeque double-ended queue, which is FIFO

Because their expiration time is fixed , in terms of time dimension, the cache record Node node that enters the queue first is at the head.

The latest node, or the latest entered cache record Node node, is at the end.

insert image description here

Since each insertion is at the end, it can be understood that this queue implies a rule:

Elements are ordered by expiration time

So, the conclusion is:

The data at the head of the queue must expire the earliest, and the data at the end of the queue must expire the latest.

When dealing with expired data, you only need to first check whether the header is expired, and then check one by one.

However, this queue has a requirement:

The expiration time of each node requires the same

If the expiration time is different, how to check the expiration time?

This requires traversal. Another way is to sort & insert the double-ended queue accessOrderQueue. The time complexity is not O(1).

Therefore, Caffeine uses a more efficient and elegant algorithm-time wheel.

Source code analysis of Caffeine's five-level time wheel

insert image description here

Important properties and methods

The specific implementation of the time wheel is a two-dimensional array, and the specific location of the array is stored as a linked list of nodes to be executed.

The first dimension of the two-dimensional array of the time wheel is the specific time interval, which are seconds, minutes, hours, days, and 4 days, but it does not strictly distinguish the units according to the time unit, but according to the closest unit of the above The integer power of 2 is used as the time interval, so the time intervals in the first dimension are 1.07s, 1.14m, 1.22h, 1.63d, 6.5d.


  static final int[] BUCKETS = { 64, 64, 32, 4, 1 };
  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
  };

When a specific time event is to be added to the time wheel, it will first locate the first dimension of the two-dimensional array according to the closest unit of the event to the current time, which will be explained later.

The reason for choosing the nearest integer of 2 instead of a specific time integer is that the change of time rolling in the first dimension of the two-dimensional array can be quickly obtained through shift comparison.

In the time wheel, the offsets of the above time intervals are recorded in detail. In the time wheel, the difference between the current time and the last time is calculated and the digits of SHIFT are continuously shifted to the right, so that the time change can be quickly located.


  //Long.numberOfTrailingZeros(value1) 返回最左侧之后的0位数,  10100010000 》》》 4
  static final long[] SHIFT = {
      Long.numberOfTrailingZeros(SPANS[0]),
      Long.numberOfTrailingZeros(SPANS[1]),
      Long.numberOfTrailingZeros(SPANS[2]),
      Long.numberOfTrailingZeros(SPANS[3]),
      Long.numberOfTrailingZeros(SPANS[4]),
  };

  final Node<K, V>[][] wheel;

  long nanos;

Take simple numbers as an example, the first dimension is 1s, 10s, 100s, 1000s,

Then there are four slots in the specific first dimension, which are stored within 10s, within 10s to 100s, within 100s to 1000s, and after 1000s. When a time event that occurs after 300s enters, the first difference is 300 , compared in turn, it is obvious that 300 is greater than 100 and less than 1000, then the event that occurs after 300s is in the third position of the first dimension of the two-dimensional array on the time wheel.

Among them, 1s is used to mark the minimum time scale between expired operations, and does not participate in the positioning of time events, analogous to 1.07s in the time wheel.

Please see the video "Chapter 25: Penetrating Caffeine's Architecture and Source Code Analysis"

Constructor

Initialize the five-level time wheel, each time wheel is an array.


  @SuppressWarnings({"rawtypes", "unchecked"})
  TimerWheel() {
    wheel = new Node[BUCKETS.length][];
    for (int i = 0; i < wheel.length; i++) {
      wheel[i] = new Node[BUCKETS[i]];
      for (int j = 0; j < wheel[i].length; j++) {
        wheel[i][j] = new Sentinel<>();
      }
    }
    System.out.println("BUCKETS = " + Arrays.toString(BUCKETS));
    System.out.println("SPANS = " +Arrays.toString( SPANS));
    System.out.println("SHIFT = " + Arrays.toString(SHIFT));
    System.out.println("Long.toBinaryString(SPANS[0]) = " + Long.toBinaryString(SPANS[0]));
    System.out.println("wheel = " + wheel);
  }

Please see the video "Chapter 25: Penetrating Caffeine's Architecture and Source Code Analysis"

time stepping

/**
   * Advances the timer and evicts entries that have expired.
   *
   * @param cache the instance that the entries belong to
   * @param currentTimeNanos the current time, in nanoseconds
   */
  public void advance(BoundedLocalCache<K, V> cache, long currentTimeNanos) {
    long previousTimeNanos = nanos;
    nanos = currentTimeNanos;

    // If wrapping then temporarily shift the clock for a positive comparison. We assume that the
    // advancements never exceed a total running time of Long.MAX_VALUE nanoseconds (292 years)
    // so that an overflow only occurs due to using an arbitrary origin time (System.nanoTime()).
    if ((previousTimeNanos < 0) && (currentTimeNanos > 0)) {
      previousTimeNanos += Long.MAX_VALUE;
      currentTimeNanos += Long.MAX_VALUE;
    }

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

task scheduling

First find the corresponding time wheel, and the slot inside the time wheel

Then via sentinel, insert the task

  * @param node the entry in the cache
   */
  public void schedule(Node<K, V> node) {
    Node<K, V> sentinel = findBucket(node.getVariableTime());
    link(sentinel, node);
  }

Find the corresponding time wheel, and the slot inside the time wheel

 * @param time the time when the event fires
   * @return the sentinel at the head of the bucket
   */
  Node<K, V> findBucket(long time) {
    long duration = time - nanos;
    int length = wheel.length - 1;
    for (int i = 0; i < length; i++) {
      if (duration < SPANS[i + 1]) {
        long ticks = (time >>> SHIFT[i]);
        int index = (int) (ticks & (wheel[i].length - 1));
        return wheel[i][index];
      }
    }
    return wheel[length][0];
  }

Please see the video "Chapter 25: Penetrating Caffeine's Architecture and Source Code Analysis"

Time wheel in Dubbo source code

Later, we will analyze the time wheel in the Dubbo source code against the time wheel of Caffeine

to be continued

Time wheel in XXL-Job source code

Later, we will compare the time wheel of Caffeine and analyze the time wheel in the XXL-Job source code

to be continued

references

https://www.likecs.com/show-204434429.html

http://www.cs.columbia.edu/~nahum/w6998/papers/sosp87-timing-wheels.pdf

https://blog.csdn.net/xinzhongtianxia/article/details/86221241

https://blog.csdn.net/m0_37039331/article/details/87401758

https://blog.csdn.net/qq924862077/article/details/112550085

https://baijiahao.baidu.com/s?id=1714290103234167995

https://www.cnblogs.com/smileIce/p/11156412.html

https://blog.csdn.net/bz120413/article/details/122107790

https://blog.csdn.net/Javaesandyou/article/details/123918852

https://blog.csdn.net/Javaesandyou/article/details/123918852

https://blog.csdn.net/FreeeLinux/article/details/54897192

https://blog.csdn.net/weixin_41605937/article/details/121972371

Recommended reading:

Guess you like

Origin blog.csdn.net/crazymakercircle/article/details/128205683