18910140161

nginx优化之https、keepalive等

顺晟科技

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;

我们已经准备好了,你呢?
2024我们与您携手共赢,为您的企业形象保驾护航