nginx出于性能考虑採用类似lib_event的方式,自己对时间进行了cache,用来降低对gettimeofday()的调用,由于一般来说server对时间的精度要求不是特别的高,只是假设须要比較精确的timer,nginx还提供了一个timer_resolution指令用来设置时间精度。详细的机制再后面会做介绍。在ngx_times.c中提供了ngx_time_update()函数来更新时间缓存。另外另一个在信号处理中用来更新cached_err_log_time的ngx_time_sigsafe_update()函数。其它地方都是从时间缓存中取得时间。

因为nginx採用的是master-workers多进程的方式。每一个一进程都会自己维护一个时间缓存。那么在nginx中什么时候会更新时间缓存呢?上面说到nginx採用了2种方式来维护时间,首先来介绍没实用timer_resolution指令设置时间精度的情况。也就是ngx_timer_resolution为0的情况,实际上仅仅要找一下ngx_time_update()和ngx_time_sigsafe_update()这两个函数被调用的位置就知道。首先来说一下ngx_time_sigsafe_update(),它比較简单仅仅是更新了ngx_cached_err_log_time。它会在每次运行信号处理函数的时候被调用,也就是在ngx_signal_handler()函数中。ngx_time_update()函数在master进程中的ngx_master_process_cycle()主循环中被调用。详细位置为sigsuspend()函数之后。也就是说master进程捕捉到并处理完一个信号返回的时候会更新时间缓存;在worker进程中,ngx_time_update函数的调用链为ngx_worker_process_cycle()
-> ngx_process_events_and_timers() -> ngx_process_events() -> ngx_time_update(), 当中ngx_process_events()实际上一个宏,nginx中定义例如以下:

#define ngx_process_events   ngx_event_actions.process_events

而ngx_event_actions为nginx的I/O模型接口函数结构体。封装如epoll, kqueue,select,poll等这些提供的接口,这里仅对epoll进行分析,其它类似。于是ngx_event_actions.process_events 相应ngx_epoll_module.c文件里的 ngx_epoll_process_events()函数,在这个函数中运行epoll_wait()返回后会调用ngx_time_update()更新时间缓存,也就是当epoll通知有事件到达或者epoll超时返回后会更新一次时间;最后在cache_manager的进程也调用ngx_time_update()维护自己的时间缓存,这里不做介绍。

另外一种方式,ngx_timer_resolution被设置为大于0,也就是说。此时nginx的时间缓存精确到ngx_timer_resolution毫秒,详细的实现方法是在event模块的初始化函数ngx_event_process_init()中调用了setitimer()函数,它每隔ngx_timer_resolution毫秒会产生一个SIGALRM信号,这个信号的处理函数为ngx_timer_signal_handler(),定义例如以下:

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

}

它很easy,仅仅是将ngx_event_timer_alarm设置为1,用来记录有SIGALRM信号发生了,这时在来看ngx_epoll_process_events()函数。epoll_wait()的timeout被设置为-1。假设epoll_wait()是被SIGALRM信号唤醒。则调用ngx_time_update()更新时间缓存,否则继续使用之前的时间缓存。由于setitimer()每隔ngx_timer_resolution毫秒总会产生一次SIGALRM信号,这样就保证了时间缓存的精度为ngx_timer_resolution毫秒。这里仅仅介绍了worker进程的情况。其它进程类似。

ngx_time_update()和ngx_time_sigsafe_update()这两个函数的实现比較简单。可是还是有几个值得注意的地方。首先由于时间可能在信号处理中被更新,另外多线程的时候也可能同一时候更新时间(nginx如今尽管没有开放多线程。可是代码中有考虑),nginx使用了原子变量ngx_time_lock来对时间变量进行写加锁,并且nginx考虑到读时间的操作比較多。出于性能的原因没有对读进行加锁。而是採用维护多个时间slot的方式来尽量降低读訪问冲突。基本原理就是,当读操作和写操作同一时候发生时(1,多线程时可能发生。2,当进程正在读时间缓存时。被一信号中断去运行信号处理函数。信号处理函数中会更新时间缓存),也就是读操作正在进行时(比方刚拷贝完ngx_cached_time->sec,或者拷贝ngx_cached_http_time.data进行到一半时),假设写操作改变了读操作的时间。读操作终于得到的时间就变混乱了。nginx这里採用了64个slot时间,也就是每次更新时间的时候都是更新下一个slot。假设读操作同一时候进行,读到的还是之前的slot,并没有被改变,当然这里仅仅能是尽量降低了时间混乱的几率。由于slot的个数不是无限的。slot是循环的。写操作总有几率会写到读操作的slot上。只是nginx如今实际上并没有採用多线程的方式,并且在信号处理中仅仅是更新cached_err_log_time。所以对其它时间变量的读訪问是不会发生混乱的。
还有一个地方是两个函数中都调用了 ngx_memory_barrier() ,实际上这个也是一个宏,它的详细定义和编译器及体系结构有关,gcc和x86环境下,定义例如以下:

#define ngx_memory_barrier()    __asm__ volatile ("" ::: "memory")

它的作用实际上还是和防止读操作混乱有关,它告诉编译器不要将其后面的语句进行优化。不要打乱其运行顺序,详细还是来看一下 ngx_time_update函数:

ngx_time_update()

{

...

if (!ngx_trylock(&ngx_time_lock)) {

        return;

    }



   ...



    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%02d%02d",

                       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));





    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_unlock(&ngx_time_lock);

}

能够看到ngx_memory_barrier()之后是四条赋值语句。假设没有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分别和之前的tp = &cached_time[slot], p0 = &cached_http_time[slot][0], p1 = &cached_err_log_time[slot][0],
p2 = &cached_http_log_time[slot][0]合并优化掉,这种后果是ngx_cached_time,ngx_cached_http_time,ngx_cached_err_log_time, ngx_cached_http_log_time这四个时间缓存的不一致性时长增大了,由于在最后一个ngx_sprintf运行完后这四个时间缓存才一致,在这之前假设有其它地方正在读时间缓存就可能导致读到的时间不对或者不一致。而採用ngx_memory_barrier()后。时间缓存更新到一致的状态仅仅须要几个时钟周期。由于仅仅有四条赋值指令,显然在这么短的时间内发生读时间缓存的概率会小的多了。从这里能够看出Igor考虑是很仔细的。

nginx中时间的管理的更多相关文章

  1. nginx中的502错误

    遇到这种情况,首先看一下慢日志 [17-Aug-2015 13:13:43] WARNING: [pool www] child 27780, script '/data/s.com/index.ph ...

  2. Nginx中FastCGI配置优化

    FastCGI: FastCGI是从CGI发展改进而来的.传统CGI接口方式的主要缺点是性能很差,因为每次HTTP服务器遇到动态程序时都需要重新启动脚本解析器来执行解析,然后结果被返回给HTTP服务器 ...

  3. Nginx中防盗链(下载防盗链和图片防盗链)操作记录

    日常运维工作中,设置防盗链的需求会经常碰到,这也是优化网站的一个必要措施.今天在此介绍Nginx中设置下载防盗链和图片防盗链的操作~ 一.Nginx中下载防盗链的操作记录对于一些站点上的下载操作,有很 ...

  4. 解决IIS7、IIS7.5中时间格式显示的问题

    今天在用IIS7的时候发现一个关于时间格式的问题,当我在ASP中使用now()时间函数的时候,日期是以"/"来分隔,而不是以"-"来分隔的,使得我在运行程序的时 ...

  5. Spring中的事物管理,用 @Transactional 注解声明式地管理事务

    事物: 事务管理是企业级应用程序开发中必不可少的技术,  用来确保数据的 完整性和 一致性. 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用 事务的四 ...

  6. How Tomcat works — 八、tomcat中的session管理

    在使用shiro的session的时候感觉对于tomcat中session的管理还不是特别清楚,而且session管理作为tomcat中比较重要的一部分还是很有必要学习的. 目录 概述 session ...

  7. Nginx中的upstream轮询机制介绍

    Nginx中upstream有以下几种方式: 1.轮询(weight=1) 默认选项,当weight不指定时,各服务器weight相同, 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器d ...

  8. TCMalloc优化MySQL、Nginx、Redis内存管理

    TCMalloc(Thread-Caching Malloc)与标准glibc库的malloc实现一样的功能,但是TCMalloc在效率和速度效率都比标准malloc高很多.TCMalloc是 goo ...

  9. Spring中的事务管理

    事务简介: 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性 事务就是一系列的动作,它们被当作一个单独的工作单元.这些动作要么全部完成,要么全部不起作用 事务的四个关键属性( ...

随机推荐

  1. excel - 相等判断

    函数使用例子: =IF(EVALUATE(B23)=C23,"正确","错误") 即判断单元格'B23'与'C23'是否相等,若为true,则返回"正 ...

  2. Python文件操作方法

    python中对文件.文件夹(文件操作函数)的操作需要涉及到os模块和shutil模块.   得到当前工作目录,即当前Python脚本工作的目录路径: os.getcwd()   返回指定目录下的所有 ...

  3. SQL利用临时表实现动态列、动态添加列

    --方法一--------------------------------------------------------------------- declare @sql as varchar(1 ...

  4. 如何修改UITableView每个cell的分隔线和左边的距离?

    在ios7中,UITableViewCell左侧会有默认15像素的空白.这时候,设置setSeparatorInset:UIEdgeInsetsZero 能将空白去掉.但是在ios8中,设置setSe ...

  5. [转]STL的内存分配器

    题记:内存管理一直是C/C++程序的红灯区.关于内存管理的话题,大致有两类侧重点,一类是内存的正确使用,例如C++中new和delete应该成对出现,用RAII技巧管理内存资源,auto_ptr等方面 ...

  6. Android单位度量

    px(像素):屏幕上的点. in(英寸):长度单位.mm(毫米):长度单位.pt(磅):1/72英寸.dp(与密度无关的像素):一种基于屏幕密度的抽象单位.在每英寸160点的显示器上,1dp = 1p ...

  7. Mysql 数据类型使用说明

    FLOAT 和DOUBLE 类型支持使用标准的浮点运算进行近似计算. DECIMAL类型用于存储精确的小数. 因为cpu不支持对DECIMAL的直接计算,所以在Mysql5.0及更高的版本中,MYSQ ...

  8. 初涉JavaScript模式 (2) : 基本技巧

    尽量少用全局变量 大量使用全局变量会导致的后果 全局变量创建以后会在整个JavaScript应用和Web页面中共享.所有的全局变量都存在于一个全局命名空间内,很容易发生冲突 不知不觉创建了全局变量 其 ...

  9. highCharts 图表统计控件使用方法

    1.首先引用js文件 在引用上面文件时,保证已经引用了jquery.js文件.且位置在上面两个文件之前. 2. <div id="container" style=" ...

  10. jquery 的日期时间控件(年月日时分秒)

    <!-- import package --> <script type="text/javascript" src="JS/jquery.js&quo ...