nginx优化——包括https、keepalive等

1、nginx之tcp_nopush、tcp_nodelay、sendfile

一、TCP_NODELAY
你怎么能够强制 socket 在它的缓冲区里发送数据?
一个解决方案是 TCP 堆栈的 TCP_NODELAY选项。这样就可使缓冲区中的数据当即发送出去。php

Nginx的 TCP_NODELAY 选项使得在打开一个新的 socket 时增长了TCP_NODELAY选项。但这时会形成一种状况:
终端应用程序每产生一次操做就会发送一个包,而典型状况下一个包会拥有一个字节的数据以及40个字节长的包头,因而产生4000%的过载,很轻易地就能令网络发生拥塞。为了不这种状况,TCP堆栈实现了等待数据 0.2秒钟,所以操做后它不会发送一个数据包,而是将这段时间内的数据打成一个大的包。这一机制是由Nagle算法保证。html

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

二、TCP_NOPUSH
在 nginx 中,tcp_nopush 配置和 tcp_nodelay “互斥”。它能够配置一次发送数据的包大小。也就是说,它不是按时间累计 0.2 秒后发送包,而是当包累计到必定大小后就发送。java

注:在 nginx 中,tcp_nopush 必须和 sendfile 搭配使用。node

三、sendfile
如今流行的web 服务器里面都提供 sendfile选项用来提升服务器性能,那到底 sendfile是什么,怎么影响性能的呢?
sendfile其实是 Linux2.0+之后的推出的一个系统调用,web服务器能够经过调整自身的配置来决定是否利用 sendfile这个系统调用。先来看一下不用 sendfile的传统网络传输过程:
read(file,tmp_buf, len);
write(socket,tmp_buf, len);nginx

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

1)通常来讲一个网络应用是经过读硬盘数据,而后写数据到socket 来完成网络传输的。上面2行用代码解释了这一点,不过上面2行简单的代码掩盖了底层的不少操做。来看看底层是怎么执行上面2行代码的:redis

  1. 系统调用 read()产生一个上下文切换:从 user mode 切换到 kernel mode,而后 DMA 执行拷贝,把文件数据从硬盘读到一个 kernel buffer 里。
  2. 数据从 kernel buffer拷贝到 user buffer,而后系统调用 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);后端

硬盘 >> kernel buffer (快速拷贝到kernelsocket buffer) >>协议栈

  1. 系统调用sendfile()经过 DMA把硬盘数据拷贝到 kernel buffer,而后数据被 kernel直接拷贝到另一个与 socket相关的 kernel buffer。这里没有 user mode和 kernel mode之间的切换,在 kernel中直接完成了从一个 buffer到另外一个 buffer的拷贝。
  2. DMA 把数据从 kernelbuffer 直接拷贝给协议栈,没有切换,也不须要数据从 user mode 拷贝到 kernel mode,由于数据就在 kernel 里。

步骤减小了,切换减小了,拷贝减小了,天然性能就提高了。这就是为何说在Nginx 配置文件里打开 sendfile on 选项能提升 web server性能的缘由。

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

2、nginx长链接——keepalive

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

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

一、保持和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
语法:

keepalive_timeout timeout [header_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,甚至达到30000,50000甚至更高) 的场景,默认的100就显得过低。
简单计算一下,QPS=10000时,客户端每秒发送10000个请求(一般创建有多个长链接),每一个链接只能最多跑100次请求,意味着平均每秒钟就会有100个长链接所以被nginx关闭。一样意味着为了保持QPS,客户端不得不每秒中从新新建100个链接。所以,就会发现有大量的TIME_WAIT的socket链接(即便此时keep alive已经在client和nginx之间生效)。所以对于QPS较高的场景,很是有必要加大这个参数,以免出现大量链接被生成再抛弃的状况,减小TIME_WAIT。

二、保持和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:

  1. 下一个10毫秒,有100个链接结束请求回收链接到链接池,可是假设此时请求不均匀10毫秒内没有预计的100个请求进来,而是只有50个请求。注意此时链接池回收了100个链接又分配出去50个链接,所以链接池内有50个空闲链接。
  2. 而后注意看keepalive=10的设置,这意味着链接池中最多允许保留有10个空闲链接。所以nginx不得不将这50个空闲链接中的40个关闭,只留下10个。
  3. 再下一个10个毫秒,有150个请求进来,有100个请求结束任务释放链接。150 - 100 = 50,空缺了50个链接,减掉前面链接池保留的10个空闲链接,nginx不得不新建40个新链接来知足要求。

C、一样,若是假设相应不均衡也会出现上面的链接数波动状况。

形成链接数量反复震荡的一个推手,就是这个keepalive 这个最大空闲链接数。毕竟链接池中的1000个链接在频繁利用时,出现短期内多余10个空闲链接的几率实在过高。所以为了不出现上面的链接震荡,必须考虑加大这个参数,好比上面的场景若是将keepalive设置为100或者200,就能够很是有效的缓冲请求和应答不均匀。

总结:
keepalive 这个参数必定要当心设置,尤为对于QPS比较高的场景,推荐先作一下估算,根据QPS和平均响应时间大致能计算出须要的长链接的数量。好比前面10000 QPS和100毫秒响应时间就能够推算出须要的长链接数量大概是1000. 而后将keepalive设置为这个长链接数量的10%到30%。比较懒的同窗,能够直接设置为keepalive=1000之类的,通常都OK的了。

三、综上,出现大量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

3、nginx配置https

一、配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
    listen                  80  default_server;
    listen          443 ssl;
    server_name     toutiao.iqiyi.com  toutiao.qiyi.domain m.toutiao.iqiyi.com;
    root            /data/none;
    index           index.php index.html index.htm;


    ###ssl settings start
    ssl_protocols                   TLSv1 TLSv1.1 TLSv1.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:RC4+RSA:+HIGH:+EXP;
    ssl_prefer_server_ciphers       on;
    ###ssl settings end
…

 

二、性能比较:
经过https访问Nginx通常会比http访问慢30%(https方式访问主要是耗Nginx服务器的cpu)经过下面实验验证:

  1. nginx后端挂了5台java服务器,java服务器中有个简单的java程序,从redis缓存随机读取一个value值输出到前端;(挂的java服务器越多,对nginx压力越大)
  2. 压测nginx,3000并发,一共请求30000次,返回结果都是200的状况下进行对比;
    实验结果:
    A、服务器负载对比:
    https访问,服务器cpu最高能够达到20%,而http的访问,服务器cpu基本在1%左右;不管那种访问,nginx服务器负载、内存都不高;
    B、nginx吞吐量对比(qps):
    • https访问,30000次请求花了28s;(是http的3倍)
    • http访问,30000次请求花了9s;

统计qps时,每次清空nginx日志,而后加压,执行完毕后使用以下命令查看qps:

1
2
# cat log.2.3000https | grep '/api/news/v1/info?newsId=' | awk '{print$3}'| uniq | wc -l
37


注:不能持续加压,不然无限加大压力后每每是后端java服务出现瓶颈,致使返回给nginx的响应变慢,从而使得nginx压力变小。

 

三、优化: Nginx默认使用DHE算法来产生密匙,该加密算法效率很低。能够经过以下命令,删掉了kEDH算法。 ssl_ciphers ALL:!kEDH!ADH:RC4+RSA:+HIGH:+EXP;

相关文章
相关标签/搜索