Ubuntu 下 nginx-1.24.0 源码分析 - ngx_time_update函数

定义在 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 中用于高效更新时间缓存的核心函数,目的是通过预生成多种时间格式,避免频繁的系统调用和格式化开销,从而提升性能。

  1. 加锁与时间获取
  • 通过 `ngx_trylock` 尝试获取 `ngx_time_lock` 锁,若失败则直接返回(避免阻塞)。

  • 调用 `ngx_gettimeofday` 获取当前时间(秒 `tv.tv_sec` 和微秒 `tv.tv_usec`),并计算毫秒 `msec`。

  • 更新全局单调时间 `ngx_current_msec`(用于高性能计时,避免系统时间跳变的影响)。

  1. 时间缓存更新
  • 使用 `cached_time` 数组作为循环缓冲区(`NGX_TIME_SLOTS` 个槽位),存储秒级时间戳和毫秒。

  • 若当前槽位的秒数未变化(`tp->sec == sec`),仅更新毫秒并返回,减少重复计算。

  • 若时间已跨秒,切换到下一个槽位(循环),更新秒和毫秒。

  1. 时间格式化
  • 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`)。

  1. 内存屏障与全局发布
  • 通过 `ngx_memory_barrier` 确保缓存数据写入完成。

  • 更新全局指针(如 `ngx_cached_time`、`ngx_cached_http_time.data`),使新时间数据对其他线程可见。

  1. 释放锁
  • 完成更新后释放 `ngx_time_lock`。

意图

  1. 高性能时间管理
  • 通过缓存时间数据和预格式化字符串,避免每次获取时间时的重复计算(如 `ngx_sprintf`),显著减少系统调用和格式化开销。

  • 使用循环缓冲区(多槽位)实现无锁读取:写入新槽位时,其他线程仍可读取旧槽位的有效数据。

  1. 线程安全与原子性
  • 通过 `ngx_time_lock` 保证时间更新的原子性。

  • 通过内存屏障确保全局指针更新的可见性,避免部分更新导致的数据不一致。

  1. 平台兼容性
  • 通过条件编译处理不同平台的时区获取方式(`NGX_HAVE_GETTIMEZONE`、`NGX_HAVE_GMTOFF`),增强跨平台兼容性。
  1. 多样化时间格式支持
  • 预生成 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; 的作用是声明两个变量 tmgmt

它们分别用于存储本地时间和格林威治标准时间(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)

这行代码的意图是将当前的时间值(secmsec)转换为单调时间值,并更新到 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 字段用于存储时区偏移

代码段整体逻辑

  1. 通过 ngx_localtime 获取本地时间(含时区信息)
  2. tm 结构体提取秒级时区偏移,转换为分钟单位
  3. 将计算结果缓存到全局变量和当前时间结构体

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,直接显示为 0112)。

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]),其中已存储最新时间(secmsec)。

通过将 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 响应头(如 DateLast-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);

释放锁

相关推荐
眠修2 分钟前
Kuberrnetes 服务发布
linux·运维·服务器
即将头秃的程序媛3 小时前
centos 7.9安装tomcat,并实现开机自启
linux·运维·centos
fangeqin3 小时前
ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法
linux·python·ubuntu·openssl
爱奥尼欧4 小时前
【Linux 系统】基础IO——Linux中对文件的理解
linux·服务器·microsoft
超喜欢下雨天5 小时前
服务器安装 ros2时遇到底层库依赖冲突的问题
linux·运维·服务器·ros2
tan77º6 小时前
【Linux网络编程】网络基础
linux·服务器·网络
风口上的吱吱鼠6 小时前
Armbian 25.5.1 Noble Gnome 开启远程桌面功能
服务器·ubuntu·armbian
笑衬人心。6 小时前
Ubuntu 22.04 + MySQL 8 无密码登录问题与 root 密码重置指南
linux·mysql·ubuntu
生如夏花℡7 小时前
HarmonyOS学习记录3
学习·ubuntu·harmonyos
chanalbert8 小时前
CentOS系统新手指导手册
linux·运维·centos