时间事件(Time Event)源码详解
一、概述
Redis 的事件驱动机制中,**时间事件(Time Event)**是与文件事件并列的另一类核心事件。时间事件用于处理定时任务,如服务器定时维护、客户端超时检测、AOF 持久化、主从复制心跳等。Redis 通过时间事件实现了一个轻量级的定时器机制,在单线程中高效地管理各类周期性任务。
本文将深入剖析 Redis 时间事件的实现原理,从数据结构到核心 API,再到实际应用场景。
二、核心数据结构
2.1 时间事件结构体 aeTimeEvent
1// src/ae.h:78-88
2typedef struct aeTimeEvent {
3 long long id; // 时间事件唯一标识符,全局递增
4 long when_sec; // 触发时间的秒数部分(绝对时间)
5 long when_ms; // 触发时间的毫秒数部分(绝对时间)
6 aeTimeProc *timeProc; // 时间事件回调函数
7 aeEventFinalizerProc *finalizerProc; // 事件销毁时的清理函数(可选)
8 void *clientData; // 用户自定义数据,传递给回调函数
9 struct aeTimeEvent *next; // 指向下一个时间事件,形成单向链表
10} aeTimeEvent;
字段解析:
id:时间事件的唯一标识符,全局递增,用于删除特定事件when_sec、when_ms:事件触发的绝对时间(秒 + 毫秒)timeProc:事件触发时的回调函数,返回值决定事件是否继续周期执行finalizerProc:事件被删除时的清理回调(可选)clientData:传递给回调函数的用户数据next:指向下一个时间事件节点,时间事件以无序链表组织
2.2 事件循环中的时间事件
1// src/ae.h:97-109
2typedef struct aeEventLoop {
3 int maxfd; // 当前注册的最大文件描述符
4 int setsize; // 事件表容量上限
5 long long timeEventNextId; // 下一个时间事件的 ID(全局递增计数器)
6 time_t lastTime; // 上次处理时间事件的时间,用于检测时钟偏移
7 aeFileEvent *events; // 注册的文件事件表
8 aeFiredEvent *fired; // 已触发的文件事件表
9 aeTimeEvent *timeEventHead; // 时间事件链表头节点
10 int stop; // 停止标志
11 void *apidata; // 多路复用 API 的私有数据
12 aeBeforeSleepProc *beforesleep; // 事件循环前回调
13 aeBeforeSleepProc *aftersleep; // 事件循环后回调
14} aeEventLoop;
关键点:
timeEventNextId:全局递增的事件 ID 计数器timeEventHead:时间事件链表的头节点,所有时间事件通过链表串联lastTime:记录上次处理时间事件的时间,用于检测系统时钟偏移
三、时间事件的类型
Redis 的时间事件主要分为两类:
3.1 一次性时间事件
事件触发执行后自动删除,不再重复执行。通过回调函数返回 AE_NOMORE 实现:
1// src/ae.h:51
2#define AE_NOMORE -1
3.2 周期性时间事件
事件触发执行后,根据回调函数的返回值重新计算下次触发时间,实现周期执行。返回值为距离下次触发的毫秒数。
Redis 中最典型的周期性时间事件是 serverCron,默认每 100ms 执行一次。
四、核心 API 实现
4.1 创建时间事件 aeCreateTimeEvent
1// src/ae.c:178-203
2long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
3 aeTimeProc *proc, void *clientData,
4 aeEventFinalizerProc *finalizerProc)
5{
6 long long id = eventLoop->timeEventNextId++; // 分配全局唯一 ID
7 aeTimeEvent *te;
8
9 te = zmalloc(sizeof(*te)); // 分配时间事件结构体内存
10 if (te == NULL) return AE_ERR;
11 te->id = id;
12 // 计算触发的绝对时间:当前时间 + 延迟毫秒数
13 aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
14 te->timeProc = proc; // 设置时间事件回调函数
15 te->finalizerProc = finalizerProc; // 设置销毁时的清理函数
16 te->clientData = clientData; // 设置用户自定义数据
17 // 头插法插入链表:新事件插入到链表头部
18 te->next = eventLoop->timeEventHead;
19 eventLoop->timeEventHead = te;
20 return id; // 返回事件 ID,用于后续删除操作
21}
执行流程:
- 分配全局唯一的事件 ID
- 分配并初始化时间事件结构体
- 计算事件触发的绝对时间
- 将事件插入链表头部
- 返回事件 ID
4.2 删除时间事件 aeDeleteTimeEvent
1// src/ae.c:205-232
2int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
3{
4 aeTimeEvent *te = eventLoop->timeEventHead; // 从链表头开始遍历
5 aeTimeEvent *prev = NULL; // 记录前驱节点,用于删除操作
6
7 // 遍历链表查找指定 ID 的时间事件
8 while(te) {
9 if (te->id == id) {
10 // 找到目标事件,执行删除
11 if (prev == NULL)
12 // 删除的是头节点,更新头指针指向下一个节点
13 eventLoop->timeEventHead = te->next;
14 else
15 // 删除中间或尾部节点,调整前驱的 next 指针
16 prev->next = te->next;
17 // 如果设置了清理函数,则调用它释放相关资源
18 if (te->finalizerProc)
19 te->finalizerProc(eventLoop, te->clientData);
20 zfree(te); // 释放时间事件结构体内存
21 return AE_OK;
22 }
23 prev = te; // 记录当前节点作为前驱
24 te = te->next; // 移动到下一个节点
25 }
26 return AE_ERR; /* NO event with the specified ID found */
27}
执行流程:
- 遍历时间事件链表
- 根据事件 ID 查找目标事件
- 从链表中移除该事件
- 调用清理函数(如有)
- 释放内存
4.3 查找最近的时间事件 aeSearchNearestTimer
1// src/ae.c:234-252
2static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
3{
4 aeTimeEvent *te = eventLoop->timeEventHead; // 从链表头开始遍历
5 aeTimeEvent *nearest = NULL; // 记录最近的时间事件
6
7 // 遍历整个链表,找到触发时间最早的事件
8 while(te) {
9 // 比较 when_sec 和 when_ms,找到最小的时间
10 if (!nearest || te->when_sec < nearest->when_sec ||
11 (te->when_sec == nearest->when_sec &&
12 te->when_ms < nearest->when_ms))
13 nearest = te; // 更新最近事件
14 te = te->next;
15 }
16 return nearest; // 返回最近的时间事件,可能为 NULL
17}
说明:
- 时间事件链表是无序的,需要遍历查找最早触发的事件
- 该函数用于计算
aeApiPoll的超时时间,确保不会错过时间事件 - 时间复杂度 O(n),但 Redis 通常只有 1 个时间事件,开销可忽略
4.4 处理时间事件 processTimeEvents
1// src/ae.c:254-336
2static int processTimeEvents(aeEventLoop *eventLoop) {
3 int processed = 0; // 统计已处理的事件数量
4 aeTimeEvent *te;
5 long long maxId;
6 time_t now = time(NULL); // 获取当前时间(秒)
7
8 // 检测系统时钟是否被向后调整(时钟回退)
9 /* If the system clock is moved to the future, and then set back to the
10 * right value, time events may be delayed in a random way. Often this
11 * means that scheduled operations will not be performed fast enough.
12 *
13 * Here we try to detect system clock skews, and force a timeout of all
14 * the time events that are to be processed in the next 1 second, so
15 * that such events will be processed ASAP. */
16 if (now < eventLoop->lastTime) {
17 // 时钟被向后调整,立即触发所有时间事件
18 // 将所有事件的触发时间设为 0,使其立即执行
19 te = eventLoop->timeEventHead;
20 while(te) {
21 te->when_sec = 0; // 设置为立即触发
22 te = te->next;
23 }
24 }
25 eventLoop->lastTime = now; // 更新上次处理时间
26
27 // 遍历处理所有到期的时间事件
28 te = eventLoop->timeEventHead;
29 maxId = eventLoop->timeEventNextId-1; // 记录当前最大 ID
30 while(te) {
31 long long id;
32
33 // 跳过在本次处理过程中新创建的事件(ID > maxId)
34 // 防止新事件在本次循环中被立即处理
35 if (te->id > maxId) {
36 te = te->next;
37 continue;
38 }
39
40 // 获取当前精确时间(秒 + 毫秒)
41 // aeGetTime(&now, &now_ms); // 实际代码中有这行
42 // 检查事件是否已到期:触发时间 <= 当前时间
43 if (te->when_sec < now ||
44 (te->when_sec == now && te->when_ms <= now_ms))
45 {
46 int retval;
47 id = te->id;
48 // 调用时间事件回调函数
49 retval = te->timeProc(eventLoop, id, te->clientData);
50 processed++; // 统计已处理的事件数
51
52 // 根据返回值决定事件是否继续
53 if (retval != AE_NOMORE) {
54 // 周期事件:重新计算下次触发时间
55 // retval 为距离下次触发的毫秒数
56 aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
57 } else {
58 // 一次性事件:返回 AE_NOMORE,删除事件
59 aeDeleteTimeEvent(eventLoop, id);
60 }
61 // 事件链表可能已被修改(如删除了事件),重新从头遍历
62 te = eventLoop->timeEventHead;
63 } else {
64 // 事件未到期,继续检查下一个
65 te = te->next;
66 }
67 }
68 return processed; // 返回已处理的事件数量
69}
执行流程:
- 检测系统时钟偏移,处理时钟回退情况
- 遍历时间事件链表
- 检查每个事件是否已到期
- 到期事件:调用回调函数,根据返回值决定是否继续
- 返回处理的事件数量
4.5 计算超时时间
在 aeProcessEvents 中,会根据最近的时间事件计算 aeApiPoll 的超时时间:
1// src/ae.c:365-407
2 // 判断是否需要计算超时时间
3 if (eventLoop->maxfd != -1 ||
4 ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
5 int j;
6 aeTimeEvent *shortest = NULL; // 最近的时间事件
7 struct timeval tv, *tvp;
8
9 // 查找最近的时间事件
10 if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
11 shortest = aeSearchNearestTimer(eventLoop);
12 if (shortest) {
13 // 存在时间事件,计算超时时间
14 long now_sec, now_ms;
15
16 aeGetTime(&now_sec, &now_ms); // 获取当前时间
17 tvp = &tv;
18
19 /* How many milliseconds we need to wait for the next
20 * time event to fire? */
21 // 计算距离最近时间事件还有多少毫秒
22 long long ms =
23 (shortest->when_sec - now_sec)*1000 +
24 shortest->when_ms - now_ms;
25
26 if (ms > 0) {
27 // 还有剩余时间,设置为超时值
28 tvp->tv_sec = ms/1000; // 秒部分
29 tvp->tv_usec = (ms % 1000)*1000; // 微秒部分
30 } else {
31 // 时间已到,立即返回不阻塞
32 tvp->tv_sec = 0;
33 tvp->tv_usec = 0;
34 }
35 } else {
36 /* If we have to check for events but need to return
37 * ASAP because of AE_DONT_WAIT we need to set the timeout
38 * to zero */
39 // 没有时间事件
40 if (flags & AE_DONT_WAIT) {
41 // 设置了不等待标志,立即返回
42 tv.tv_sec = tv.tv_usec = 0;
43 tvp = &tv;
44 } else {
45 /* Otherwise we can block */
46 // 没有时间事件且允许等待,无限阻塞直到有文件事件
47 tvp = NULL; /* wait forever */
48 }
49 }
50
51 /* Call the multiplexing API, will return only on timeout or when
52 * some event fires. */
53 // 调用 I/O 多路复用,等待文件事件或超时
54 numevents = aeApiPoll(eventLoop, tvp);
说明:
- 如果有时间事件,计算最近事件的剩余时间作为超时值
- 如果没有时间事件,可能无限等待(直到有文件事件)
- 如果设置了
AE_DONT_WAIT,超时为 0,立即返回
五、时间事件在 Redis 中的应用
5.1 serverCron 的注册
Redis 启动时,会在 initServer 中注册 serverCron 时间事件:
1// src/server.c:2115-2117
2 /* Create the timer callback, this is our way to process many background
3 * operations incrementally, like clients timeout, eviction of unaccessed
4 * expired keys and so forth. */
5 // 创建定时器回调,这是处理后台任务的核心机制
6 // 包括客户端超时、过期键清理等
7 if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
8 serverPanic("Can't create event loop timers.");
9 exit(1);
10 }
serverCron 是 Redis 的核心定时任务,负责:
- 更新服务器时间缓存
- 处理客户端超时
- 执行过期键清理
- 触发 AOF 重写/RDB 保存
- 主从复制心跳
- 集群心跳
- 等等…
5.2 serverCron 的实现
1// src/server.c:1090-1352
2int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
3 int j;
4 UNUSED(eventLoop);
5 UNUSED(id);
6 UNUSED(clientData);
7
8 /* Software watchdog: deliver the SIGALRM that will reach the signal
9 * handler if we don't return here fast enough. */
10 // 软件看门狗:如果 serverCron 执行超时,发送 SIGALRM 信号
11 if (server.watchdog_period > 0) watchdogScheduleSignal(server.watchdog_period);
12
13 /* Update the time cache. */
14 updateCachedTime(1); // 更新服务器时间缓存,避免频繁调用 time()
15 server.lruclock = getLRUClock(); // 更新 LRU 时钟,用于键淘汰
16
17 /* Record the max memory used since the server was started. */
18 // 记录峰值内存使用量
19 if (zmalloc_used_memory() > server.stat_peak_memory)
20 server.stat_peak_memory = zmalloc_used_memory();
21
22 /* Sample the RSS here since this is a relatively slow call. */
23 server.resident_set_size = zmalloc_get_rss(); // 获取进程实际物理内存
24
25 /* We received a SIGTERM, shutting down here in a safe way, as it is
26 * ok not to do anything but exit in an asyncronous event handler. */
27 // 处理 SIGTERM 信号,安全关闭服务器
28 if (server.shutdown_asap) {
29 if (prepareForShutdown(SHUTDOWN_NOFLAGS) == C_OK) exit(0);
30 serverLog(LL_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
31 server.shutdown_asap = 0;
32 }
33
34 /* Show some info about non-empty databases */
35 // 每 5 秒打印一次数据库统计信息
36 run_with_period(5000) {
37 for (j = 0; j < server.dbnum; j++) {
38 long long size, used, vkeys;
39
40 size = dictSlots(server.db[j].dict); // 哈希表槽位数
41 used = dictSize(server.db[j].dict); // 已使用键数量
42 vkeys = dictSize(server.db[j].expires); // 设置过期时间的键数量
43 if (used || vkeys) {
44 serverLog(LL_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size);
45 }
46 }
47 }
48
49 /* Show information about connected clients */
50 // 每 5 秒打印一次客户端连接信息
51 if (!server.sentinel_mode) {
52 run_with_period(5000) {
53 serverLog(LL_VERBOSE,
54 "%lu clients connected (%lu replicas), %zu bytes in use",
55 listLength(server.clients)-listLength(server.slaves),
56 listLength(server.slaves),
57 zmalloc_used_memory());
58 }
59 }
60
61 /* We need to do a few operations on clients asynchronously. */
62 clientsCron(); // 异步处理客户端相关任务:超时检测、输入输出缓冲区检查
63
64 /* Handle background operations on Redis databases. */
65 databasesCron(); // 数据库后台任务:过期键清理、字典 rehash
66
67 /* Start a scheduled AOF rewrite if this was requested by the user while
68 * a BGSAVE was in progress. */
69 // 如果 AOF 重写被延迟(因为当时有 BGSAVE),现在尝试执行
70 if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
71 server.aof_rewrite_scheduled)
72 {
73 rewriteAppendOnlyFileBackground();
74 }
75
76 /* Check if a background saving or rewrite in progress terminated. */
77 // 检查后台子进程是否结束(RDB 或 AOF)
78 if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
79 ldbPendingChildren())
80 {
81 int statloc;
82 pid_t pid;
83
84 // 非阻塞等待子进程结束
85 if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
86 int exitcode = WEXITSTATUS(statloc); // 退出码
87 int bysignal = 0;
88
89 if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc); // 终止信号
90
91 if (pid == -1) {
92 // wait3 出错
93 serverLog(LL_WARNING,"wait3() returned an error: %s. "
94 "rdb_child_pid = %d, aof_child_pid = %d",
95 strerror(errno),
96 (int) server.rdb_child_pid,
97 (int) server.aof_child_pid);
98 } else if (pid == server.rdb_child_pid) {
99 // RDB 后台保存完成
100 backgroundSaveDoneHandler(exitcode,bysignal);
101 } else if (pid == server.aof_child_pid) {
102 // AOF 后台重写完成
103 backgroundRewriteDoneHandler(exitcode,bysignal);
104 } else {
105 if (!ldbPendingChildren()) {
106 serverLog(LL_WARNING,
107 "Warning, detected child with unmatched pid: %ld",
108 (long)pid);
109 }
110 }
111 updateDictResizePolicy(); // 根据是否有子进程调整字典 rehash 策略
112 }
113 } else {
114 /* If there is not a background saving/rewrite in progress check if
115 * we have to save/rewrite now */
116 // 没有后台任务,检查是否需要执行 RDB 保存
117 for (j = 0; j < server.saveparamslen; j++) {
118 struct saveparam *sp = server.saveparams+j;
119
120 /* saved < changes && elapsed >= seconds */
121 // 检查 save 配置条件:变更数 >= sp->changes 且时间 >= sp->seconds
122 if (server.dirty >= sp->changes &&
123 server.unixtime-server.lastsave > sp->seconds) {
124 serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
125 sp->changes, (int)sp->seconds);
126 rdbSaveInfo rsi, *rsiptr;
127 rsiptr = rdbPopulateSaveInfo(&rsi);
128 rdbSaveBackground(server.rdb_filename,rsiptr);
129 break;
130 }
131 }
132
133 /* Trigger an AOF rewrite if needed */
134 // 检查是否需要触发 AOF 重写
135 if (server.rdb_child_pid == -1 &&
136 server.aof_child_pid == -1 &&
137 server.aof_rewrite_perc &&
138 server.aof_current_size > server.aof_rewrite_min_size)
139 {
140 // AOF 文件增长率超过阈值时触发重写
141 long long baseline = server.aof_rewrite_base_size ?
142 server.aof_rewrite_base_size : 1;
143 long long growth = (server.aof_current_size*100/baseline) - 100;
144 if (growth >= server.aof_rewrite_perc) {
145 serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
146 rewriteAppendOnlyFileBackground();
147 }
148 }
149 }
150 // ... 更多任务
151}
5.3 serverCron 的返回值
serverCron 返回下次执行的间隔时间(毫秒):
1// src/server.c:1351-1352
2 server.cronloops++; // 增加循环计数器,用于 run_with_period 宏
3 return 1000/server.hz; // 返回下次执行的毫秒数
说明:
server.hz默认值为 10,表示每秒执行 10 次- 返回
1000/10 = 100,即每 100ms 执行一次 - 可通过配置文件调整
hz参数,影响定时任务的执行频率
5.4 run_with_period 宏
serverCron 中大量使用 run_with_period 宏来控制任务执行频率:
1// src/server.h:1397-1401
2#define run_with_period(_ms_) if ((_ms_ <= 1000/server.hz) || !(server.cronloops%((_ms_)/(1000/server.hz))))
宏解析:
- 如果
_ms_ <= 1000/server.hz,则每次serverCron都执行 - 否则,每隔
_ms_/(1000/server.hz)次循环执行一次
用法示例:
1// 每 5 秒打印一次数据库信息
2run_with_period(5000) {
3 // 打印数据库统计信息
4}
5
6// 每 100ms 执行一次集群心跳
7run_with_period(100) {
8 if (server.cluster_enabled) clusterCron();
9}
六、时间事件处理流程图
1 ┌─────────────────────────────────────────┐
2 │ aeMain() │
3 │ while(!stop) │
4 └─────────────────┬───────────────────────┘
5 │
6 ▼
7 ┌─────────────────────────────────────────┐
8 │ beforesleep() │
9 │ (处理待发送响应、快速过期检查等) │
10 └─────────────────┬───────────────────────┘
11 │
12 ▼
13 ┌─────────────────────────────────────────┐
14 │ aeProcessEvents() │
15 └─────────────────┬───────────────────────┘
16 │
17 ▼
18 ┌─────────────────────────────────────────┐
19 │ aeSearchNearestTimer() │
20 │ 查找最近的时间事件 │
21 └─────────────────┬───────────────────────┘
22 │
23 ▼
24 ┌─────────────────────────────────────────┐
25 │ 计算超时时间(距离最近事件的毫秒数) │
26 │ tvp = nearest_when - now │
27 └─────────────────┬───────────────────────┘
28 │
29 ▼
30 ┌─────────────────────────────────────────┐
31 │ aeApiPoll(tvp) │
32 │ 等待文件事件或超时 │
33 └─────────────────┬───────────────────────┘
34 │
35 ┌─────────────────┴───────────────────────┐
36 │ │
37 ▼ ▼
38 ┌─────────────────┐ ┌─────────────────┐
39 │ 文件事件触发 │ │ 超时返回 │
40 │ (处理文件事件) │ │ (无文件事件) │
41 └────────┬────────┘ └────────┬────────┘
42 │ │
43 └─────────────────┬───────────────────┘
44 │
45 ▼
46 ┌─────────────────────────────────────────┐
47 │ processTimeEvents() │
48 │ 处理到期的时间事件 │
49 └─────────────────┬───────────────────────┘
50 │
51 ▼
52 ┌─────────────────────────────────────────┐
53 │ serverCron() │
54 │ ┌─────────────────────────────────┐ │
55 │ │ - 更新时间缓存 │ │
56 │ │ - 清理超时客户端 │ │
57 │ │ - 过期键删除 │ │
58 │ │ - 触发 AOF/RDB 后台任务 │ │
59 │ │ - 主从复制心跳 │ │
60 │ │ - 集群心跳 │ │
61 │ │ - ... │ │
62 │ └─────────────────────────────────┘ │
63 │ return 1000/server.hz │
64 └─────────────────────────────────────────┘
七、设计亮点
7.1 无序链表 + 遍历查找
时间事件采用无序链表组织,查找最近事件需要遍历整个链表。
为什么不用更高效的数据结构(如最小堆、时间轮)?
- 时间事件数量极少:Redis 通常只有
serverCron一个时间事件 - 实现简单:链表插入 O(1),删除 O(n),总体开销可忽略
- 内存紧凑:无需额外的数据结构开销
7.2 时钟偏移检测
Redis 会检测系统时钟是否被向后调整:
1// src/ae.c:268-281
2 // 如果当前时间 < 上次处理时间,说明时钟被回退
3 if (now < eventLoop->lastTime) {
4 te = eventLoop->timeEventHead;
5 while(te) {
6 te->when_sec = 0; // 设置为 0,立即触发
7 te = te->next;
8 }
9 }
10 eventLoop->lastTime = now;
说明:如果检测到时钟回退,立即触发所有时间事件,确保定时任务不会被延迟过久。
7.3 周期事件的处理
通过回调函数返回值实现周期执行:
- 返回
AE_NOMORE (-1):一次性事件,执行后删除 - 返回正整数:周期事件,返回值为下次执行的延迟毫秒数
这种设计避免了为周期事件创建新对象,复用同一个事件结构体。
7.4 与文件事件的协同
时间事件与文件事件在同一个事件循环中处理:
- 先处理文件事件
- 再处理时间事件
- 时间事件影响
aeApiPoll的超时时间
这确保了时间事件不会无限延迟,同时也不会阻塞文件事件的处理。
八、时间事件 vs 文件事件
| 特性 | 时间事件 | 文件事件 |
|---|---|---|
| 触发条件 | 到达指定时间点 | fd 可读/可写 |
| 数据结构 | 无序链表 | fd 索引数组 |
| 处理顺序 | 后处理 | 先处理 |
| 典型用途 | 定时任务 | 网络 I/O |
| 典型回调 | serverCron |
readQueryFromClient |
| 数量 | 通常 1 个 | 随连接数变化 |
九、总结
Redis 时间事件机制的核心设计要点:
| 特性 | 说明 |
|---|---|
| 轻量级实现 | 无序链表,O(n) 查找,适配少量时间事件 |
| 周期复用 | 通过返回值控制周期执行,避免重复创建 |
| 时钟容错 | 检测时钟回退,确保任务及时执行 |
| 协同调度 | 与文件事件协同,计算合理超时时间 |
| 统一抽象 | 通过 aeTimeEvent 统一管理定时任务 |
时间事件是 Redis 实现定时任务的核心机制,serverCron 作为唯一的时间事件,承载了服务器的大部分后台维护工作。理解时间事件的实现原理,有助于深入掌握 Redis 的内部运行机制。