Why
nginx把整个时间的系统调用都封装了一遍。
其中有两个原因
1
不谈其他平台,至少在linux上,很多时间相关的系统调用不是async-signal-safe或thread-safe的。
例如local_time_r虽然是thread-safe的但不是async-signal-safe的,当然local_time两个都不是。
这里不是专门谈什么是async-siganl-safe的,对此概念上有疑惑或者错用后危害上有疑惑的可以点这个博文。写的相当透彻。
这篇文章主要是讲twemproxy遇到的一个死锁BUG(我在线上也遇到这样的问题了),连twitter的明星coder都会犯这个错误。可见还是需要注意这一点的。
twemproxy-deadlock-on-signal_handler
2 另外则是减少系统调用的耗时,毕竟web
server不是那么要求时间的精确性,但是如果有场合需要,nginx还是提供了参数来控制时间的精确性。
How
先看大逻辑再看细节吧。
大逻辑
前文已经分析过nginx的启动流程的。
可以看到时间缓存的更新是在ngx_epoll_process_events函数里面的
1 2 3 4 5 6 7 8 9
| static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) { events = epoll_wait(ep, event_list, (int) nevents, timer); if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { ngx_time_update(); } }
|
当flag为NGX_UPDATE_TIME或者ngx_event_timer_alarm不为0时进行时间更新。
这分别代表两个策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void ngx_process_events_and_timers(ngx_cycle_t *cycle) { ngx_uint_t flags; ngx_msec_t timer, delta;
if (ngx_timer_resolution) { timer = NGX_TIMER_INFINITE; flags = 0;
} else { timer = ngx_event_find_timer(); flags = NGX_UPDATE_TIME; }
(void) ngx_process_events(cycle, timer, flags); }
|
策略一:当未设置ngx_timer_resolution时直接每一次调用后都更新时间
策略二:当设置了ngx_timer_resolution后会如何呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) { if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) { struct sigaction sa; struct itimerval itv;
sa.sa_handler = ngx_timer_signal_handler; if (sigaction(SIGALRM, &sa, NULL) == -1) {}
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {} } } void ngx_timer_signal_handler(int signo) { ngx_event_timer_alarm = 1; }
|
可以看到,在最开始事件循环初始化时设置了一个时间信号函数,每隔ngx_timer_resolution时间进行触发
触发函数把ngx_event_timer_alarm设置为1
随后当epoll被信号触发后不在阻塞立刻返回,errno设置为EINTR
随后进行时间更新
时间更新的实现
再看core/ngx_times.c里核心函数ngx_time_update的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| void ngx_time_update(void) { u_char *p0, *p1, *p2, *p3; 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_msec_t) sec * 1000 + msec;
tp = &cached_time[slot];
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);
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);
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_unlock(&ngx_time_lock); }
|
可以看到这里考虑到了很多问题。
比如使用了NGX_TIME_SLOTS大的缓存数组,来进行更新,这是为了防止多线程下或者信号函数读时,这里正在进行更新操作而导致不一致。
目前并未使用多线程机制,所以64这个值已经很大了,我认为使用2也可以同样满足需求。
另外ngx_memory_barrier内存屏障,是个宏
1
| #define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
|
当然在不同平台下有不同实现,总得来说都是让编译器不要优化这一段合并到上面去,让p0,p1,p2,p3的值一起更新到time_cache中去。
编译器可能会优化成这样的顺序:
1 2 3 4 5
| p0 = &cached_http_time[slot][0]; ngx_cached_http_time.data = p0; p1 = &cached_err_log_time[slot][0]; ngx_cached_err_log_time.data = p1; ...
|
加了编译器级别的内存屏障以后,在多线程情况下。
能够使得大部分情况下p0,p1,p2,p3都是代表同一时刻的值。
极小部分情况下当前p0值的代表的时刻可能比p1,p2,p3代表的新。
这里细节处理的很厉害。
顺带提一下ngx_times.c的其他函数
ngx_time_init时间初始化
ngx_time_sigsafe_update当信号切入时进行时间更新,只更新了ngx_cached_err_log_time
ngx_http_time和ngx_http_cookie_time,http和http_cookie的时间格式化,调用了ngx_gmtime
ngx_gmtime(time_t t, ngx_tm_t *tp)使用了自己的算法来进行时间转换
time_t ngx_next_time(time_t when)
这里when参数只是代表了当天的时间,只有秒+分钟+小时,顾名思义,寻找下一次这个时间点出现的时候的绝对时间time_t值
比如已经当前已经过了12点0分0秒,那么返回的就是下一天的12点0分0秒的,从标准计时点(一般是1970年1月1日午夜)到当前时间的秒数。否则则是当天的。