(nginx源码系列一)--nginx源代码初步学习

原创内容,转载请注明出处

Posted by Weakyon Blog on January 28, 2015

nginx真是博大精深,最近写fastdfs的动态缩略图模块,刚好能有时间研究下,真是满心欢喜。

学习主要是靠两本书,其他的稍微搜搜也就能解惑了。

《Nginx模块开发与架构解析》这个我看的电子PDF

《Nginx开发从入门到精通》这个有网页版本的,挺好的:

Nginx开发从入门到精通


nginx源代码初步学习(1)——–《Nginx模块开发与架构解析》的勘误

nginx源代码初步学习(2)——–nginx accept惊群问题探究


#nginx源代码初步学习(1)——–《Nginx模块开发与架构解析》的勘误#

先贴个《Nginx模块开发与架构解析》的勘误吧,我在这本书的勘误网站上也没看到

书里10.6.11说到NGX_HTTP_CONTENT_PHASE有两种介入方式,一种是在postconfiguration方法push进handler方法,第二种是通过设置ngx_http_core_loc_conf_t结构体的handler指针来实现的。

在382页的第五段和第六段,说第一种方式的handler无论返回什么值就立刻调用ngx_http_finalize_request,第二种方式的handler如果返回NGX_DECLINED,就按顺序处理下一个handler。

这是搞反了,实际上第二种方式会立刻调用ngx_http_finalize_request,第一种会判断是否要接着处理下一个handler。

贴代码,这是NGX_HTTP_CONTENT_PHASE的checker方法:

  
ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph)
{
	//省略
	...
	
    if (r->content_handler) {
        r->write_event_handler = ngx_http_request_empty_handler;
        ngx_http_finalize_request(r, r->content_handler(r));
        return NGX_OK;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "content phase: %ui", r->phase_handler);

    rc = ph->handler(r);

    if (rc != NGX_DECLINED) {
        ngx_http_finalize_request(r, rc);
        return NGX_OK;
    }

    /* rc == NGX_DECLINED */

    ph++;

    if (ph->checker) {
        r->phase_handler++;
        return NGX_AGAIN;
    }

	//省略
	...

    return NGX_OK;
}

可以看到判断r->content_handler,如果存在就会立刻调用ngx_http_finalize_request,而r->content_handler 就是运行时刻从location handler中注册的,也就是第二种方法。


#nginx源代码初步学习(2)——–nginx accept惊群问题探究#

虽然大部分操作系统都解决了惊群问题,但是为了实现一个高可移植的系统,nginx自己解决了惊群问题。

  
void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
	//省略
	...

    if (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } else {
			//尝试获取accept锁
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }
			//如果获取到了,那么进程加上NGX_POST_EVENTS标志
            if (ngx_accept_mutex_held) {
                flags |= NGX_POST_EVENTS;

            } else {
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }

    delta = ngx_current_msec;

    (void) ngx_process_events(cycle, timer, flags);

    delta = ngx_current_msec - delta;

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "timer delta: %M", delta);

}

进入ngx_process_events函数

  
if (flags & NGX_POST_EVENTS) {
    queue = (ngx_event_t **) (rev->accept ?
            &ngx_posted_accept_events : &ngx_posted_events);

    ngx_locked_post_event(rev, queue);

} else {
    rev->handler(rev);
}

当设置了NGX_POST_EVENTS时不会立刻执行,如果是新连接就放入ngx_posted_accept_events队列,否则放入ngx_posted_events队列

回到ngx_process_events_and_timers函数接着往下运行

  
void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
	//省略
	...

	if (ngx_posted_accept_events) {
        ngx_event_process_posted(cycle, &ngx_posted_accept_events);
    }

    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }

    if (delta) {
        ngx_event_expire_timers();
    }

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "posted events %p", ngx_posted_events);

    if (ngx_posted_events) {
        if (ngx_threaded) {
            ngx_wakeup_worker_thread(cycle);

        } else {
            ngx_event_process_posted(cycle, &ngx_posted_events);
        }
    }
}

先运行ngx_posted_accept_events队列中的事件,然后释放accept锁,然后运行ngx_posted_events队列中的事件,这样的处理能防止太久的posted队列处理占用太久accept锁

nginx的设计还是很不错的,但是我后来看到有篇文章觉得这样的设定不好

闲扯Nginx的accept_mutex配置

引用文章的话

“Nginx缺省激活了accept_mutex,也就是说不会有惊群问题,但真的有那么严重么?实际上Nginx作者Igor Sysoev曾经给过相关的解释:

OS may wake all processes waiting on accept() and select(), this is called thundering herd problem. This is a problem if you have a lot of workers as in Apache (hundreds and more), but this insensible if you have just several workers as nginx usually has. Therefore turning accept_mutex off is as scheduling incoming connection by OS via select/kqueue/epoll/etc (but not accept()).

简单点说:Apache动辄就会启动成百上千的进程,如果发生惊群问题的话,影响相对较大;但是对Nginx而言,一般来说,worker_processes会设置成CPU个数,所以最多也就几十个,即便发生惊群问题的话,影响相对也较小。”

这里说的也很有道理,nginx的进程不多,所以惊群其实没那么严重。

但是文章说建议关闭accept_mutex,这就扯蛋了,accept_mutex关闭以后连接的负载均衡功能也就关闭了。

  
if (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } else {
			//尝试获取accept锁
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }

在ngx_event_accept中,ngx_accept_disabled会被赋值,每次新连接进入都会刷新ngx_accept_disabled这个进程内的全局变量。

  
ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;

如果当前使用连接达到总连接的7/8,那么就不会处理新连接,同时每次处理旧连接的时候都将ngx_accept_disabled减一。

直到ngx_accept_disabled小于0才会继续处理新连接,从而能够重新刷新ngx_accept_disabled值。

小结:

对nginx来说,关闭accept_mutex是不可取的,会影响连接的负载均衡。

但是如果设计一个新系统,如果同样是nginx的架构,可以不去考虑惊群的问题,但是需要用一些机制去处理负载均衡的问题,nginx的这个方式就挺合适的。


未完待续

28 Jan 2015