Nginx优化长连接

引用:

一、nginx之tcp_nooush、tcp_nodelay、snefile

1. TCP_NODELAY

怎么强制socket在它的缓冲区里发送数据?
一个解决方案是TCP堆栈的TCP_NODELAY选项。这样就可以使缓冲区中的数据立即发送出去。
Nginx的TCP_NODELAY选项使得在打开一个新的socket时增加了TCP_NODELAY选项。但这时会造成一种情况:
终端应用程序每产生一次操作就会发送一个包,而典型情况下下一个包会拥有一个字节的数据以及40个字节长的包头,于是产生4000%的过载,很轻易的能令网络发生拥塞。为了避免这种情况,TCP堆栈实现了等待数据0.2秒钟,因此操作后它不会发送一个数据包,而是将这段时间内的数据打成一个大的包。这一机制是由Nagle算法保证。

Nagle化后来成了一种标准并且立即在因特网上得以实现。它现在已经成为默认配置了,但是有些场合下把这一选项关掉也是合乎需要的。现在假设某个应用程序发出了一个请求,希望发送小块数据。我们可以选择立即发送数据或者等待产生更多的数据然后再一次发送两种策略。
如果我们马上发送数据,那么交互性的以及客户/服务器型的应用程序将极大地受益。如果请求立即发出那么响应时间也会快一些。**以上操作可以通过设置套接字的TCP_NODELAY = on 选项来完成,这样就禁用了Nagle算法。(不需要等待0.2s)

2. TCP_NOPUSH

在nginx中,tcp_nopush配置和tcp_nodelay“互斥”。它可以配置一次发送数据的包大小。也就是说,它不是按时间累计0.2秒后发送包,而是当宝累计到一定大小后就发送。
注:在nginx中,tcp_nopush必须和sendfile搭配使用。

3. sendfile

现在流行的web服务器里面都提供sendfile选项用来提高服务器性能,那到底sendfile是什么,怎么影响性能的呢?
sendfile实际上是linux2.0+以后推出的一个系统调用,web服务器可以通过调整自身的配置来决定是否利用sendfile这个系统调用。先来看一下不用sendfile的传统网络传输过程:
read(file,tmp_buf,len);
write(socket,rmp_buf,len);

1
硬盘 >> kernel buffer >> user buffer >> kernel socket buffer >> 协议栈

1)一般来说一个网络应用是通过读硬盘数据,然后写数据到socket来完成网络传输的。上面的2行代码解释了这一点,不过上面2行简单的代码掩盖了低层的很多操作。来看看低层是怎么执行的:

  1. 系统调用read()产生一个上下文切换,从user mode切换到kernel mode,然后DMA执行拷贝,把文件数据从硬盘读到一个kernel buffer里。
  2. 数据从kernel buffer拷贝到user mode,然后系统调用read()返回,这时又产生一个上下文切换:从kernel mode切换到user mode。
  3. 系统调用write()产生一个上下文切换:从user mode切换到kernel mode,然后把步骤2读到user buffer的数据拷贝到kernel buffer(数据第2次拷贝到kernel buffer),不过这次是个不同的kernel buffer,这个buffer和socket相关联。
  4. 系统调用write()返回,产生一个上下文切换:从kernel mode切换到user mode(第4次切换了),然后DMA从kernel buffer拷贝数据到协议产(第4次拷贝了)。
    上面4个步骤有4次切换,4次拷贝,我们发现如果能减少切换次数和拷贝次数将会有效提升性能。在kernel2.0+版本中,系统调用sendfile()就是用来简化上面步骤提升性能的。
    sendfile()不但能减少切换次数,还能减少拷贝次数。
    2)再来看下sendfile()来进行网络传输的过程:
    sendfile(socket,file,len);
    1
    硬盘 >> kernel buffer(快速拷贝到kernel socket buffer) >> 协议栈
  5. 系统调用sendfile()通过DMA把硬盘数据拷贝到kernel buffer,然后数据被kernel直接拷贝到另外一个socket相关的kernel buffer。这里没有user mode和kernel mode之间的切换,在kernel中直接完成了一个buffer到另一个buffer的拷贝。
  6. DMA把数据从kernel buffer直接拷贝给协议栈,没有切换,也不需要数据从user mode拷贝到kernel mode,因为数据就在kernel中。
    步骤减少了,切换减少了,拷贝减少了,自然性能就提升了。这就是为什么说在Nginx配置文件里打开sendfile on选项能提高web server性能的原因。

综上,这三个参数都应该配置成on:sendfile on;tcp_nopush on;tcp_nodelay on;

二、 nginx长连接-keepalive

当时用nginx作为反向代理时,为了支持长连接,需要做到两点:

  • 从client到nginx的连接是长连接
  • 从nginx到server的连接是长连接

1、保持和client的长连接:

默认情况下,nginx已经自动开启了对client连接的keep alive支持(同时client发送的HTTP请求要求keep alive)。一般场景可以直接使用,但是对于一些比较特殊的场景,还是有必要调整个别参数(keepalive_timeout和keepalive_requests)。

1
2
3
4
http{
keepalive_timeout 120s 120s;
keepalive_requests 10000;
}

1)keepalive_timeout

  1. 第一个参数:设置keep-alive客户端连接在服务器端保持开启的超时值(默认75s);值为0会禁用keep-alive客户端连接;
  2. 第二个参数:可选,在响应的header域中设置一个值“Keep-Alive: timeout=time”;通常可以不用设置;
    注:keepalive_timeout默认75s,一般情况下够用,对于一些请求比较大的内部服务器通讯的场景,适当加大为120s或者300s;

2)keepalive_requests
keepalive_requests指令用于设置一个keep-alive连接上可以服务的请求的最大数量,当最大请求数量达到时,连接被关闭。默认是100。这个参数的真实含义,是指一个keep alive建立之后,nginx就会为这个连接设置一个计数器,记录这个keep alive的长连接上已经接收并处理的客户端请求的数量。如果达到这个参数设置的最大值时,则nginx会强行关闭这个长连接,逼迫客户端不得不重新建立新的长连接。
大多数情况下当QPS(每秒请求数)不是很高时,默认值100凑活够用。但是,对于一些QPS比较高(比如超过10000QPS,甚至达到3k,5k设置更高)的场景,默认的100就显得太低。
简单计算一下,QPS=10000时,客户端每秒发送10000个请求(通常建立有多个长连接),每个连接只能最多跑1000次请求,意味着平均每秒钟重新新建100个连接。因此,就会发现有大量的TIME_WAIT的socket连接(即使此时keep alive已经在client和nginx之间生效)。因为对于QPS较高的场景,非常有必要加大这个参数,以避免大量连接被生成再抛弃的情况,减少TIME_WAIT。

2、保持和server的长连接

为了让nginx和后端server(nginx成为upstream)之间保持长连接,典型设置如下:(默认nginx访问后端都是用的短连接(HTTP1.0),一个请求来了,nginx新开一个端口和后端建立连接,后端执行完毕后主动关闭该链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
http {
upstream BACKEND {
server 192.168.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.0.2:8080 weight=1 max_fails=2 fail_timeout=30s;
keepalive 300; // 这个很重要!
}
server {
listen 8080 default_server;
server_name "";
location / {
proxy_pass http://BACKEND;
proxy_set_header Host $Host;
proxy_set_header x-forwarded-for $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
add_header Cache-Control no-store;
add_header Pragma no-cache;
proxy_http_version 1.1; // 这两个最好也设置
proxy_set_header Connection "";
}
}
}

1)location中有两个参数需要设置:

1
2
3
4
5
6
7
8
http {
server {
location / {
proxy_http_version 1.1; // 这两个最好也设置
proxy_set_header Connection "";
}
}
}

HTTP协议中对长连接的支持是从1.1版本之后才有的,因为最好通过proxy_http_version指令设置为“1.1;
而“Connection” header应该被清理。清理的意思,我的理解,是清理从client过来的http header,因为即使client和nginx之间是短连接,nginx和upstream之间也是可以开启长连接的。这种情况下必须清理来自client请求中的“Connection” header。

2)upstream中的keepalive设置
次数keepalive的含义不是开启、关闭长连接的开关,也不是用来设置超时的timeout;更不是设置长连接池的最大连接数。官方解释:

  1. The connections parameter sets the maximum number of idle keepalive connections to upstream servers connections(设置到upstream服务器的空闲keepalive连接的最大数量
  2. When this number is exceeded, the least recently used connections are closed. (当这个数量被突破时,最近使用最少的连接将被关闭
  3. It should be particularly noted that the keepalive directive does not limit the total number of connections to upstream servers that an nginx worker process can open.(特别提醒:keepalive指令不会限制一个nginx worker进程到upstream服务器连接的总数量
    我们先假设一个场景:有一个HTTP服务,作为upstream服务器接收请求,响应时间为100毫秒。如果要达到10000 QPS的性能,就需要在nginx和upstream服务器之间建立大约1000条HTTP连接。nginx为此建立连接池,然后请求过来时为每个请求分配一个连接,请求结束时回收连接到连接池中,连接的状态也就更改为idle。我们再假设这个upstream服务器的keepalive参数值比较小,比如常见的10:
    A、假设请求和响应是均匀而平稳的,那么这1000条连接应该都是一放回连接池就立即被后续请求申请使用,线程池中的idle线程会非常少,趋近于零,不会造成连接数量反复震荡。
    B、现实中请求和响应不可能平稳,我们以10毫秒为一个单位,来看连接的情况(逐一场景是1000个线程+100毫秒响应时间,每秒有10000个请求完成),我们假设应答始终都是平稳的,只是请求不平稳,第一个10毫秒只有50,第二个10毫秒有150:
  4. 下一个10毫秒,有100个连接结束请求回收连接到连接池,但是假设此时请求不均匀10毫秒内没有预计的100个请求进来,而是只有50个请求。注意此时连接池回收了100个连接又分配出去50个连接,因此连接池内有50个空闲连接。
  5. 然后注意看keepalive=10的设置,这意味着连接池中最多容许保留有10个控线连接。因为nginx不得不将这50个空闲连接中的40个关闭,只保留10个。
  6. 再下一个10毫秒,有150个请求进来,有100个请求结束任务释放连接。150-100=50,孔雀50个连接,减掉前面连接池保留的10个空闲连接,nginx不得不新建40个新连接来满足要求。
    C、同样,如果假设响应不均衡也会出现上面的连接数波动情况。

造成连接数量反复震荡的一个推手,就是keepalive这个最大空闲连接数。毕竟连接池中的1000个连接在频繁利用时,出现短时间内多余10个空闲连接的概率是在太高。因此为了避免出现上面的连接震荡,必须考虑加大这个参数,比如上面的场景如果将keepalive设置为100或者200,就可以非常有效的缓冲请求和应答不均。

总结:
keepalive这个参数一定要小心设置,尤其对于QPS比较高的场景,推荐先做一下估算(容量规划),根据QPS和平均响应时间答题能计算出需要的长连接的数量。比如前面1000QPS和100毫秒响应时间就可以推算出需要的长连接数量大概是1000.然后将keepalive设置为这个长连接数量的10%到30%。比较懒的同学,可以直接设置为keepalive=1000之类的,一般都是OK的。

3、综上,出现大量TIME_WAIT的情况:

1)导致nginx端出现大量TIME_WAIT的情况有两种:

  • keepalive_requests设置比较小,高并发下超过此值后nginx会强制关闭和客户端保持的keepalive长连接;(主动关闭连接后导致nginx出现TIME_WAIT)
  • keepalive设置的比较小(空闲数太小),导致高并发下nginx会频繁的出现连接数震荡(超过该值会关闭连接),不停的关闭、开启和后端server保持的keepalive长连接
    2)导致后端server端出现大量TIME_WAIT的情况:
    nginx没有打开和后端的长连接,即:没有设置proxy_http_version 1.1和proxy_set_header Connection “”;从而导致后端server每次关闭连接,高并发下就会出现server端出现大量TIME_WAIT。
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020 ChpiTer
  • Powered by Hexo Theme Ayer

请我喝杯咖啡吧~

支付宝
微信