Nginx时间管理与定时器
我眼中的是时间管理
从系统调用函数直接获取当前时间
我眼中的定时器
- 有一定数据结构组织起来【链表】
- 有回调处理函数
Nginx实现的时间管理
Linux下操作时间的相关函数和结构体
- 结构体:
struct tm
: 用于表示日期和时间的各个组成部分,包括秒、分、时、日、月、年等。struct timeval
: 用于表示时间间隔的结构体,包括秒和微秒。
- 函数:
time_t time(time_t *t)
: 获取当前日历时间的秒数,返回自 1970 年 1 月 1 日 00:00:00 UTC(协调世界时)以来经过的秒数。int gettimeofday(struct timeval *tv, struct timezone *tz)
: 获取当前时间和时区信息,填充到struct timeval
结构体和struct timezone
结构体中。struct tm *localtime(const time_t *timep)
: 将日历时间转换为本地时间,返回指向struct tm
结构体的指针。struct tm *gmtime(const time_t *timep)
: 将日历时间转换为格林尼治时间(UTC 时间),返回指向struct tm
结构体的指针。time_t mktime(struct tm *tm)
: 将本地时间或格林尼治时间转换为日历时间,返回对应的日历时间秒数。char *asctime(const struct tm *tm)
: 将struct tm
结构体表示的时间转换为字符串格式,并返回一个指向静态分配的字符串的指针。char *ctime(const time_t *timep)
: 将日历时间转换为字符串格式,并返回一个指向静态分配的字符串的指针。
时间结构体
typedef struct {
time_t sec; // 秒数
ngx_uint_t msec; // 毫秒数
ngx_int_t gmtoff; // GMT偏移量
} ngx_time_t;
typedef struct tm ngx_tm_t;
struct tm
{
int tm_sec; /* Seconds. [0-60] (1 leap second) */
int tm_min; /* Minutes. [0-59] */
int tm_hour; /* Hours. [0-23] */
int tm_mday; /* Day. [1-31] */
int tm_mon; /* Month. [0-11] */
int tm_year; /* Year - 1900. */
int tm_wday; /* Day of week. [0-6] */
int tm_yday; /* Days in year.[0-365] */
int tm_isdst; /* DST. [-1/0/1]*/
# ifdef __USE_MISC
long int tm_gmtoff; /* Seconds east of UTC. */
const char *tm_zone; /* Timezone abbreviation. */
# else
long int __tm_gmtoff; /* Seconds east of UTC. */
const char *__tm_zone; /* Timezone abbreviation. */
# endif
};
注意差别:
struct tm
结构体:struct tm
结构体是标准 C 库<time.h>
中定义的,在很多 C 语言的标准库中都可以找到。- 它用于表示日期和时间的各个组成部分,如年、月、日、时、分、秒等,并提供了一些其他与时间相关的信息,如周几、一年中的第几天、夏令时标志等。
- 它通常用于时间日期的转换和格式化,例如将时间戳转换为人类可读的日期时间格式,或者将日期时间格式解析为时间戳等操作。
ngx_tm_t
结构体:ngx_tm_t
结构体是在 Nginx 源码中定义的,用于表示 Nginx 内部的时间信息。- 它与标准的
struct tm
结构体类似,但通常只包含了时间的基本信息,如秒数、毫秒数等,并没有包含日期、周几、时区等额外信息。 - 它通常用于 Nginx 内部的时间操作,如计算时间差、时间戳的获取、时间的更新等。
struct tm
结构体更加通用,适用于标准 C 语言环境下的时间操作,而 ngx_tm_t
结构体则是针对 Nginx 内部的时间处理需求而设计的,更加简洁和高效。
全局时间缓存
用于存储当前时间及其不同格式的字符串表示
static ngx_time_t cached_time[NGX_TIME_SLOTS];
extern volatile ngx_time_t *ngx_cached_time;
简单科普一下GMT与ISO 8601
ISO 8601 时间格式
ISO 8601 是国际标准化组织(ISO)发布的日期和时间表示法标准,旨在提供一种清晰、一致的方式来表示日期和时间。其主要特点和格式如下:
-
日期格式:
YYYY-MM-DD
:四位数的年份,二位数的月份和日期。例如,2024年5月27日表示为2024-05-27
。
-
时间格式:
hh:mm:ss
:二位数的小时、分钟和秒。例如,下午2点30分45秒表示为14:30:45
。
-
日期和时间的组合:
YYYY-MM-DDThh:mm:ss
:日期和时间通过字母“T”连接。例如,2024年5月27日下午2点30分45秒表示为2024-05-27T14:30:45
。
-
时区表示:
Z
:表示时间是以UTC(协调世界时)表示。例如,2024-05-27T14:30:45Z
。±hh:mm
:表示相对于UTC的偏移量。例如,东八区的时间可以表示为2024-05-
什么是 GMT?
GMT(Greenwich Mean Time,格林尼治标准时间)是指通过位于英国伦敦的皇家格林尼治天文台测定的时间。GMT 是全球统一的时间基准,用于定义世界各地的时间。它曾是国际标准时间,但现在更多使用的是 UTC(协调世界时),两者在日常使用中通常被认为是等价的。
相对于 GMT 的偏移量
相对于 GMT 的偏移量(GMT offset)表示一个地区的本地时间与 GMT 之间的时差。偏移量通常以小时或分钟为单位,可以是正数也可以是负数。例如:
- UTC+8:表示当地时间比 GMT 快 8 小时。这适用于像中国这样的国家。
- UTC-5:表示当地时间比 GMT 慢 5 小时。这适用于美国东部标准时间。
GMT 与 UTC
GMT(格林尼治标准时间) 和 UTC(协调世界时) 通常被用来表示世界时间的标准,但二者略有不同:
- GMT:格林尼治标准时间,是指通过位于英国伦敦的格林尼治天文台的时间。这是一种历史标准,通常被认为等同于UTC。
- UTC:协调世界时,是基于原子钟的时间标准,精度更高。UTC 是现代国际时间标准。
具体时间实例
假设当前的 GMT 时间是 2024 年 5 月 27 日 12:00:00(中午 12 点)。
- 在北京(UTC+8),当地时间是 2024 年 5 月 27 日 20:00:00(晚上 8 点)。
- 在纽约(UTC-5),当地时间是 2024 年 5 月 27 日 07:00:00(早上 7 点)。
全局缓存的时间字符串
extern volatile ngx_str_t ngx_cached_err_log_time;
extern volatile ngx_str_t ngx_cached_http_time;
extern volatile ngx_str_t ngx_cached_http_log_time;
extern volatile ngx_str_t ngx_cached_http_log_iso8601;
extern volatile ngx_str_t ngx_cached_syslog_time;
初始化时间缓存
ngx_time_init
的作用是初始化 Nginx 的时间缓存系统,为日志记录和时间格式化等操作提供预定义的时间格式。
void ngx_time_init(void) {
// 设置时间字符串的长度
ngx_cached_err_log_time.len = sizeof("1970/09/28 12:00:00") - 1;//设置错误日志时间格式的字符串长度。
ngx_cached_http_time.len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;//设置HTTP头部时间格式的字符串长度。
ngx_cached_http_log_time.len = sizeof("28/Sep/1970:12:00:00 +0600") - 1;//设置HTTP日志时间格式的字符串长度。
ngx_cached_http_log_iso8601.len = sizeof("1970-09-28T12:00:00+06:00") - 1;//设置ISO 8601格式的时间字符串长度。
ngx_cached_syslog_time.len = sizeof("Sep 28 12:00:00") - 1;//设置系统日志时间格式的字符串长度。
ngx_cached_time = &cached_time[0]; // 初始化全局时间缓存指针
ngx_time_update(); // 更新时间
}
更新时间缓存
ngx_time_update
函数用于更新 Nginx 的时间缓存。这个函数会获取当前的时间,将其转换为多种格式,并缓存起来,以便在日志记录和其他需要时间的地方高效使用。通过缓存时间,可以减少频繁调用系统时间获取函数的开销,提高系统性能。
void ngx_time_update(void) {
u_char *p0, *p1, *p2, *p3, *p4;
ngx_tm_t tm, gmt;
time_t sec;
ngx_uint_t msec;
ngx_time_t *tp;
struct timeval tv;
// 尝试加锁,确保线程安全
if (!ngx_trylock(&ngx_time_lock)) {
return;
}
// 获取当前时间
ngx_gettimeofday(&tv);
sec = tv.tv_sec; // 秒
msec = tv.tv_usec / 1000; // 毫秒
ngx_current_msec = ngx_monotonic_time(sec, msec); // 获取单调时间(毫秒)
tp = &cached_time[slot];
// 如果秒数没有变化,只更新毫秒值并解锁返回
if (tp->sec == sec) {
tp->msec = msec;
ngx_unlock(&ngx_time_lock);
return;
}
// 更新缓存槽位
if (slot == NGX_TIME_SLOTS - 1) {
slot = 0;
} else {
slot++;
}
tp = &cached_time[slot];
tp->sec = sec; // 更新秒
tp->msec = msec; // 更新毫秒
// 将时间转换为 GMT 时间
ngx_gmtime(sec, &gmt);
// 更新 HTTP 时间缓存
p0 = &cached_http_time[slot][0];
(void) ngx_sprintf(p0, "%s, %02d %s %4d %02d:%02d:%02d GMT",
week[gmt.ngx_tm_wday], gmt.ngx_tm_mday,
months[gmt.ngx_tm_mon - 1], gmt.ngx_tm_year,
gmt.ngx_tm_hour, gmt.ngx_tm_min, gmt.ngx_tm_sec);
#if (NGX_HAVE_GETTIMEZONE)
tp->gmtoff = ngx_gettimezone(); // 获取时区偏移量
ngx_gmtime(sec + tp->gmtoff * 60, &tm); // 计算本地时间
#elif (NGX_HAVE_GMTOFF)
ngx_localtime(sec, &tm); // 获取本地时间
cached_gmtoff = (ngx_int_t) (tm.ngx_tm_gmtoff / 60);
tp->gmtoff = cached_gmtoff; // 更新时区偏移量
#else
ngx_localtime(sec, &tm); // 获取本地时间
cached_gmtoff = ngx_timezone(tm.ngx_tm_isdst); // 计算时区
tp->gmtoff = cached_gmtoff; // 更新时区偏移量
#endif
// 更新错误日志时间缓存
p1 = &cached_err_log_time[slot][0];
(void) ngx_sprintf(p1, "%4d/%02d/%02d %02d:%02d:%02d",
tm.ngx_tm_year, tm.ngx_tm_mon,
tm.ngx_tm_mday, tm.ngx_tm_hour,
tm.ngx_tm_min, tm.ngx_tm_sec);
// 更新 HTTP 日志时间缓存
p2 = &cached_http_log_time[slot][0];
(void) ngx_sprintf(p2, "%02d/%s/%d:%02d:%02d:%02d %c%02i%02i",
tm.ngx_tm_mday, months[tm.ngx_tm_mon - 1],
tm.ngx_tm_year, tm.ngx_tm_hour,
tm.ngx_tm_min, tm.ngx_tm_sec,
tp->gmtoff < 0 ? '-' : '+',
ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));
// 更新 ISO8601 格式的时间缓存
p3 = &cached_http_log_iso8601[slot][0];
(void) ngx_sprintf(p3, "%4d-%02d-%02dT%02d:%02d:%02d%c%02i:%02i",
tm.ngx_tm_year, tm.ngx_tm_mon,
tm.ngx_tm_mday, tm.ngx_tm_hour,
tm.ngx_tm_min, tm.ngx_tm_sec,
tp->gmtoff < 0 ? '-' : '+',
ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));
// 更新系统日志时间缓存
p4 = &cached_syslog_time[slot][0];
(void) ngx_sprintf(p4, "%s %2d %02d:%02d:%02d",
months[tm.ngx_tm_mon - 1], tm.ngx_tm_mday,
tm.ngx_tm_hour, tm.ngx_tm_min, tm.ngx_tm_sec);
// 内存屏障,确保写操作的顺序性
ngx_memory_barrier();
// 更新全局时间缓存指针
ngx_cached_time = tp;
ngx_cached_http_time.data = p0;
ngx_cached_err_log_time.data = p1;
ngx_cached_http_log_time.data = p2;
ngx_cached_http_log_iso8601.data = p3;
ngx_cached_syslog_time.data = p4;
// 解锁
ngx_unlock(&ngx_time_lock);
}
格式化转换
ngx_gmtime
函数的作用是将一个 time_t
类型的时间值转换为一个 ngx_tm_t
结构体,该结构体包含人类可读的时间信息(例如年、月、日、小时、分钟和秒)。该函数类似于标准库中的 gmtime
函数,但进行了专门的实现和优化,以满足 Nginx 的特定需求。
time_t
是一种在标准 C 库中定义的类型,用于表示日历时间。通常,它表示从 1970 年 1 月 1 日 00:00:00 UTC(称为 Unix 纪元)以来经过的秒数。具体的实现可以是一个整数或一个浮点数,取决于平台。
在 Nginx 中,
time_t
通常用于存储秒级精度的时间戳。ngx_time_t
是一个自定义的时间结构体,扩展了time_t
的功能,以包含毫秒和 GMT 偏移量等信息。
void ngx_gmtime(time_t t, ngx_tm_t *tp) {
ngx_int_t yday;
ngx_uint_t sec, min, hour, mday, mon, year, wday, days, leap;
// 该计算仅适用于正的 time_t 值
if (t < 0) {
t = 0;
}
days = t / 86400; // 计算总天数
sec = t % 86400; // 计算一天中的秒数
// 支持的最大日期为9999年12月31日23:59:59
if (days > 2932896) {
days = 2932896;
sec = 86399;
}
// 1970年1月1日是星期四
wday = (4 + days) % 7;
hour = sec / 3600; // 计算小时
sec %= 3600;
min = sec / 60; // 计算分钟
sec %= 60; // 计算秒
// 基于高斯公式的算法,详见 src/core/ngx_parse_time.c
days = days - (31 + 28) + 719527; // 自公元前1年3月1日以来的天数
// 计算年份,公式中调整了2天以处理某些年份的3月1日
year = (days + 2) * 400 / (365 * 400 + 100 - 4 + 1);
yday = days - (365 * year + year / 4 - year / 100 + year / 400);
// 处理负的 yday 值(闰年)
if (yday < 0) {
leap = (year % 4 == 0) && (year % 100 || (year % 400 == 0));
yday = 365 + leap + yday;
year--;
}
// 将 yday 映射到月份的经验公式
mon = (yday + 31) * 10 / 306;
// 计算月之前的天数的高斯公式
mday = yday - (367 * mon / 12 - 30) + 1;
if (yday >= 306) {
year++;
mon -= 10;
// Win32 SYSTEMTIME 没有 yday
} else {
mon += 2;
// Win32 SYSTEMTIME 没有 yday
}
// 设置 tp 结构体的各个字段
tp->ngx_tm_sec = (ngx_tm_sec_t) sec;
tp->ngx_tm_min = (ngx_tm_min_t) min;
tp->ngx_tm_hour = (ngx_tm_hour_t) hour;
tp->ngx_tm_mday = (ngx_tm_mday_t) mday;
tp->ngx_tm_mon = (ngx_tm_mon_t) mon;
tp->ngx_tm_year = (ngx_tm_year_t) year;
tp->ngx_tm_wday = (ngx_tm_wday_t) wday;
}
设置预定时间点
计算下一个特定时间点的时间戳。给定一个时间戳 when
,函数根据当前时间计算出下一个 when
所表示的时分秒时间,然后转换为时间戳。如果下一个时间点比当前时间晚,则返回该时间点的时间戳;否则,将日期加一,再次尝试计算下一个时间点的时间戳,直到计算成功或者达到某种错误条件。
它通常用于需要在特定时间执行某些任务的情况下,例如定时任务、定时器等场景。在这些场景中,我们可能需要计算下一个执行时间点,以便安排下一次的任务执行。
- 定时任务调度器: 在一个定时任务调度器中,可能会有很多任务需要在特定的时间点执行,这时就可以使用这个函数来计算下一个执行时间点。
- 定时器: 在网络编程中,有时候需要设置定时器来执行一些操作,比如超时处理、定时发送心跳等,这时就可以使用这个函数来计算下一个超时时间点。
- 计划任务管理器: 在系统中可能会有一些计划任务需要按照预定的时间执行,这时就可以使用这个函数来计算下一个执行时间点。
time_t
ngx_next_time(time_t when)
{
time_t now, next; // 当前时间和下一个时间
struct tm tm; // 结构体 tm 用于存储时间的分解部分
now = ngx_time(); // 获取当前时间
ngx_libc_localtime(now, &tm); // 将当前时间转换为本地时间,存储在结构体 tm 中
// 计算下一个时间的小时、分钟和秒
tm.tm_hour = (int) (when / 3600);
when %= 3600;
tm.tm_min = (int) (when / 60);
tm.tm_sec = (int) (when % 60);
// 将结构体 tm 转换为 time_t 类型的时间,即计算下一个时间的时间戳
next = mktime(&tm);
// 如果 mktime() 返回 -1,表示转换失败,返回 -1 表示错误
if (next == -1) {
return -1;
}
// 如果下一个时间大于当前时间,则返回下一个时间
if (next - now > 0) {
return next;
}
// 如果下一个时间小于或等于当前时间,说明已经过了今天的设定时间,需要设定为明天的设定时间
tm.tm_mday++; // 将日期加 1
/* mktime() should normalize a date (Jan 32, etc) */
// 再次尝试计算下一个时间
next = mktime(&tm);
// 如果计算成功,返回下一个时间的时间戳,否则返回 -1 表示错误
if (next != -1) {
return next;
}
return -1; // 返回 -1 表示错误
}
转换为 HTTP 时间格式和 Cookie 时间格式
u_char *
ngx_http_time(u_char *buf, time_t t)
{
ngx_tm_t tm;
ngx_gmtime(t, &tm); // 将时间戳转换为格林尼治时间
// 将格林尼治时间格式化为 HTTP 时间格式,并写入缓冲区 buf 中
return ngx_sprintf(buf, "%s, %02d %s %4d %02d:%02d:%02d GMT",
week[tm.ngx_tm_wday],
tm.ngx_tm_mday,
months[tm.ngx_tm_mon - 1],
tm.ngx_tm_year,
tm.ngx_tm_hour,
tm.ngx_tm_min,
tm.ngx_tm_sec);
}
u_char *
ngx_http_cookie_time(u_char *buf, time_t t)
{
ngx_tm_t tm;
ngx_gmtime(t, &tm); // 将时间戳转换为格林尼治时间
/*
* Netscape 3.x does not understand 4-digit years at all and
* 2-digit years more than "37"
*/
// 根据规范格式化为 Cookie 时间格式,并写入缓冲区 buf 中
return ngx_sprintf(buf,
(tm.ngx_tm_year > 2037) ?
"%s, %02d-%s-%d %02d:%02d:%02d GMT":
"%s, %02d-%s-%02d %02d:%02d:%02d GMT",
week[tm.ngx_tm_wday],
tm.ngx_tm_mday,
months[tm.ngx_tm_mon - 1],
(tm.ngx_tm_year > 2037) ? tm.ngx_tm_year:
tm.ngx_tm_year % 100,
tm.ngx_tm_hour,
tm.ngx_tm_min,
tm.ngx_tm_sec);
}
获取当前时间
#define ngx_time() ngx_cached_time->sec
#define ngx_timeofday() (ngx_time_t *) ngx_cached_time
Nginx定时器事件
概述
Nginx 定时器事件是 Nginx 服务器中用于管理和处理事件的一种机制,用于实现异步、非阻塞的事件驱动模型。定时器事件主要用于处理与时间相关的事件,如超时事件、定时任务等。
在 Nginx 中,定时器事件通常用于以下几个方面:
- 超时处理:Nginx 服务器需要处理各种网络请求,如客户端的连接请求、HTTP 请求等。为了避免请求处理时间过长导致资源浪费或性能下降,Nginx 使用定时器事件来管理请求的超时时间,一旦超时,即可将相应的请求标记为超时状态,然后进行相应的处理。
- 定时任务:Nginx 可以执行一些周期性的定时任务,如定时清理日志、定时刷新缓存等。这些定时任务通常也是通过定时器事件来实现的,通过设置一定的时间间隔,定期执行相应的任务。
- 事件调度:定时器事件还用于管理和调度事件的执行顺序。在异步事件处理模型中,事件的触发和执行通常是异步的,而定时器事件可以确保事件按照预定的顺序进行执行,从而保证系统的稳定性和可靠性。
定时器数据结构
保存事件的结构体ngx_event_t 中有三个关于时间管理的成员
struct ngx_event_s{
...
/* 标志位,为1表示当前事件已超时 */
unsigned timedout:1;
/* 标志位,为1表示当前事件存在于由红黑树维护的定时器中 */
unsigned timer_set:1;
/* 由红黑树维护的定时器 */
ngx_rbtree_node_t timer;
...
};
Nginx 设置两个关于定时器的全局变量
/* 所有定时器事件组成的红黑树 */
ngx_thread_volatile ngx_rbtree_t ngx_event_timer_rbtree;
/* 红黑树的哨兵节点 */
static ngx_rbtree_node_t ngx_event_timer_sentinel;
初始化事件定时器
初始化事件定时器,它使用红黑树作为底层数据结构来管理定时事件。函数通过调用 ngx_rbtree_init
初始化了一个红黑树 ngx_event_timer_rbtree
,并指定了哨兵节点 ngx_event_timer_sentinel
,以及用于比较的插入函数 ngx_rbtree_insert_timer_value
。初始化成功后返回状态码 NGX_OK
。
/*
* 初始化事件定时器,使用红黑树作为底层数据结构。
* 参数:
* log: 日志对象指针,用于记录初始化过程中的日志信息。
* 返回值:
* NGX_OK: 初始化成功。
*/
ngx_int_t ngx_event_timer_init(ngx_log_t *log)
{
// 使用 ngx_rbtree_init 函数初始化红黑树 ngx_event_timer_rbtree,
// 第一个参数为红黑树的根节点,第二个参数为红黑树的哨兵节点,
// 第三个参数为插入时用于比较的函数指针 ngx_rbtree_insert_timer_value。
ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
ngx_rbtree_insert_timer_value);
return NGX_OK; // 返回初始化成功的状态码
}
添加定时器事件
向事件定时器中添加指定事件,并设置事件的定时器时间。首先,计算了事件定时器的时间戳,然后判断事件的定时器是否已经设置,如果已经设置,则检查新旧定时器值之间的差异是否小于 NGX_TIMER_LAZY_DELAY
毫秒,如果是,则不添加新的定时器,直接返回,以减少红黑树操作的次数。如果差异超过了阈值,则先从红黑树中删除旧的定时器,然后设置事件的定时器时间,并将事件的定时器插入到红黑树中。最后,设置事件的定时器已被设置标志位。
/*
* 向事件定时器中添加指定事件,并设置定时器时间。
* 参数:
* ev: 待添加的事件指针。
* timer: 定时器的时间,以毫秒为单位。
*/
static ngx_inline void ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
{
ngx_msec_t key;
ngx_msec_int_t diff;
// 计算定时器的时间戳
key = ngx_current_msec + timer;
// 如果事件的定时器已经设置,则检查新旧定时器值之间的差异是否小于 NGX_TIMER_LAZY_DELAY 毫秒,
// 如果是,则不添加新的定时器,直接返回,这样可以减少红黑树操作的次数,提高性能。
if (ev->timer_set) {
diff = (ngx_msec_int_t) (key - ev->timer.key);
if (ngx_abs(diff) < NGX_TIMER_LAZY_DELAY) {
// 调试日志记录定时器更新情况。
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"event timer: %d, old: %M, new: %M",
ngx_event_ident(ev->data), ev->timer.key, key);
return;
}
// 从红黑树中删除旧的定时器。
ngx_del_timer(ev);
}
// 设置事件的定时器时间。
ev->timer.key = key;
// 调试日志记录定时器添加情况。
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"event timer add: %d: %M:%M",
ngx_event_ident(ev->data), timer, ev->timer.key);
// 将事件的定时器插入到红黑树中。
ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
// 设置事件的定时器已被设置标志位。
ev->timer_set = 1;
}
删除定时器事件
从事件定时器中移除指定的事件,并清除事件相关的定时器数据。首先,通过调试日志记录了被删除的事件的标识符和定时器键值。然后,通过调用 ngx_rbtree_delete
函数从红黑树中删除了事件的定时器。最后,如果编译时启用了调试模式(NGX_DEBUG
宏定义),则清空了事件的定时器节点的左、右、父节点,用于调试目的。最后,将事件的 timer_set
标志位设置为 0,表示事件的定时器已被移除。
/*
* 从事件定时器中移除指定的事件,并清除事件相关的定时器数据。
* 参数:
* ev: 待移除的事件指针。
*/
static ngx_inline void ngx_event_del_timer(ngx_event_t *ev)
{
// 记录调试日志,包括事件标识符和定时器键值。
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"event timer del: %d: %M",
ngx_event_ident(ev->data), ev->timer.key);
// 从红黑树中删除事件的定时器。
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
#if (NGX_DEBUG)
// 清空事件的定时器节点的左、右、父节点,用于调试。
ev->timer.left = NULL;
ev->timer.right = NULL;
ev->timer.parent = NULL;
#endif
// 标记事件的定时器已被移除。
ev->timer_set = 0;
}
查找最近定时器时间
查找事件定时器中最近的定时器时间。如果事件定时器为空,则返回无限大的定时器时间。否则,遍历红黑树找到最小的定时器节点,计算最近的定时器时间,并返回该时间。
/*
* 查找事件定时器中最近的定时器时间。
* 返回值:
* 返回值为事件定时器中最近的定时器时间,以毫秒为单位。
*/
ngx_msec_t ngx_event_find_timer(void)
{
ngx_msec_int_t timer;
ngx_rbtree_node_t *node, *root, *sentinel;
// 如果事件定时器为空,则返回无限大的定时器时间。
if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) {
return NGX_TIMER_INFINITE;
}
// 获取事件定时器的根节点和哨兵节点。
root = ngx_event_timer_rbtree.root;
sentinel = ngx_event_timer_rbtree.sentinel;
// 查找事件定时器中最小的定时器节点。
node = ngx_rbtree_min(root, sentinel);
// 计算最近的定时器时间。
timer = (ngx_msec_int_t) (node->key - ngx_current_msec);
// 返回最近的定时器时间,如果定时器时间小于等于0,则返回0。
return (ngx_msec_t) (timer > 0 ? timer : 0);
}
处理已经过期的事件定时器
处理已经过期的事件定时器。它会遍历事件定时器红黑树,找到所有已经过期的事件定时器,并调用相应的事件处理函数来处理这些已经过期的事件。
/*
* 处理已经过期的事件定时器。
* 该函数会遍历事件定时器红黑树,处理所有已经过期的事件定时器。
*/
void ngx_event_expire_timers(void)
{
ngx_event_t *ev; // 指向已经过期的事件
ngx_rbtree_node_t *node, *root, *sentinel; // 用于遍历红黑树的节点指针
sentinel = ngx_event_timer_rbtree.sentinel; // 获取红黑树的哨兵节点
for ( ;; ) {
root = ngx_event_timer_rbtree.root; // 获取红黑树的根节点
// 如果根节点是哨兵节点,表示事件定时器为空,直接返回
if (root == sentinel) {
return;
}
// 查找红黑树中最小的定时器节点
node = ngx_rbtree_min(root, sentinel);
// 如果最小节点的定时器时间大于当前时间,表示没有事件定时器过期,直接返回
if ((ngx_msec_int_t) (node->key - ngx_current_msec) > 0) {
return;
}
// 获取对应的事件对象
ev = ngx_rbtree_data(node, ngx_event_t, timer);
// 打印调试日志,表示该事件定时器已经过期
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"event timer del: %d: %M",
ngx_event_ident(ev->data), ev->timer.key);
// 从红黑树中删除该事件定时器
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
#if (NGX_DEBUG)
// 清空定时器节点的左右子节点和父节点指针(仅在调试模式下有效)
ev->timer.left = NULL;
ev->timer.right = NULL;
ev->timer.parent = NULL;
#endif
// 将事件的 timer_set 标志位清零,表示该事件的定时器已经被删除
ev->timer_set = 0;
// 设置事件的 timedout 标志位为1,表示该事件已经超时
ev->timedout = 1;
// 调用事件的 handler 处理函数,处理超时事件
ev->handler(ev);
}
}
总结
- 利用缓存尽量减少调用系统函数以提高系统性能
- Linux下的时间相关处理与程序在不同时区内运行的准确性
- 时间格式的转换
限于篇幅限制,定时器事件执行与Nginx事件模块将会在下一篇中介绍。
er del: %d: %M",
ngx_event_ident(ev->data), ev->timer.key);
// 从红黑树中删除该事件定时器
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
#if (NGX_DEBUG)
// 清空定时器节点的左右子节点和父节点指针(仅在调试模式下有效)
ev->timer.left = NULL;
ev->timer.right = NULL;
ev->timer.parent = NULL;
#endif
// 将事件的 timer_set 标志位清零,表示该事件的定时器已经被删除
ev->timer_set = 0;
// 设置事件的 timedout 标志位为1,表示该事件已经超时
ev->timedout = 1;
// 调用事件的 handler 处理函数,处理超时事件
ev->handler(ev);
}
}
## 总结
- 利用缓存尽量减少调用系统函数以提高系统性能
- Linux下的时间相关处理与程序在不同时区内运行的准确性
- 时间格式的转换
限于篇幅限制,定时器事件执行与Nginx事件模块将会在下一篇中介绍。
------