实现思路

首先针对架构设计,介绍比较常用的几个思路

主动轮询

这是实现定时器最简单粗暴的方式:轮询 + 触发,主要流程有:

  1. 注册定时器:按显式的任务执行时间,制定一系列定时任务

  2. 节点自轮询:每间隔一个微小的时间范围,对定时任务列表进行全量查询

  3. 过滤&触发:以执行时间小于等于当前时刻作为过滤条件,摘出满足执行条件的定时任务进行执行

但这样的定时器需要承担 O(N) 的查询时间复杂度

存储结构优化

有序表

基于有序表提升查询效率

即利用存储结构,将时间复杂度均摊到每一笔操作中。例如使用红黑树或跳表,通过牺牲插入时间复杂度,降低查询时间复杂度

此处选用 Redis ZSet,以定时任务执行时间为 Score 进行有序结构的搭建,实现流程如下:

  1. 以 Redis ZSet 作为存储介质(即所有元素始终按照 Score 排序好)
  2. 每次添加定时任务时,执行 ZAdd 动作,以执行时间的时间戳作为排序的键(Score)进行有序结构的搭建
  3. 每次查询定时任务时,执行 ZRangeByScore 动作,以当前时刻的时间戳加上一个微小偏移量(1s)作为 Score 的左右边界(因为要选择的是某一段时间已经到期需要执行的任务)

横向分治

通过时间范围分片,减少查询设计的任务数量

当每次查询的时候,其实是在查找将要执行的任务,也就是当前时间点以及一段时间片内的任务,这是即将执行的任务,是真正的目标。在此之后的任务在查找过程没有实际用途且增大了数据规模

因此可以将整个时间线按照时间片进行切割,将和当前时刻同属一个时间片的任务视为即将执行的任务,而从下一个时间片开始往后的任务都视为低优先级的定时任务

在具体实现中,我们可以采取1分钟作为分片的范围,实现流程如下:

  1. 插入每笔定时任务时,根据执行时间推算出所属分钟级范围表达式,如2025-12-22-2:13:26属于2025-12-22-2:13:00 -> 2025-12-22-2:14:00