connection
简介
在Nginx中connection就是封装了TCP连接,其中包括连接的socket,读写事件。利用nginx封装的connection,方便的处理与连接相关的事情,比如,建立连接,发送与接收数据等。Nginx中http请求的处理就是建立在connection上,所以Nginx不仅可以作为一个web服务器,也可以是邮件服务器。我们可以用connection与任何后端服务打交道。
连接
结合一个TCP连接的生命周期,我们看看nginx对连接的处理。
- nginx启动时,会解析配置文件,得到需要监听的端口与IP地址,在nginx的master进程里初始化好这个监控的socket(创建socket,设置addrreuse等选项,绑定到指定的IP地址端口,在listen)
- fork出多个子进程,子进程会竞争accept新的链接。
- 客户端可以向nginx发起连接了,当客户端与服务端通过三次握手建立一个链接后,nginx的某子进程会accept成功,得到这个建立好的socket,然后创建对nginx的封装,即ngx_connection_t结构体。
- 设置读写事件的处理函数并添加读写事件与客户端进行数据交换
- 任意一方关闭连接
nginx也可以作为客户端来请求其他server的数据(如upstream模块),此时,与其他server创建的连接封装在ngx_connection_t中。
- 作为客户端,nginx先获取一个ngx_connection_t结构体,然后创建socket,并设置socket的属性(比如非阻塞),
- 然后通过添加读写事件,调用connect/read/write来调用连接
- 最后关闭连接,释放ngx_connection_t结构体。
连接数
在nginx中,每个进程有一个连接数的最大上限,这个跟系统对fd的限制不一样。在操作系统中,通过ulimit -n,我们可以得到一个进程所能够打开的fd的最大数,即nofile,因为每个socket连接会占用一个fd,所以这限制了我们进程的最大连接数,直接影响到我们程序所能支持的最大并发数,当fd用完后,在创建socket就会失败。不过这里nginx对连接数的限制与nofile没有直接联系。nginx通过设置worker_connections设置每个进程可使用的连接最大值。nginx实现时,通过一个连接池来管理的,每个worker进程都有一个独立的连接池,连接池的大小是worker_connections。这里连接池不是真实的连接。是一个 worker_connections 大小的一个 ngx_connection_t 结构的数组。并且, nginx 会通过一个链表 free_connections 来保存所有的空闲 ngx_connection_t,每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面。
woeker_connections这个值是表示每个 worker 进程所能建立连接的最大值,所以,一个 nginx 能建立的最大连接数,应该是worker_connections * worker_processes。这里说的是最大连接数,对于 HTTP 请求本地资源来说,能够支持的最大并发数量是 worker_connections * worker_processes,而如果是 HTTP 作为反向代理来说,最大并发数量应该是 worker_connections *worker_processes/2。因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,会占用两个连接。
连接控制
之前说过,一个客户端连接过来后,多个空闲的进程,会竞争这个连接,这种竞争会导致不公平,如果某个进程得到accept机会较多,空闲连接很快就用完了,如果不提前做一些控制,当acceot到一个新tcp连接后,因为无法得到新空闲连接,而且无法将此链接转交给其他进程,最终导致tcp得不到处理,就中止掉了。
那么如何解决呢?
- nginx处理得先打开accept_mutex选项,此时,只有获得了 accept_mutex 的进程才会去添加 accept 事件,也就是说, nginx 会控制进程是否添加 accept 事件。nginx 使用一个叫 ngx_accept_disabled 的变量来控制是否去竞争 accept_mutex 锁。
- 在第一段代码中,计算 ngx_accept_disabled 的值,这个值是 nginx 单进程的所有连接总数的八分之一,减去剩下的空闲连接数量,得到的这个 ngx_accept_disabled 有一个规律,当剩余连接数小于总连接数的八分之一时,其值才大于 0,而且剩余的连接数越小,这个值越大。
- 再看第二段代码,当 ngx_accept_disabled 大于0 时,不会去尝试获取 accept_mutex 锁,并且将 ngx_accept_disabled 减 1,于是,每次执行到此处时,都会去减 1,直到小于 0。不去获取 accept_mutex 锁,就是等于让出获取连接的机会,很显然可以看出,当空余连接越少时, ngx_accept_disable 越大,于是让出的机会就越多,这样其它进程获取锁的机会也就越大。不去 accept,自己的连接就控制下来了,其它进程的连接池就会得到利用,这样,nginx 就控制了多进程间连接的平衡了。
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
if (ngx_accept_disabled > 0)
{
ngx_accept_disabled--;
} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR)
{
return;
}
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;
}
}
}
好了,连接的基本概念就先介绍到这。