定义在 src\core\ngx_times.c 中
ngx_time_init 函数后面
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; ngx_gmtime(sec, &gmt); 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); 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)); 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_time_update
是 Nginx 中用于高效更新时间缓存的核心函数,目的是通过预生成多种时间格式,避免频繁的系统调用和格式化开销,从而提升性能。
- 加锁与时间获取
通过 `ngx_trylock` 尝试获取 `ngx_time_lock` 锁,若失败则直接返回(避免阻塞)。
调用 `ngx_gettimeofday` 获取当前时间(秒 `tv.tv_sec` 和微秒 `tv.tv_usec`),并计算毫秒 `msec`。
更新全局单调时间 `ngx_current_msec`(用于高性能计时,避免系统时间跳变的影响)。
- 时间缓存更新
使用 `cached_time` 数组作为循环缓冲区(`NGX_TIME_SLOTS` 个槽位),存储秒级时间戳和毫秒。
若当前槽位的秒数未变化(`tp->sec == sec`),仅更新毫秒并返回,减少重复计算。
若时间已跨秒,切换到下一个槽位(循环),更新秒和毫秒。
- 时间格式化
GMT 时间:生成 HTTP 标准日期格式(如 `Wed, 21 Oct 2020 12:34:56 GMT`),存于 `cached_http_time`。
本地时间:根据时区配置(`gmtoff`)计算本地时间,生成多种日志格式:
错误日志格式(`2020/10/21 12:34:56`)。
HTTP 访问日志格式(`21/Oct/2020:12:34:56 +0800`)。
ISO8601 格式(`2020-10-21T12:34:56+08:00`)。
Syslog 格式(`Oct 21 12:34:56`)。
- 内存屏障与全局发布
通过 `ngx_memory_barrier` 确保缓存数据写入完成。
更新全局指针(如 `ngx_cached_time`、`ngx_cached_http_time.data`),使新时间数据对其他线程可见。
- 释放锁
- 完成更新后释放 `ngx_time_lock`。
意图
- 高性能时间管理
通过缓存时间数据和预格式化字符串,避免每次获取时间时的重复计算(如 `ngx_sprintf`),显著减少系统调用和格式化开销。
使用循环缓冲区(多槽位)实现无锁读取:写入新槽位时,其他线程仍可读取旧槽位的有效数据。
- 线程安全与原子性
通过 `ngx_time_lock` 保证时间更新的原子性。
通过内存屏障确保全局指针更新的可见性,避免部分更新导致的数据不一致。
- 平台兼容性
- 通过条件编译处理不同平台的时区获取方式(`NGX_HAVE_GETTIMEZONE`、`NGX_HAVE_GMTOFF`),增强跨平台兼容性。
- 多样化时间格式支持
- 预生成 HTTP 头、日志、Syslog 等场景所需的时间格式,满足不同模块的需求,提升整体效率。
关键设计思想
空间换时间:牺牲少量内存缓存多种时间格式,换取 CPU 和 I/O 效率。
无锁读优化:通过多槽位设计,允许读操作无需加锁,适合高并发场景。
模块化时间处理:将时间更新逻辑集中管理,降低模块耦合度。
此函数是 Nginx 高性能的核心组件之一,通过精细的优化确保时间处理在极端负载下仍能保持高效。
u_char *p0, *p1, *p2, *p3, *p4;
用于指向不同时间格式字符串的指针
ngx_tm_t tm, gmt;
Ubuntu 下 nginx-1.24.0 源码分析 ngx_tm_t 类型-CSDN博客
ngx_tm_t
是一个表示时间结构的自定义类型。
ngx_tm_t tm, gmt;
的作用是声明两个变量tm
和gmt
,它们分别用于存储本地时间和格林威治标准时间(GMT)的日期和时间信息。
time_t sec;
存储当前时间的秒数
time_t
是 C 标准库中用于表示时间的数据类型,通常是一个整数,表示从 1970 年 1 月 1 日 00:00:00 UTC 开始到当前时间的秒数(即 Unix 时间戳)。ngx_uint_t msec;
存储当前时间的毫秒部分
ngx_time_t *tp;
指向一个存储当前时间信息的结构体
用于存储和操作当前时间的信息
ngx_times.h 中:
typedef struct { time_t sec; ngx_uint_t msec; ngx_int_t gmtoff; } ngx_time_t;
秒数(
sec
)、毫秒数(msec
)以及时间偏移量(gmtoff
)struct timeval tv;
用于存储当前时间的秒数和微秒数
struct timeval
是 C 标准库中定义的一种时间结构体,用于存储秒(tv_sec
)和微秒(tv_usec
)级别的时间信息
struct timeval
是一个在 POSIX 标准中定义的结构体,用于表示时间,通常以秒和微秒为单位。它在<sys/time.h>
头文件中定义。
struct timeval
的定义如下:struct timeval { long tv_sec; /* 秒 */ long tv_usec; /* 微秒 */ };
成员变量
tv_sec
:表示从 1970 年 1 月 1 日 00:00:00 UTC(Unix 纪元)开始的秒数。
tv_usec
:表示秒后的微秒数(范围 0-999999)
if (!ngx_trylock(&ngx_time_lock)) { return; }
这段代码中的
if (!ngx_trylock(&ngx_time_lock)) { return; }
是用来尝试获取一个互斥锁(ngx_time_lock
),以确保
ngx_time_update
函数的临界区(即需要同步访问的代码部分)只会被一个线程执行。如果锁获取失败,函数会直接返回,不执行后续的逻辑
ngx_trylock
是一个非阻塞的锁尝试函数如果
ngx_time_lock
已被其他线程持有,ngx_trylock
会立即返回失败,而不是阻塞等待。成功获取锁时 :返回
true
,函数继续执行法获取锁时 :返回
false
,函数直接返回,不执行后续的逻辑
ngx_time_lock
互斥锁
在 src\core\ngx_times.c 中定义的全局变量
static ngx_atomic_t ngx_time_lock;
ngx_time_lock
是一个原子锁变量,用于协调多进程(Nginx 的 Worker 进程)对共享时间数据 的访问,通过ngx_time_lock
确保原子性ngx_atomic_t
src\os\unix\ngx_atomic.h 中:
typedef volatile ngx_atomic_uint_t ngx_atomic_t; typedef long ngx_atomic_int_t; typedef unsigned long ngx_atomic_uint_t;
所以本质上 无符号 long 类型
ngx_trylock 函数
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_trylock函数-CSDN博客
ngx_gettimeofday(&tv);
ngx_gettimeofday
是 Nginx 中用于获取当前系统时间的一个函数。它返回当前时间的秒数和微秒数,存储在
struct timeval
结构体中。tv.tv_sec
表示秒数,tv.tv_usec
表示微秒数
ngx_gettimeofday 函数
src/os/unix/ngx_time.h 中:
#define ngx_gettimeofday(tp) (void) gettimeofday(tp, NULL);
gettimeofday
是 C 语言中的一个函数,用于获取当前的时间。它属于 POSIX 标准,通常在类 Unix 系统(如 Linux 和 macOS)中使用。该函数可以返回自 1970年1月1日 00:00:00 UTC(即 Unix 时间起点)以来的秒数和微秒数函数原型
#include <sys/time.h> int gettimeofday(struct timeval *tv, struct timezone *tz);
struct timeval *tv
是一个指向
timeval
结构体的指针,用于存储当前时间
struct timezone *tz
是一个指向
timezone
结构体的指针,用于存储时区信息
timezone
结构体定义如下struct timezone { int tz_minuteswest; // 西侧相对于 UTC 的分钟差 int tz_dsttime; // 夏令时修正类型(已废弃,通常设为 NULL) };
在现代程序中,
tz
参数通常设置为NULL
,因为时区信息可以通过其他更可靠的方式获取(例如通过环境变量或系统调用)返回值
如果调用成功,
gettimeofday
返回0
。如果调用失败,返回
-1
,并设置errno
来指示错误原因sec = tv.tv_sec;
msec = tv.tv_usec / 1000;这两行代码的作用是将获取到的系统时间(
struct timeval
)拆分为秒和毫秒部分
sec = tv.tv_sec;
从
struct timeval
结构体中提取出秒数部分,赋值给变量sec
msec = tv.tv_usec / 1000;
从
struct timeval
结构体中提取出微秒部分,并将其转换为毫秒,赋值给变量msec
ngx_current_msec = ngx_monotonic_time(sec, msec);
ngx_current_msec
是 Nginx 内部维护的一个全局变量,用于存储当前的时间值(以毫秒为单位)
ngx_monotonic_time
是 Nginx 提供的一个函数,用于将时间值(秒和毫秒)转换为单调递增的时间值(monotonic time)这行代码的意图是将当前的时间值(
sec
和msec
)转换为单调时间值,并更新到ngx_current_msec
中单调时间值通常表示自某个固定起点(例如系统启动时间)以来经过的时间,而不是绝对的日历时间
不会因为系统时钟调整(如 NTP 时间同步、人为更改系统时间等)而回退或突然变小
ngx_current_msec全局变量
src\core\ngx_times.c 中:
volatile ngx_msec_t ngx_current_msec;
ngx_msec_t 类型本质上是 uintptr_t
ngx_monotonic_time函数
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_monotonic_time函数-CSDN博客
tp = &cached_time[slot];
获取对时间缓存数组
cached_time
中特定位置的引用,并将其赋值给指针tp
cached_time
是一个全局数组,用于存储时间值及其相关数据。它是一个缓存机制,用于减少频繁调用系统时间函数的开销
cached_time
使用环形缓冲区(Ring Buffer)设计,slot
表示当前活跃的缓存槽索引当时间跨越整秒时,切换到下一个槽(
slot++
),确保新时间写入新槽,避免读写冲突
slot
src\core\ngx_times.c 中
static ngx_uint_t slot;
cached_time
src\core\ngx_times.c 中
static ngx_time_t cached_time[NGX_TIME_SLOTS]; #define NGX_TIME_SLOTS 64
数组的作用是存储多个时间槽的数据,每个槽位保存一个时间点的信息
定期更新
cached_time
中的时间数据在多线程或多进程环境中,不同线程或进程可以使用不同的槽位,从而减少对共享资源的竞争
即使某个槽位正在被更新,其他槽位仍然可以被读取,保证了时间读取的高效性
if (tp->sec == sec) { tp->msec = msec; ngx_unlock(&ngx_time_lock); return; }
if (tp->sec == sec)
:检查缓存的时间(
tp->sec
)是否与当前获取的时间(sec
)一致,是否仍处于同一秒内若相等,说明时间未跨越整秒,只需更新毫秒部分
若不相等,说明已进入新的一秒,需触发完整的时间更新流程
避免在同一秒内重复生成时间字符串(如 HTTP 时间、日志时间),减少计算开销。
高频时间更新场景下(如每毫秒调用一次),此检查能显著提升性能。
tp->msec = msec;
如果时间值的秒数部分未发生变化,仅更新时间缓存中的毫秒数(
msec
)
ngx_unlock(&ngx_time_lock);
释放之前获取的互斥锁(
ngx_time_lock
),以允许其他线程访问时间更新函数如果时间值的秒数未发生变化,缓存更新已经完成,可以提前释放锁
防止因长时间持有锁导致其他线程等待,提升并发性能
ngx_unlock 函数
src/os/unix/ngx_atomic.h:310:#define ngx_unlock(lock) *(lock) = 0
src/os/unix/ngx_atomic.h 中:
#define ngx_unlock(lock) *(lock) = 0
将 lock 指向的内存值赋 0,释放之前获取的互斥锁
if (slot == NGX_TIME_SLOTS - 1) { slot = 0; } else { slot++; }
检查当前
slot
是否已经到达缓存数组cached_time
的最后一个位置
NGX_TIME_SLOTS
是数组的大小,因此最后一个索引是NGX_TIME_SLOTS - 1
如果当前
slot
已经到达数组末尾,则将slot
重置为0
,表示循环到数组的起始位置。如果当前
slot
尚未到达数组末尾,则将slot
增加1
,继续遍历数组。通过轮换时间槽的索引,实现了时间缓存的循环复用
tp = &cached_time[slot];
将指针
tp
指向cached_time
数组中索引为slot
的位置tp->sec = sec; tp->msec = msec;
将当前时间的秒数(
sec
)存储到时间缓存对象tp
中将当前时间的毫秒数(
msec
)存储到时间缓存对象tp
中ngx_gmtime(sec, &gmt);
将秒数时间值(
sec
)转换为人类可读的 GMT 时间格式
sec
(一个time_t
类型的时间值,表示自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数)转换为 GMT 时间,并将结果存储在gmt
结构体中。
ngx_gmtime
用于将时间值转换为 GMT 时间。ngx_gmtime 函数
src\core\ngx_times.h 中:
void ngx_gmtime(time_t t, ngx_tm_t *tp);
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_gmtime 函数-CSDN博客
p0 = &cached_http_time[slot][0];
获取当前时间槽(
slot
)对应的 HTTP 时间缓存字符串的起始地址
cached_http_time
是一个二维数组,每个slot
对应一个预分配的字符数组,用于存储格式化后的 HTTP 时间字符串。
slot
是一个循环索引,通过轮换槽位实现无锁更新:写入新时间到下一个槽位后,原子切换全局指针,避免读写竞争。为格式化的时间字符串(如 HTTP 报文头中的时间信息)准备一个存储空间。
通过直接操作缓冲区地址,避免频繁的内存分配和释放,提高效率
cached_http_time
src\core\ngx_times.c
static u_char cached_http_time[NGX_TIME_SLOTS] [sizeof("Mon, 28 Sep 1970 06:00:00 GMT")];
cached_http_time
是一个二维数组,用于缓存多个 HTTP 时间格式的字符串每个槽位存储一个时间字符串,格式为
"Mon, 28 Sep 1970 06:00:00 GMT"
(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);
ngx_sprintf
函数将格式化的时间字符串存储到p0
指向的缓冲区中
(void) ngx_sprintf(...)
显式忽略返回值,
ngx_sprintf
是Nginx自带的字符串格式化函数,返回写入的字节数,但这里不需要使用这个返回值。
p0
指针指向预分配的缓存数组
cached_http_time[slot][0]
,用于存储格式化后的HTTP时间字符串格式字符串解析
格式 :
"%s, %02d %s %4d %02d:%02d:%02d GMT"
分解:
%s
:星期缩写(如"Mon")%02d
:两位数的日期(不足补零)%s
:月份缩写(如"Jan")%4d
:四位数的年份%02d:%02d:%02d
:时:分:秒(两位数格式)GMT
:固定时区标识
ngx_sprintf
src\core\ngx_string.h 中:
u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);
src\core\ngx_string.c 中:
u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...) { u_char *p; va_list args; va_start(args, fmt); p = ngx_vslprintf(buf, (void *) -1, fmt, args); va_end(args); return p; }
ngx_sprintf
是一个用于格式化字符串的函数,类似于 C 标准库中的sprintf
底层实现依赖于
ngx_vslprintf
week[gmt.ngx_tm_wday]
获取星期几的缩写
gmt.ngx_tm_wday
:0-6(0=Sunday)
week[]
:预定义的星期字符串数组(如["Sun", "Mon", ...])src\core\ngx_times.c 中:
static char *week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
gmt.ngx_tm_mday
获取月份中的日期(1-31)
months[gmt.ngx_tm_mon - 1]
获取月份缩写
gmt.ngx_tm_mon
:1-12(1=January)
months[]
:预定义的月份字符串数组(如["Jan", "Feb", ...])
-1
:调整数组索引(从0开始)src\core\ngx_times.c 中:
static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
gmt.ngx_tm_year
获取年份(绝对年份,如2023)
gmt.ngx_tm_hour
小时(0-23)
gmt.ngx_tm_min
分钟(0-59)
gmt.ngx_tm_sec
秒(0-59)
#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
在 我现在的 Ubuntu 环境下
#elif (NGX_HAVE_GMTOFF)
这个条件成立
所以
ngx_localtime(sec, &tm); cached_gmtoff = (ngx_int_t) (tm.ngx_tm_gmtoff / 60); tp->gmtoff = cached_gmtoff;
被包含
ngx_localtime(sec, &tm);
将秒级时间戳转换为本地时间结构体
输入
sec
是自 Epoch 的秒数(UTC 时间)输出
tm
是包含本地时间的结构体(含时区信息)
cached_gmtoff = (ngx_int_t)(tm.ngx_tm_gmtoff / 60);
计算并缓存时区偏移(以分钟为单位)
ngx_tm_gmtoff成员是以秒为单位的时区偏移
除以 60 转换为分钟
强制转换为
ngx_int_t
tp->gmtoff = cached_gmtoff;
将时区偏移值存储到时间结构体
tp
指向当前时间槽的缓存时间结构体
gmtoff
字段用于存储时区偏移代码段整体逻辑:
- 通过
ngx_localtime
获取本地时间(含时区信息)- 从
tm
结构体提取秒级时区偏移,转换为分钟单位- 将计算结果缓存到全局变量和当前时间结构体
NGX_HAVE_GETTIMEZONE
Ubuntu 下 nginx-1.24.0 源码分析 - NGX_HAVE_GETTIMEZONE 宏-CSDN博客
NGX_HAVE_GMTOFF
Ubuntu 下 nginx-1.24.0 源码分析 - NGX_HAVE_GMTOFF 宏-CSDN博客
ngx_localtime
函数Ubuntu 下 nginx-1.24.0 源码分析 - ngx_localtime 函数-CSDN博客
static ngx_int_t cached_gmtoff;
cached_gmtoff
是一个全局变量,其作用是缓存当前时间的时区偏移量(以分钟为单位)。这个变量的主要目的是优化性能,避免在每次调用ngx_time_update
函数时重复计算时区偏移量。p1 = &cached_err_log_time[slot][0];
将指针
p1
指向二维数组cached_err_log_time
中当前时间槽(slot
)对应的字符串缓存的首地址
cached_err_log_time
是一个预分配的二维数组,用于存储不同时间槽的错误日志时间字符串。
slot
是当前时间槽的索引
[slot][0]
表示取第slot
个时间槽的字符串首地址
cached_err_log_time
cached_err_log_time
是一个缓存变量,用于存储格式化后的错误日志时间字符串。它的主要作用是优化错误日志记录的性能,避免每次记录错误日志时都重新格式化时间字符串。确保不同线程或请求之间不会相互干扰
(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);
将时间数据格式化为字符串并写入
格式字符串
"%4d/%02d/%02d %02d:%02d:%02d"
:
%4d
:4 位 年份(如2023
)
/%02d
:2 位 月份(不足补零,如03
表示三月)
/%02d
:2 位 日期(不足补零,如05
)
%02d:%02d:%02d
:2 位 小时、分钟、秒(不足补零)时间字段:
tm.ngx_tm_year
:四位数的年份(如2023
)。
tm.ngx_tm_mon
:月份(范围1-12
,直接显示为01
到12
)。
tm.ngx_tm_mday
:日期(范围1-31
)。
tm.ngx_tm_hour
:小时(范围0-23
)。
tm.ngx_tm_min
:分钟(范围0-59
)。
tm.ngx_tm_sec
:秒(范围0-60
,考虑闰秒)输出示例 :
2023/10/05 15:30:45
意图:
生成一个易读的本地时间字符串,用于错误日志的时间戳。
缓存此字符串,避免每次记录日志时重复格式化时间,提升性能。
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));
获取当前时间槽(
slot
)对应的缓存位置
cached_http_log_time
是一个二维数组,每个槽(slot
)存储一个时间字符
p2
指向当前槽的首地址,准备写入新的时间字符串将格式化后的时间字符串写入
p2
指向的缓存
%02d
:两位数的日期(不足补零,如02
)。/%s/
:缩写的月份(如Oct
)。%d
:四位数的年份(如2023
)。%02d:%02d:%02d
:两位数的时、分、秒(如14:05:09
)。%c%02i%02i
:时区符号(+
或-
)和两位数的时区偏移(如+0800
)
tm.ngx_tm_mday
:当前日期(1-31)。months[tm.ngx_tm_mon - 1]
:月份缩写。
tm.ngx_tm_mon
是 1-based(1=一月),所以减 1 转换为 0-based 的数组索引。months
是预定义的月份缩写数组(如Jan
,Feb
等)。时间部分
tm.ngx_tm_year
:四位数的年份(如2023
)。tm.ngx_tm_hour
:小时(0-23)。tm.ngx_tm_min
:分钟(0-59)。tm.ngx_tm_sec
:秒(0-59)。时区部分
tp->gmtoff < 0 ? '-' : '+'
:时区符号(负偏移用-
,正偏移用+
)。ngx_abs(tp->gmtoff / 60)
:时区偏移的小时部分(绝对值)。ngx_abs(tp->gmtoff % 60)
:时区偏移的分钟部分(绝对值)。**
tp->gmtoff
**以分钟为单位的时区偏移示例输出
假设当前时间为 2023 年 10 月 2 日 14:05:09,时区为东八区(+8:00):
- 格式化结果 :
02/Oct/2023:14:05:09 +0800
ngx_abs
src/core/ngx_core.h 中
#define ngx_abs(value) (((value) >= 0) ? (value) : - (value)) 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));
p3 = &cached_http_log_iso8601[slot][0];
cached_http_log_iso8601
是一个二维数组,每个槽位(slot
)存储一个预生成的 ISO 8601 时间字符串。
slot
表示当前活跃的缓存槽位,通过轮换槽位避免多线程/异步环境下的读写冲突。
p3
指向当前槽位的起始地址,后续代码将时间字符串写入此处
(void) ngx_sprintf(p3, "%4d-%02d-%02dT%02d:%02d:%02d%c%02i:%02i", ...);
作用 :将当前时间和时区信息格式化为 ISO 8601 字符串,并写入
p3
指向的缓存。参数解析:
格式字符串 :
"%4d-%02d-%02dT%02d:%02d:%02d%c%02i:%02i"
%4d
:4 位 年份(如2023
)。
%02d
:2 位 月份(如01
表示 1 月)。
%02d
:2 位 日期(如05
)。
T
:字面量字符,分隔日期和时间。
%02d
:2 位 小时(00-23)。
%02d
:2 位 分钟(00-59)。
%02d
:2 位 秒(00-59)。
%c
:时区符号(+
或-
)。
%02i:%02i
:时区偏移的小时和分钟部分(如+08:00
)。实际参数:
tm.ngx_tm_year
:年份(如2023
)。
tm.ngx_tm_mon
:月份(1-12,直接使用无需调整)。
tm.ngx_tm_mday
:日期(1-31)。
tm.ngx_tm_hour
:小时(0-23)。
tm.ngx_tm_min
:分钟(0-59)。
tm.ngx_tm_sec
:秒(0-59)。
tp->gmtoff < 0 ? '-' : '+'
:时区符号(负值为-
,正值为+
)。
ngx_abs(tp->gmtoff / 60)
:时区偏移的小时部分(绝对值)。
ngx_abs(tp->gmtoff % 60)
:时区偏移的分钟部分(绝对值)。
示例输出
假设当前时间为 2023 年 10 月 5 日 15:30:45,时区为东八区(+08:00):
格式化结果 :
2023-10-05T15:30:45+08:00
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);
p4 = &cached_syslog_time[slot][0];
将指针 `p4` 指向 `cached_syslog_time` 数组中当前 `slot` 对应的缓存位置的首地址。
`cached_syslog_time` 是一个二维数组,用于存储多个预生成的 syslog 时间字符串(通过轮换 `slot` 实现无锁更新)
(void) ngx_sprintf(p4, "%s %2d %02d:%02d:%02d", ...);
将格式化后的 syslog 时间字符串写入 `p4` 指向的缓存
格式字符串 "%s %2d %02d:%02d:%02d":
对应 `月份 日期 小时:分钟:秒`,例如 `Jan 5 08:05:03`。
`%s`:3 字母月份缩写(如 `Jan`)。
`%2d`:日期,固定 2 位宽度,单数日用空格填充(如 ` 5`)。
`%02d`:时、分、秒,固定 2 位宽度,不足补零(如 `08:05:03`)。
参数列表:
- `months[tm.ngx_tm_mon - 1]`:从月份数组中获取缩写名称。
`tm.ngx_tm_mon` 应为 `1-12`,减 1 后适配数组索引 `0-11`(假设 `months` 为 `["Jan", "Feb", ...]`)。
`tm.ngx_tm_mday`:月份中的日期(1-31)。
`tm.ngx_tm_hour`、`tm.ngx_tm_min`、`tm.ngx_tm_sec`:本地时间的小时、分钟、秒。
ngx_memory_barrier
ngx_memory_barrier();
ngx_memory_barrier()
是内存屏障(Memory Barrier),用于解决多线程环境下的 内存可见性 和 指令重排序 问题:
- 内存可见性:确保当前线程对内存的修改对其他线程立即可见。
- 指令重排序:防止编译器或 CPU 将屏障前后的指令乱序执行。
在 Nginx 这种高并发场景中,时间更新可能被多个线程(如 Worker 进程)竞争访问,内存屏障能确保全局时间变量的更新是原子且一致的。
这是 gcc -E 之后的结果
__sync_synchronize();
定义在
src/os/unix/ngx_atomic.h 中:
#define ngx_memory_barrier() __sync_synchronize()
objs/ngx_auto_config.h:9:#define NGX_HAVE_GCC_ATOMIC 1
这个宏存在时成立
在C语言中,
__sync_synchronize()
是一个由GCC(GNU Compiler Collection)提供的内置函数,用于实现内存屏障(Memory Barrier)或内存栅栏(Memory Fence)。它的主要作用是确保在多线程环境中,编译器和处理器不会对指令进行重排序,从而保证内存访问的顺序性。
__sync_synchronize()
的作用(1) 对编译器的影响
编译器在优化代码时,可能会重排指令以提高性能
使用
__sync_synchronize()
后,编译器会确保在此屏障之前的指令不会被重排到屏障之后,反之亦然(2) 对处理器的影响
处理器也可能对指令进行重排。例如,在某些架构上,写操作可能会被延迟,或者读操作可能会被提前。
内存屏障会强制处理器按照程序的逻辑顺序执行屏障前后的内存操作,并确保屏障之前的所有写操作在屏障之后的读操作之前完成。
(3) 对缓存的影响
内存屏障会强制处理器将屏障之前的写操作刷新到主存,同时使其他核心上的缓存失效(如果它们缓存了相同的内存地址)。
这样,其他线程在读取该内存地址时,会从主存或更新后的缓存中获取最新的值,而不是使用旧的缓存值。
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;
1
.ngx_cached_time = tp;
作用:更新全局时间结构体指针。
tp
指向当前时间槽(cached_time[slot]
),其中已存储最新时间(sec
和msec
)。通过将
ngx_cached_time
指向tp
,所有读取时间的线程无需加锁即可获取最新时间。
2.
ngx_cached_http_time.data = p0;
作用:设置 HTTP 协议标准时间格式缓存。
p0
指向预格式化的 HTTP 时间字符串(如Date: Wed, 21 Feb 2024 03:04:05 GMT
)。格式符合 RFC 1123,用于 HTTP 响应头(如
Date
、Last-Modified
)。
3.
ngx_cached_err_log_time.data = p1;
作用:设置错误日志时间格式缓存。
4.
ngx_cached_http_log_time.data = p2;
作用:设置 HTTP 访问日志时间格式缓存。
5.
ngx_cached_http_log_iso8601.data = p3;
作用:设置 ISO 8601 标准时间格式缓存。
6.
ngx_cached_syslog_time.data = p4;
作用:设置 Syslog 时间格式缓存。
ngx_unlock(&ngx_time_lock);
释放锁