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

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

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

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

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

Nginx开发从入门到精通



#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方法:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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自己解决了惊群问题。

```c
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函数

```c
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函数接着往下运行

```c
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配置](http://huoding.com/2013/08/24/281)

引用文章的话

"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关闭以后连接的负载均衡功能也就关闭了。

```c
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这个进程内的全局变量。

```c
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的这个方式就挺合适的。


未完待续