熟悉nginx的时间机制,主要从以下几点
1)Linux系统时间相关函数
2)nginx是怎么通过缓存时间变量减少gettimeofday系统调用,从而提高效率
3)什么时候,怎么更新时间缓存的?
1)Linux系统时间相关的库函数介绍 char *asctime(const struct tm* timeptr); //将结构中的信息转换为真实世界的时间,以字符串的形式显示”Thu Nov 17 20:40:10 2016“”; char *ctime(const time_t *timep); //将timep转换为真是世界的时间,以字符串显示,它和asctime不同就在于传入的参数形式不一样; double difftime(time_t time1, time_t time2); //返回两个时间相差的秒数; struct tm* gmtime(const time_t *timep); //将time_t表示的时间转换为没有经过时区转换的UTC时间,是一个struct tm结构指针; stuct tm* localtime(const time_t *timep); //和gmtime类似,但是它是经过时区转换的时间。 time_t mktime(struct tm* timeptr); //将struct tm 结构的时间转换为从1970年至今的秒数; time_t time(time_t *t); //取得从1970年1月1日至今的秒数。 int gettimeofday(struct timeval *tv, struct timezone *tz); //返回当前距离1970年的秒数和微妙数,后面的tz是时区,一般不用;例如:1479386410(参数tv.tv_sec的值--->经过ctime转换后得到(Thu Nov 17 20:40:10 2016)
2)nginx开始设计的时候,就考虑到设计在时间更新的时候,如何减少系统调用? nginx主要通过下面四个时间变量进行缓存,然后更新缓存时间来提高效率的,而不是每次都调用gettimeofday,因此如何更新缓存时间保证和实际时间一致,而又减少系统调用是nginx时间管理机制的重点。nginx的缓存时间变量主要用下面几个变量: volatile ngx_msec_t ngx_current_msec; volatile ngx_time_t *ngx_cached_time; volatile ngx_str_t ngx_cached_err_log_time; volatile ngx_str_t ngx_cached_http_time; volatile ngx_str_t ngx_cached_http_log_time; volatile ngx_str_t ngx_cached_http_log_iso8601; static ngx_time_t cached_time[NGX_TIME_SLOTS]; static u_char cached_err_log_time[NGX_TIME_SLOTS] [sizeof("1970/09/28 12:00:00")]; static u_char cached_http_time[NGX_TIME_SLOTS] [sizeof("Mon, 28 Sep 1970 06:00:00 GMT")]; static u_char cached_http_log_time[NGX_TIME_SLOTS][sizeof("28/Sep/1970:12:00:00 +0600")]; static u_char cached_http_log_iso8601[NGX_TIME_SLOTS] [sizeof("1970-09-28T12:00:00+06:00")]; 缓存时间的更新主要是:ngx_time_update 和 ngx_time_sigsafe_update ngx_time_update 是nginx时间管理的核心函数 1)时间数据的一致性主要是读写上,首先要防止写冲突,对写时间缓存加锁,因为读时间操作远大于写时间,所以读操作没有加锁 2)还有另外一种是,在读的时候,另外有个信号中断了读操作,而这个信号里面更新了时间缓存变量导致读的时间不一致,nginx通过时间数组变量来解决这个问题 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; //ngx_time_lock加锁是为了解决信号处理过程中更新缓存时间产生的时间不一致的问题。 if (!ngx_trylock(&ngx_time_lock)) { return; } //该函数时间更新实际就是调用系统函数,只是对#define ngx_gettimeofday(tp) (void) gettimeofday(tp, NULL); ngx_gettimeofday(&tv); sec = tv.tv_sec; msec = tv.tv_usec / 1000; //nginx将时间统一换成毫秒级别,这样时间精度准确,而没有换成微妙,恰当好处 ngx_current_msec = (ngx_msec_t) sec * 1000 + msec; //#define NGX_TIME_SLOTS 64 tp = &cached_time[slot]; //由于nginx在读时间缓存比写换件缓存的次数远远要多,处于性能考虑所以没有对读进行加锁,看这前后几行代码,大牛是如何解决这个读写不一致的问题, 当前面已经有一个读时间slot等于10,本来读时间是cached_time[10]的值,结果来了个信号中断读,但是这个信号中又写时间了,下面这个判断如果这个 读时间变量和要写的时间一致直接返回,如果不一致,对slot++,写完后,上次本来要读取的时间是cached_time[10],但是每次读取的时候是数组里面最新的那个时间 也就是cached_time[11]。有没有觉得这种解决办法很简单,但有时候你解决的时候死活想不到。复杂问题,简单处理。精髓之处,也是我们平时值得学习借鉴应用的地方 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, d %s M d:d:d 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, "M/d/d d:d:d", 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, "d/%s/%d:d:d:d %cdd", 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, "M-d-dTd:d:d%cd:d", 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)); //这个函数也是宏定义,里面兼容了各种平台,volatile关键字就知道主要告诉编译器不要对后面的语句进行优化了, 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_sigsafe_update机制上和ngx_time_update差不多,只是更新了一个缓存时间,原理一样。
上面两个函数重点是理解nginx在时间管理上在更新缓存时间怎么处理这种数据不一致的问题,3)缓存时间的精度以及如何nginx是什么时候更新缓存时间
要更新先了解一下ngx_time_update调用开销主要在些地方:
1)服务器主进程ngx_master_process_cycle中有一次调用
2)缓存索引管理
3)事件处理函数中(epoll,select,kqueue)
其中更新缓存时间ngx_time_update最主要的开销在事件处理函数中,下面就以epoll为例分析
缓存时间精度控制,通过ngx_timer_resolution指令配置,默认为100ms,ngx_event_process_init函数里面进行初始化的
ngx_event_process_init函数的665~688这段代码
if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) { struct sigaction sa; struct itimerval itv; ngx_memzero(&sa, sizeof(struct sigaction)); sa.sa_handler = ngx_timer_signal_handler; //更新时间标志位ngx_event_timer_alarm的信号处理函数 sigemptyset(&sa.sa_mask); //初始化一个信号SIGALRM,然后调用settimer实现每隔ngx_timer_resolution 产生一个SIGALRM信号, if (sigaction(SIGALRM, &sa, NULL) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "sigaction(SIGALRM) failed"); return NGX_ERROR; } itv.it_interval.tv_sec = ngx_timer_resolution / 1000; itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000; itv.it_value.tv_sec = ngx_timer_resolution / 1000; itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000; if (setitimer(ITIMER_REAL, &itv, NULL) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "setitimer() failed"); } }
//信号处理函数对ngx_event_timer_alarm 标志位置1,然后在事件处理函数中(ngx_epoll_process_events)标志位置1,会调用ngx_time_update()
static void ngx_timer_signal_handler(int signo) { ngx_event_timer_alarm = 1; #if 1 ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer signal"); #endif } static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) { int events; uint32_t revents; ngx_int_t instance, i; ngx_uint_t level; ngx_err_t err; ngx_event_t *rev, *wev, **queue; ngx_connection_t *c; /* NGX_TIMER_INFINITE == INFTIM */ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll timer: %M", timer); events = epoll_wait(ep, event_list, (int) nevents, timer); err = (events == -1) ? ngx_errno : 0; //判断条件中,除了前面的true会更新,ngx_event_timer_alarm为1也会对时间缓存进行更新 if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { ngx_time_update(); } } 综合以上:nginx更新缓存时间主要是通过ngx_timer_resolution时间频率发送信号SIGALRM处理,从而达到更新缓存时间的目的