[Dry goods sharing] What an interesting time wheel

foreword

If there is a requirement that a task needs to be retried after the execution fails, the number of retries and the time point are configurable, so how to achieve it? A nice way to do this is through the time wheel .

what is time wheel

Time Wheel is a common timing task scheduling algorithm. It divides a period of time into several discrete time slots and rotates clockwise at fixed time intervals to trigger and execute tasks that should be executed at a specific time point.

time wheel.png

Common time wheel application scenarios

  • Scheduled task scheduling : The time wheel is very suitable for scheduling scheduled tasks. By dividing tasks into different slots according to the trigger time, precise triggering and execution of tasks can be achieved. For example, in a distributed system, the time wheel can be used to trigger and schedule scheduled tasks.

  • Timeout management : In network communication or distributed systems, it is often necessary to manage request timeouts. The time wheel can be used to manage and handle overtime tasks. Each slot can store a timeout request and trigger corresponding actions when the timeout period is reached, such as resending the request or handling exceptions.

  • Timer : The time wheel can be used to implement the timer function. By adding the timing task to the corresponding slot of the time wheel, the execution of the timing task can be triggered at a predetermined time point. Timers are widely used in various needs, such as batch processing, timing reminders, timing data refresh, etc.

  • Scheduler : The time wheel can be used to implement a task scheduler. By dividing tasks into different slots according to their priorities, it is possible to trigger the execution of tasks in order of priority. This is very useful in some scenarios where urgent tasks need to be prioritized.

  • Cache invalidation management : In the cache system, it is necessary to manage the cache invalidation time. The time wheel can be used to manage and handle cache invalidation tasks. Each slot can store a cache invalidation item, and trigger corresponding operations when the expiration time arrives, such as updating the cache or reloading data.

By using the time wheel reasonably, the task scheduling efficiency and execution accuracy of the system can be improved.

Timed task scheduling to see the time wheel

xxl-job should be familiar to most friends, xxl-job is a distributed task scheduling platform. Its core design goals are rapid development, easy learning, lightweight, and easy expansion. The source code is now open and connected to the online product lines of many companies, out of the box.

Let's see how the time wheel is used in xxl-job.

Time wheel in xxl-job

 
 

java

copy code

private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();

That's right, this line of code is the time wheel in xxl-job, which is essentially one ConcurrentHashMap. The key is the number of seconds to execute, and the value is the id list of the job to be executed .

So ConcurrentHashMaphow is the data in the database maintained and managed?

Let's take a look at how to do it in xxl-job, JobScheduleHelperclass.

xxl-job source code

 
 

java

copy code

private Thread scheduleThread; private Thread ringThread;

Two threads are used in xxl-job:

  • scheduleThread thread: read ahead, calculate the next scheduling time, expired tasks are processed according to the configuration policy, tasks within 5 seconds of expiration are put into the thread pool, and unexpired tasks are put into the time wheel.
  • ringThread thread: time wheel scheduling, the rotation of the time wheel triggers task scheduling.

scheduleThread thread

There are a lot of source codes, and only some main codes are kept.

 
 

java

copy code

scheduleThread = new Thread(new Runnable() { @Override public void run() { // 时间对齐,因为预读读的是每次读取现在开始的未来5s内的任务 TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis() % 1000); logger.info(">>>>>>>>> init xxl-job admin scheduler success."); // 计算预读数量: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20) int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20; while (!scheduleThreadToStop) { // 扫描任务开始时间 long start = System.currentTimeMillis(); Connection conn = null; Boolean connAutoCommit = null; PreparedStatement preparedStatement = null; boolean preReadSuc = true; conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection(); connAutoCommit = conn.getAutoCommit(); conn.setAutoCommit(false); // 悲观锁。集群部署的话可能存在性能瓶颈 preparedStatement = conn.prepareStatement("select * from xxl_job_lock where lock_name = 'schedule_lock' for update"); preparedStatement.execute(); // 预读开始 long nowTime = System.currentTimeMillis(); // 查询任务信息表,当前时间为基准,读未来5s需要执行的任务 List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount); if (scheduleList != null && scheduleList.size() > 0) { // 2、push time-ring for (XxlJobInfo jobInfo : scheduleList) { if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) { // 2.1、当前任务过期大于5s logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId()); // 1、匹配过期策略 MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING); if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) { // 执行策略 JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null); logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId()); } // 2、计算下一次调度时间 refreshNextValidTime(jobInfo, new Date()); } else if (nowTime > jobInfo.getTriggerNextTime()) { // 2.2、当前任务过期小于5s // 1、生成任务线程放入线程池 JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null); logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId()); // 2、计算下一次调度时间 refreshNextValidTime(jobInfo, new Date()); // 下一次调度时间在5s内 if (jobInfo.getTriggerStatus() == 1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) { // 1、计算时间轮槽 int ringSecond = (int) ((jobInfo.getTriggerNextTime() / 1000) % 60); // 2、任务放入时间轮 pushTimeRing(ringSecond, jobInfo.getId()); // 3、计算下一次调度时间 refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime())); } } else { // 2.3、任务未过期 // 1、计算时间轮槽 int ringSecond = (int) ((jobInfo.getTriggerNextTime() / 1000) % 60); // 2、任务放入时间轮 pushTimeRing(ringSecond, jobInfo.getId()); // 3、计算下一次调度时间 refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime())); } } // 3、更新任务的调度信息 for (XxlJobInfo jobInfo : scheduleList) { XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo); } } else { preReadSuc = false; } // tx stop long cost = System.currentTimeMillis() - start; if (cost < 1000) { // scan-overtime, not wait // 本次调度执行时间小于1s,时间对齐,再进入下一轮调度 TimeUnit.MILLISECONDS.sleep((preReadSuc ? 1000 : PRE_READ_MS) - System.currentTimeMillis() % 1000); } } logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop"); } }); scheduleThread.setDaemon(true); scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread"); scheduleThread.start();

image.png

ringThread thread

 
 

java

copy code

ringThread = new Thread(new Runnable() { @Override public void run() { while (!ringThreadToStop) { // 时间对齐 TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000); try { // second data List<Integer> ringItemData = new ArrayList<>(); // 获取当前时间的秒 int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度; for (int i = 0; i < 2; i++) { List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 ); if (tmpData != null) { ringItemData.addAll(tmpData); } } // 时间轮调度 logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) ); if (ringItemData.size() > 0) { // 调度 for (int jobId: ringItemData) { // 执行调度 JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null); } // 清除,避免重复执行 ringItemData.clear(); } } catch (Exception e) { if (!ringThreadToStop) { logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e); } } } logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop"); } }); ringThread.setDaemon(true); ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread"); ringThread.start();

Simple Time Wheel:

The simple time wheel consists of a circular array and a pointer, and each slot stores a linked list. The pointer rotates a slot clockwise within a fixed time interval, triggering and executing the task corresponding to the slot. This time wheel is suitable for scenarios where tasks are triggered frequently. This is the time wheel of the xxl-job analyzed above.

time scale problem

Now if 1 second is used as a time scale, then there will be 86,400 scales in one day. If you add a task to execute after 80,000 seconds, then most of the polls will be empty polls, and memory space will be wasted (each time scale is has its own task queue).

So how to solve this problem?

With round time wheel

Add a round attribute to each task, and each time the time wheel moves a time scale, traverse the task queue to take out the task with round = 0 for execution, and then round - 1 the rest of the tasks.

The problem of memory space is solved, and there is no need to create so many task queues, but it is time-consuming to scan all the tasks in the task queue every time it is rotated.

Hierarchical Time Wheel:

The hierarchical time wheel is an extension of the simple time wheel, which divides the time wheel into multiple levels (Levels) according to different precisions. The time wheel of each level is wider than the time interval of the previous level. When the low-level time wheel is triggered, the corresponding task is added to the high-level time wheel to realize the delayed triggering of the task. This kind of time wheel is suitable for task scheduling that needs to support a long time range and has high trigger accuracy. Tasks shift positions in the queue by promoting and demoting them. CaffeineThis wheel is used in .

edGYujrkkn.jpg

Time Heap:

The time heap is a time wheel implemented by a priority queue. Tasks are inserted into the priority queue in the order of trigger time, and the most recently triggered tasks are taken out of the queue for execution each time. The time complexity of adding and deleting tasks is O(log n), which is suitable for scenarios where there are few task triggers and precise trigger time is required.

Combination of time wheel and time heap:

Some implementations combine time wheels and time heaps to balance trigger accuracy and time complexity. For example, the time wheel can be used as a large-scale scheduler to support fast trigger tasks; and for tasks that require more precise trigger time, the time heap can be used for management.

Guess you like

Origin blog.csdn.net/wdj_yyds/article/details/132364898