顺晟科技
2021-06-16 10:42:36
566
I. tcp_nopush,tcp_nodelay,nginx的sendfile
1、TCP_NODELAY
如何强制socket在其缓冲区发送数据?
一种解决方案是TCP堆栈的TCP_NODELAY选项。这使得缓冲区中的数据可以立即发送出去。
Nginx的TCP _ nodely选项在打开新的套接字时添加了TCP _ nodely选项。但这时会造成一种情况:
终端应用程序每次生成操作时都会发送一个数据包,但通常一个数据包会有一个字节的数据和一个40字节的报头,导致4000%的过载,这很容易导致网络拥塞。为了避免这种情况,TCP栈实现了等待数据0.2秒,所以操作后不会发送数据包,而是将这段时间的数据打包成一个大数据包。这种机制由Nagle算法保证。
Nagle成了标准,马上就在网上实现了。现在这是默认配置,但在某些情况下更好关闭此选项。现在假设一个应用程序请求发送一小段数据。我们可以选择立即发送数据,或者等待更多数据生成,然后再次发送。
如果我们立即发送数据,交互式和客户机/服务器应用程序将受益匪浅。如果立即发送请求,响应时间会更快。上述操作可以通过设置套接字的TCP_NODELAY=on选项来完成,从而禁用Nagle算法。(不需要等0.2s)
2、TCP_NOPUSH
在nginx中,tcp_nopush配置和tcp_nodelay是互斥的。它可以配置一次发送数据的数据包大小。也就是说,它不按时间累加0.2秒后发送数据包,而是累加到一定大小后发送数据包。
注意:tcp_nopush必须与nginx中的sendfile一起使用。
3、发送文件
如今,流行的web服务器提供sendfile选项来提高服务器性能。什么是sendfile,它如何影响性能?
Sendfile其实是Linux2.0之后引入的系统调用,web服务器可以通过调整自己的配置来决定是否使用sendfile。我们先来看看传统的没有sendfile的网络传输过程:
read(file,tmp_buf,len);
write(socket,tmp_buf,len);
硬盘内核缓冲用户缓冲内核套接字缓冲协议栈
1)一般来说,网络应用程序通过读取硬盘数据,然后将数据写入套接字来完成网络传输。上面两行用代码解释了这一点,但是上面两行的简单代码掩盖了底层的很多操作。让我们看看底层是如何执行上面两行代码的:
系统调用read()进行上下文切换:从用户模式切换到内核模式,然后DMA执行复制,将硬盘上的文件数据读入内核缓冲区。
数据从内核缓冲区复制到用户缓冲区,然后系统调用read()返回,此时还有另一个上下文切换:从内核模式到用户模式。
系统调用write()产生一个上下文切换:从用户模式切换到内核模式,然后将步骤2中从用户缓冲区读取的数据复制到内核缓冲区(数据第二次复制到内核缓冲区),但这次是不同的内核缓冲区,与socket相关联。
系统调用write()返回,产生一个上下文切换:从内核模式到用户模式(第四次切换),然后DMA将数据从内核缓冲区复制到协议栈(第四次复制)。
在上述四个步骤中,有四个上下文切换和四个副本。我们发现,如果我们能够减少交换机和副本的数量,性能将得到有效的提高。在kernel2.0版中,使用系统调用sendfile()来简化上述步骤,提高性能。Sendfile()不仅可以减少切换次数,还可以减少复制次数。
2)我们来看看用sendfile()进行网络传输的过程:
sendfile(socket、file、len);
硬盘内核缓冲区(快速复制到内核时钟缓冲区)协议栈
系统调用sendfile()通过DMA将硬盘数据复制到内核缓冲区,然后由内核直接将数据复制到另一个与socket相关的内核缓冲区。用户模式和内核模式之间没有切换,从一个缓冲区到另一个缓冲区的复制直接在内核中完成。
DMA直接将数据从kernelbuffer复制到协议栈,不需要切换,也不需要将数据从用户模式复制到内核模式,因为数据在内核中。
通过更少的步骤、更少的切换和更少的拷贝,自然性能得到改善。这就是为什么在Nginx配置文件中打开sendfile on选项可以提高web服务器的性能。
综上所述,这三个参数应该配置为on:send file on;tcp _ nopush ontcp_nodelay打开;
二、nginx长连接——keepalive
当使用nginx作为反向代理时,为了支持长连接,需要做两件事:
从客户端到nginx的连接是一个长连接
从nginx到服务器的连接是长连接
1.与客户保持长期联系:
默认情况下,nginx自动启用了对客户端连接的保持活动支持(同时,客户端发送的HTTP请求需要保持活动)。一般场景可以直接使用,但对于一些特殊场景,需要调整个别参数(keepalive_timeout和keepalive_requests)。
http {
keepalive _ timeout 120s 120s
keepalive _ requests 10000
}
1)keepalive_timeout
语法:
keepalive _ time out time out[header _ time out];
个参数:设置服务器端保持保活客户端连接打开的超时值(默认75s);值0禁用保持活动客户端连接;
第二个参数:可选,在响应的头字段设置一个值“keep-alive : time out=time”;通常不用设置;
注意:keepalive_timeout默认为75s,一般就够了。对于一些需要大型内部服务器通信的场景,应该适当增加到120s或300s;
2)keepalive_requests:
keepalive_requests指令用于设置在保持活动连接上可以服务的更大请求数。当达到更大请求数时,连接将关闭。默认值为100。此参数的真正含义是,在建立保持活动后,nginx将为此连接设置一个计数器,以记录在此保持活动长连接上接收和处理的客户端请求的数量。如果达到此参数的更大值,nginx将强制关闭此长连接,强制客户端重新建立新的长连接。
在大多数情况下,当QPS(每秒请求数)不是很高时,默认值100就足够了。但是,对于某些QPS值较高的场景(如超过10000QPS,甚至达到30000、50000甚至更高),默认值100太低。
简单计算一下,当QPS=10000时,客户端每秒发送10000个请求(通常有多个长连接),每个连接最多只能运行100个请求,也就是说平均每秒会有100个长连接,所以会被nginx关闭。这也意味着,为了维护QPS,客户端必须每秒重新建立100个连接。所以你会发现TIME_WAIT的套接字连接很多(即使此时客户端和nginx之间已经在生效keep alive)。因此,对于高QPS的场景,有必要增加该参数,以避免产生大量连接然后丢弃的情况,并减少TIME_WAIT。
2.与服务器保持长时间连接:
为了保持nginx与后端服务器(nginx称为上游)之间的长连接,典型的设置如下:(默认情况下,Nginx以短连接方式访问后端(HTTP1.0)。当请求到来时,nginx打开一个新端口与后端建立连接,后端在执行后主动关闭链接)
http {
上游后端{
服务器192 . 168 . 0 . 1:8080 weight=1 max _ failed=2 fail _ time out=30s;
服务器192 . 168 . 0 . 2:8080 weight=1 max _ failed=2 fail _ time out=30s;
keepalive 300//这个很重要!
}
服务器{
listen 8080 default _ server
server _ name“”;
位置/
proxy _ pass后端;
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无存储;
add_header Pragma无缓存;
proxy _ http _ version 1.1//更好也设置这两个
代理集头连接“”;
}
}
}
1)在1)位置有两个参数需要设置:
http {
服务器{
位置/
proxy _ http _ version 1.1//更好也设置这两个
代理集头连接“”;
}
}
}
HTTP协议中对长连接的支持只有1.1版以后才有,更好通过proxy_http_version指令设置为“1.1”。
“连接”标题应该清理干净。清理,我的理解是从客户端清理http头,因为即使客户端和nginx之间有短连接,nginx和上游之间也可以打开长连接。在这种情况下,必须清除来自客户端的请求中的“连接”头。
2)上游2)中的保持活动设置:
这里keepalive的意思不是打开和关闭长连接的开关;不用于设置超时;不设置长连接池中的更大连接数。官方解释:
connections参数设置到上游服务器连接的空闲保持活动连接的更大数量
当超过该数量时,最近最少使用的连接将被关闭。
需要特别注意的是,keepalive指令并没有限制nginx工作进程可以打开的上游服务器的连接总数。(特别提醒:keepalive指令不会限制从一个nginx工作进程到上游服务器的连接总数)
让我们假设一个场景:有一个作为上游服务器接收请求的HTTP服务,响应时间为100毫秒。为了达到10000 QPS的性能,需要在nginx和上游服务器之间建立大约1000个HTTP连接。Nginx为此目的建立一个连接池,然后在请求到来时为每个请求分配一个连接。当请求结束时,连接被回收并放入连接池,连接的状态变为空闲。我们假设这个上游服务器的keepalive参数设置比较小,比如常见的10。
a、假设请求和响应是统一稳定的,那么这1000个连接一放回连接池就应该全部申请供后续请求使用,线程池中空闲线程会非常少,会趋于零,不会造成连接数反复波动。
b、显示中的请求和响应不能稳定。我们以10ms为单位来看连接情况(注意场景是1000个线程在100ms内响应,每秒完成10000个请求)。我们假设响应总是稳定的,但请求是不稳定的,前10毫秒只有50个,后10毫秒有150个:
在接下来的10毫秒内,将有100个连接结束请求来回收到连接池的连接,但是假设在10毫秒内只有50个请求,而不是预期的100个请求。请注意,此时连接池回收100个连接并分配50个连接,因此连接池中有50个空闲连接。
然后注意keepalive=10的设置,这意味着连接池中最多允许10个空闲连接。因此,nginx不得不关闭50个空闲连接中的40个,只剩下10个。
在接下来的10毫秒内,150个请求进入,100个请求结束任务并释放连接。150-100=50,50个连接是空闲的,减去先前连接池保留的10个空闲连接,因此nginx必须建立40个新连接来满足要求。
c、同样,如果假设相应的不平衡,也会出现上述连接数波动。
导致连接数量反复波动的驱动力之一是这个保持活动状态,即空闲连接的更大数量。毕竟,当连接池中的1000个连接被频繁使用时,短时间内超过10个空闲连接的概率太高了。因此,为了避免上述连接振荡,有必要增加该参数。例如,如果在上述场景中将keepalive设置为100或200,则可以有效地缓冲不均匀的请求和响应。
总结:
参数keepalive必须谨慎设置,特别是对于QPS高的场景,建议先做一个估计,需要的长连接数可以根据QPS和平均响应时间粗略计算。例如,从之前的10000 QPS和100毫秒响应时间,可以估计出所需的长连接数量约为1000,然后将keepalive设置为长连接数量的10%到30%。懒学生可以直接设置keepalive=1000,一般还可以。
3.总而言之,有很多TIME_WAIT案例
1)有两种情况会导致nginx出现大量TIME_WAIT:
keepalive_requests设置比较小,nginx在高并发下超过这个值后会强行关闭与客户端的keepalive长连接;(主动关闭连接后,时间等待出现在nginx中)
keepalive设置比较小(空闲数太小),导致nginx在高并发情况下频繁的连接震荡(超过这个值就会关闭连接),与后端服务器的keepalive长连接会关闭并持续打开;
2)后端服务器端出现大量TIME_WAIT:
Nginx不开放与后端的长连接,即未设置proxy _ http _ version 1.1和proxy _ set _ header Connection因此,每次后端服务器关闭连接时,在高并发下,服务器端都会出现大量的TIME_WAIT
第三,nginx配置https
1.配置
服务器{
listen 80 default _ server
侦听443 ssl
server _ name toutiao.iqiyi.com toutiao . qiyi . domain m.toutiao.iqiyi.com;
root/data/none;
index.phpindex.htmlindex.htm指数;
###ssl设置开始
SSL _ protocol TLSV 1 TLSV 1.1 TLSV 1.2;
SSL _ certificate/usr/local/nginx/conf/server . PEM;
SSL _ certificate _ key/usr/local/nginx/conf/server . key;
SSL _ session _ cache shared : SSL :10m;
ssl _ session _ timeout 10m
ssl_ciphers ALL:kEDH!ADH : RC 4 RSA : high : EXP;
ssl _ prefer _ server _ ciphers on
###ssl设置结束
…
2.性能比较:
一般通过https访问Nginx会比访问http慢30%(通过https访问主要消耗Nginx服务器的cpu),以下实验验证了这一点:
nginx后端挂着五个java服务器,java服务器中有一个简单的java程序,从redis缓存中随机读取一个值,输出到前端;(java服务器挂得越多,nginx的压力就越大。)
压力测试nginx,3000并发,共30000个请求,返回的结果都是200个案例进行对比;
实验结果:
A.服务器负载比较:
对于https访问,服务器cpu最多可以达到20%,而对于http访问,服务器cpu基本在1%左右;无论何种访问,nginx服务器的负载和内存都不高;
B.nginx吞吐量比较(qps):
HTTPS访问,30,000次请求用了28秒;(是http的3倍)
HTTP访问,3万个请求用了9s;
统计qp时,每次清nginx日志,然后加压。执行后,使用以下命令查看qp:
一个
2
# cat log . 2.3000 https | grep '/API/news/v1/info?news id=' | awk ' { print $ 3 } ' | uniq | WC-l
37
注意:不能一直按下去,否则无限按下去后后端java服务往往会出现瓶颈,会减缓返回nginx的响应速度,从而降低nginx的压力。
3.优化:
Nginx默认使用DHE算法生成密钥,效率不高。您可以通过以下命令删除kEDH算法。
ssl_ciphers ALL:kEDH!ADH : RC 4 RSA : high : EXP;
25
2022-05
17
2022-03
16
2021-06
16
2021-06
16
2021-06
16
2021-06