nginx性能优化之线程池

Posted 太白的技术博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nginx性能优化之线程池相关的知识,希望对你有一定的参考价值。

1、线程池

  默认情况下,nginx的work process按照顺序一个个处理http请求,因此如果后台处理时间较长,则work process会长时间等待IO状态,因此限制并发性。如下所示:

  所以,对于可能存在的这类http请求,一般会启用线程池。默认情况下,线程池特性并不启用,需要在编译时增加编译选项--with-threads包含该特性,然后在nginx.conf配置文件中定义线程池:

thread_pool MyPool threads=64;
   …
location /downloads/ {
    aio threads=MyPool;

    directio 128k;
    sendfile on;

}

  这样的话,当客户端请求的文件超过128K时,就会适用AIO,一般来说,否则使用sendfile,这个值不适合很小的,要看绝大多数js/css/html/甚至xlsx文件的大小。

  在实际中,对于大文件上传下载比较多的情况,是采用专门的服务器(采用nginx性能比scp/sftp都要好)进行处理的,不会和业务系统混合到一起的。所以,很多时候不一定是技术上一根筋走到头的搞法,有时候效果甚至不如其他简单粗暴的做法。

2、TCP_NODELAY

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

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

 3、TCP_NOPUSH和sendfile

  tcp_nopush 配置和 tcp_nodelay “互斥”。它可以配置一次发送数据的包大小。也就是说,它不是按时间累计 0.2 秒后发送包,而是当包累计到一定大小后就发送。

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

sendfile是 Linux2.0+以后的推出的一个系统调用,web服务器可以通过调整自身的配置来决定是否利用 sendfile这个系统调用。先来看一下不用 sendfile的传统网络传输过程:
read(file,tmp_buf, len);
write(socket,tmp_buf, len);

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

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

  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;

特别需要注意:nginx的sendfile还是会将文件拷贝到页面缓存,aio才不会。

4、SSL加密

对于SSL来说,最消耗资源的部分是SSL握手,所以应使用下列方式最大化性能:

1、使用keepalive一次处理多个请求;

2、重用ssl会话参数;

 

    ssl_session_cache   shared:SSL:10m;   # 1m大概可以支持4000个会话
    ssl_session_timeout 10m;

 

同时,Nginx默认使用DHE算法来产生密匙,该加密算法效率很低。可以通过如下命令,删掉了kEDH算法。

ssl_ciphers ALL:!kEDH!ADH:RC4+RSA:+HIGH:+EXP;

ssl_prefer_server_ciphers on;

5、长连接

当使用nginx作为反向代理(正向代理与反向代理区别)时,为了支持长连接,需要做到两点:

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

客户端:

  keepalive_timeout:客户端长连接超时时间

  keepalive_requests:一次keep-alive连接上可以服务的请求的最大数量

服务端:

  即使是client和nginx之间是短连接,nginx和upstream之间也是可以开启长连接的。

  keepalive 200-1000之间

  proxy_http_version 1.1; // 这两个最好也设置

  proxy_set_header Connection "";

6、基于cookie的负载均衡

  nginx默认不支持基于cookie的连接亲和性,可以适用第三方模块https://github.com/lusis/nginx-sticky-module,实现基于该特性,对于很多不是直接面向WAN的nginx来说,基于ip的机制将使得其效果极差。

7、206错误的避免

  参见经过 NGINX 加载js大文件加载不全,报206 (Partial Content)错误

8、缓存过期

参考:

http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive

http://blog.csdn.net/dream_flying_bj/article/details/54709549

http://blog.csdn.net/dream_flying_bj/article/details/54709542

http://blog.csdn.net/yu870646595/article/details/52056340

以上是关于nginx性能优化之线程池的主要内容,如果未能解决你的问题,请参考以下文章

Android性能优化之使用线程池处理异步任务

Android性能优化之使用线程池处理异步任务

newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段

Nginx 的线程池与性能剖析

Nginx引入线程池,性能提升9倍!

Nginx 的线程池与性能剖析转载