做一个代码世界的设计师!🍺|

Jikefan

园龄:4年11个月粉丝:5关注:4

Nginx Network IO Module

转载 https://blog.stdin.in/blog/nginx-network-io-module/

在网上读过很多Nginx有关的关于网络IO模型的文章,总是感觉很凌乱,再加上最近一位老友密集追问下,重读Nginx源码,才知道自己和中文网络上的文章理解错误了很多东西。

先说 Nginx 的实现吧,这里只讨论 Linux 环境下的 epoll 网络模型。 先讨论一下 Nginx 是怎么监听并且处理连接的,在Nginx的master进程启动阶段读取所有配置,然后 bind + listen 相应的 socket端口,然后 fork 出来 n 个 worker 子进程,这些子进程会在 epoll 里面注册好这个 fork 出来继承的 listen fd。然后每个 worker 进程都会有一个 event loop 不停的去 epoll_wait 等待新的连接事件,如果是 listen fd 被 epoll_wait 到了,这个时候会尝试 accept 这个 fd 来获得新连接。和上述处理模型有关的有几个相关 Nginx 配置。下面的列表顺序就是时间线。

accept_mutex 这个是最老的配置,Nginx 的上述的这套模式,很多 worker 进程的 epoll_wait 同一个listen fd, 这个时候会产生一个经典的“惊群”效应,也就是来了一个新连接的话会唤醒所有的worker的 epoll_wait, 这个时候其实是只有一个 worker 会真正需要处理连接,其他的进程是“一场空”的,这个时候如果连接和worker的数目都很多的话,会导致很多不必要的上下文切换,所以Nginx给出了一个 accept_mutex 选项,在 1.11.3 版本之前,这个选项默认是给开的,逻辑是,开了这个选项的话, Nginx会在多进程内部有个用原子指令实现的多进程的锁,来保证 调用 listen fd 在一个时刻只能被一个worker epoll_wait,这样就能保证只会有一个进程被唤醒,避免了“惊群”问题。

ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"accept mutex locked");
if (ngx_accept_mutex_held && ngx_accept_events == 0) {
return NGX_OK;
}
// add listen fd to epoll
if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
ngx_shmtx_unlock(&ngx_accept_mutex);
return NGX_ERROR;
}
ngx_accept_events = 0;
ngx_accept_mutex_held = 1;
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"accept mutex lock failed: %ui", ngx_accept_mutex_held);
if (ngx_accept_mutex_held) {
// delete listen fd to epoll
if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
return NGX_ERROR;
}
ngx_accept_mutex_held = 0;
}
return NGX_OK;
}

reuseport 时间来到 Linux 3.9 时代,内核引入了全新的一个 socket 选项,这个选项开启了之后,很多个进程可以同时监听一个端口,新连接进来的时候,内核根据“五元组”来hash出来一个连接来具体分配给某个进程,这样其实就也解决了惊群问题了。这里 Nginx 有个非常不常规的实现,一般来说使用 reuseport 的话会在每个进程内自己进行 bind + listen 动作,但是Nginx这里在master进程里一次性监听了 n 个相同端口的fd, 然后所有的 worker 分别 epoll_wait 自己对应的 fd, 所以开了reuseport 之后的 Nginx的 socket状态会看到很多个 master 进程 listen 同一个端口。这个选项在 Nginx 1.9.1 版本中引入,但是并没有成为默认配置。

ngx_int_t
ngx_clone_listening(ngx_cycle_t *cycle, ngx_listening_t *ls)
{
#if (NGX_HAVE_REUSEPORT)
ngx_int_t n;
ngx_core_conf_t *ccf;
ngx_listening_t ols;
if (!ls->reuseport || ls->worker != 0) {
return NGX_OK;
}
ols = *ls;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
for (n = 1; n < ccf->worker_processes; n++) {
/* create a socket for each worker process */
ls = ngx_array_push(&cycle->listening);
if (ls == NULL) {
return NGX_ERROR;
}
*ls = ols;
ls->worker = n;
}
#endif
return NGX_OK;
}
static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
...
...
/* for each listening socket */
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
#if (NGX_HAVE_REUSEPORT)
if (ls[i].reuseport && ls[i].worker != ngx_worker) {
// jump to another worker listen fd
continue;
}
#endif
c = ngx_get_connection(ls[i].fd, cycle->log);
...
#if (NGX_HAVE_REUSEPORT)
if (ls[i].reuseport) {
// add listen fd read event to epoll
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
continue;
}
#endif
if (ngx_use_accept_mutex) {
continue;
}
...
}
return NGX_OK;
}

EPOLLEXCLUSIVE Linux 4.5 epoll 引入了这个参数,我觉得这个参数的引入才是真正的在尝试解决“惊群”问题(reuseport主要目的是为了多进程能够监听同一个端口,然后恰好解决了“惊群”问题)。这个选项告诉内核,内核在收到连接的时候,不要唤醒所有的监听进程,只唤醒一个。它降低了多个进程/线程通过epoll_ctl 添加共享fd 引发的惊群概率,使得一个事件发生时,只唤醒一个正在epoll_wait 阻塞等待唤醒的进程/线程(而不是全部唤醒)。这个选项在Nginx 1.11.3 引入,在这个版本之前Nginx的配置是默认开启了 accept_mutex 配置,在这之后就关闭了 accept_mutex 默认配置,因为默认有了 EPOLLEXCLUSIVE 方案来避免“惊群”问题。

所以网上有很多的文章说 Nginx默认开启了 reuseport 来避免“惊群”问题,这个理解其实不对的,reuseport 这个选项从来都没有在Nginx中默认打开过,其实也不需要打开。

相关链接

https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-broken-12/
https://idea.popcount.org/2017-03-20-epoll-is-fundamentally-broken-22/
https://lwn.net/Articles/542629/
https://lwn.net/Articles/667087/
https://lpc.events/event/11/contributions/946/attachments/783/1472/Socket_migration_for_SO_REUSEPORT.pdf

本文作者:jikefan

本文链接:https://www.cnblogs.com/jikefan/articles/18233541

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Jikefan  阅读(7)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.

作曲 : Reol

作词 : Reol

fade away...do over again...

fade away...do over again...

歌い始めの一文字目 いつも迷ってる

歌い始めの一文字目 いつも迷ってる

どうせとりとめのないことだけど

伝わらなきゃもっと意味がない

どうしたってこんなに複雑なのに

どうしたってこんなに複雑なのに

噛み砕いてやらなきゃ伝わらない

ほら結局歌詞なんかどうだっていい

僕の音楽なんかこの世になくたっていいんだよ

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.

目の前 広がる現実世界がまた歪んだ

目の前 広がる現実世界がまた歪んだ

何度リセットしても

僕は僕以外の誰かには生まれ変われない

「そんなの知ってるよ」

気になるあの子の噂話も

シニカル標的は次の速報

麻痺しちゃってるこっからエスケープ

麻痺しちゃってるこっからエスケープ

遠く遠くまで行けるよ

安定なんてない 不安定な世界

安定なんてない 不安定な世界

安定なんてない きっと明日には忘れるよ

fade away...do over again...

fade away...do over again...

そうだ世界はどこかがいつも嘘くさい

そうだ世界はどこかがいつも嘘くさい

綺麗事だけじゃ大事な人たちすら守れない

くだらない 僕らみんなどこか狂ってるみたい

本当のことなんか全部神様も知らない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.