Nginx时间管理与定时器

Nginx时间管理与定时器

我眼中的是时间管理

从系统调用函数直接获取当前时间

我眼中的定时器

  • 有一定数据结构组织起来【链表】
  • 有回调处理函数

Nginx实现的时间管理

image-20240528103843360

Linux下操作时间的相关函数和结构体

20200923110542514

  1. 结构体:
    • struct tm 用于表示日期和时间的各个组成部分,包括秒、分、时、日、月、年等。
    • struct timeval 用于表示时间间隔的结构体,包括秒和微秒。
  2. 函数:
    • 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
};

注意差别:

  1. struct tm 结构体:
    • struct tm 结构体是标准 C 库 <time.h> 中定义的,在很多 C 语言的标准库中都可以找到。
    • 它用于表示日期和时间的各个组成部分,如年、月、日、时、分、秒等,并提供了一些其他与时间相关的信息,如周几、一年中的第几天、夏令时标志等。
    • 它通常用于时间日期的转换和格式化,例如将时间戳转换为人类可读的日期时间格式,或者将日期时间格式解析为时间戳等操作。
  2. 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)发布的日期和时间表示法标准,旨在提供一种清晰、一致的方式来表示日期和时间。其主要特点和格式如下:

  1. 日期格式

    • YYYY-MM-DD:四位数的年份,二位数的月份和日期。例如,2024年5月27日表示为 2024-05-27
  2. 时间格式

    • hh:mm:ss:二位数的小时、分钟和秒。例如,下午2点30分45秒表示为 14:30:45
  3. 日期和时间的组合

    • YYYY-MM-DDThh:mm:ss:日期和时间通过字母“T”连接。例如,2024年5月27日下午2点30分45秒表示为 2024-05-27T14:30:45
  4. 时区表示

    • 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 点)。

What is gmt time zone? - Yoors

全局缓存的时间字符串
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 中,定时器事件通常用于以下几个方面:

  1. 超时处理:Nginx 服务器需要处理各种网络请求,如客户端的连接请求、HTTP 请求等。为了避免请求处理时间过长导致资源浪费或性能下降,Nginx 使用定时器事件来管理请求的超时时间,一旦超时,即可将相应的请求标记为超时状态,然后进行相应的处理。
  2. 定时任务:Nginx 可以执行一些周期性的定时任务,如定时清理日志、定时刷新缓存等。这些定时任务通常也是通过定时器事件来实现的,通过设置一定的时间间隔,定期执行相应的任务。
  3. 事件调度:定时器事件还用于管理和调度事件的执行顺序。在异步事件处理模型中,事件的触发和执行通常是异步的,而定时器事件可以确保事件按照预定的顺序进行执行,从而保证系统的稳定性和可靠性。

image-20240528113151022

定时器数据结构

保存事件的结构体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事件模块将会在下一篇中介绍。

------

































  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值