首页 运维 网络学院 查看内容

Nginx 引入线程池,提升 9 倍性能(二)

2015-7-7 11:26 |来自: 伯乐在线 1849 0

摘要: 页面缓存表现优异,使得 NGINX 几乎在通常的用例中性能表现突出。从页面缓存中读取速度非常快,没有人认为类操作是“阻塞”的。换言之,分派负载给线程池会带来一些开销。所以,如果有合适的内存,并且数据集不大, ...
关键词: 线程 缓存 threads NGINX 队列 pool cache 配置 硬盘 任务

页面缓存表现优异,使得 NGINX 几乎在通常的用例中性能表现突出。从页面缓存中读取速度非常快,没有人认为类操作是“阻塞”的。换言之,分派负载给线程池会带来一些开销。

所以,如果有合适的内存,并且数据集不大,那么无需线程池,NGINX 就可以在最佳性能下工作。

分派读操作给线程池是一种对针对特定任务的技术。频次非常高的请求内容不适合放入操作系统虚拟缓存中,这时候线程池就很有了。或许就是如此,例如,重量级基于NGINX负载流媒体服务器。我们的基准测试已模仿这个场景。

如果能将读操作分派给线程池是极好的,我们所要做的是需要的文件数据是否在内存中,如果不在内存中,那么我们就应该将读操作分派给某个线程。

回到销售的例子,当下销售员面临的情况是,不知道请求物品是否在店铺,要么将所有的订单传给提取货物服务,要么他自己处理这些订单。

要命的是,操作系统可能永远没有这个功能。第一次尝试是 2010 年 linux 中引入 fincore() 系统调用方法,没有成功。接着做了一系列尝试,例如引入新的带有 RWF_NONBLOCK 标记的 preadv2() 系统调用方法。所有的这些补丁前景依旧不明朗。比较悲剧的是,因为 持续的口水战 ,导致这些补丁一直没有被内核接受。

另一个原因是,FreeBSD用户根本不会关心这个。因为 FreeBSD 已经有一个非常高效的异步文件读取接口,完全可以不用线程池。

配置线程池

如果确信你的用例采用线程池可以获利,那么是时候深入其配置了。

线程池配置非常容易而且灵活。首先你需要 NGINX 1.7.11 版,或者更新的版本,采用配置文件中的参数 –with-threads 进行编译。最简单的例子,配置看起来相当的容易,所有你需要做的的事情就是给http、server或者location上下文中添加 aio threads 指令。

aio threads;

这可能是最简短的线程池配置了,实际上,下面这个配置是一个简化版的:

thread_pool default threads=32 max_queue=65536;
aio threads=default;

它定义一个名为 default  的线程池,拥有 32 个工作线程,任务队列容纳的最大请求数为 65536。一旦任务队列过载,NGINX日志会报错并拒绝这一请求:

thread pool "NAME" queue overflow: N tasks waiting

报错意味着线程可能处理工作的速度跟不上任务添加进队列的速度,你可以试着增加队列的到最大容量。如果还是不起作用,可能是系统服务请求的数量已达到了上线。

正如你所看到的,可以用thread_pool指令设置线程数量、队列最大容量、为某个线程池命名。为某个线程池命名意味着你可以设置多个独立的线程池,在不同的配置文件用于不同目的。

http {
  thread_pool one threads=128 max_queue=0;
  thread_pool two threads=32;
  server {
    location /one {
      aio threads=one;
    }
    location /two {
      aio threads=two;
    }
  }
…
}

如果没有指定max_queue参数,它的默认值为65536。如上面所展示的,可以将max_queue设置为0。这意味这,如在本例,线程池只能处理分派给线程那些任务;因为队列中没有存储任何等待的任务。

试想你的服务器有三个硬盘,你希望服务器能像缓存代理一样作用,缓存所有来自后端的响应,预期缓存的数据量远远超过了现有的内存。这个缓存节点为私人内容分发网络(CDN)服务,当然本例中最重要的事情就是从硬盘那里获取最大性能。

一种选择是设置一个磁盘阵列,这种方式有其优点和缺点。NGINX采用另外一种方式:

# 假定每个硬盘驱动挂载一个文件目录上
# We assume that each of the hard drives is mounted on one of the directories:
# /mnt/disk1, /mnt/disk2, or /mnt/disk3 accordingly
proxy_cache_path /mnt/disk1 levels=1:2 keys_zone=cache_1:256m max_size=1024G 
         use_temp_path=off;
proxy_cache_path /mnt/disk2 levels=1:2 keys_zone=cache_2:256m max_size=1024G 
         use_temp_path=off;
proxy_cache_path /mnt/disk3 levels=1:2 keys_zone=cache_3:256m max_size=1024G 
         use_temp_path=off;
thread_pool pool_1 threads=16;
thread_pool pool_2 threads=16;
thread_pool pool_3 threads=16;
split_clients $request_uri $disk {
  33.3%	 1;
  33.3%	 2;
  *		 3;
}
location / {
  proxy_pass http://backend;
  proxy_cache_key $request_uri;
  proxy_cache cache_$disk;
  aio threads=pool_$disk;
  sendfile on;
}

在这个设置中,用到了三个独立的缓存,对应一个硬盘。同样也有三个独立的线程池对应某个硬盘。

split_clients 模块用于缓存之间的负载平衡,很好地满足这个任务。

proxy_cache_path 指令中的参数 use_temp_path=off 指示 NGINX 存储临时文件到缓存数据对应的相同目录中;在缓存更新时,避免磁盘之间拷贝响应数据。

做的所有这一切,都是为了使当前硬盘子系统性能达到最大,因为NGINX中每个线程池与磁盘的交互都是独立并行的。每个磁盘由 16 个独立的线程为其服务,即处理某个特定任务队列中文件的读取和发送。

我猜测你的客户端采用类似客户定制的方式,那么同样确保你的硬盘驱动也采用类似的方式。

本例很好的展示了NGINX如何灵活地针对特定硬盘做出调优,就像你给出指令,告诉NGINX与计算机以及数据集的最佳交互方式。通过细粒度的NGINX调优,可以确保软件、操作系统、硬件处在一种最佳的工作状态,即尽可能有效地利用系统资源。

结论

总之,线程池是一个非常棒的特性,它能促使NGINX性能上一个新台阶,移除了众所周知的顽疾——阻塞,特别是涉及海量数据的时候。

当然远非这些,正如前面所提到的,新的接口可能会允许我们分派任何长的阻塞操作,而且不会有性能损失。NGINX开辟了新天地,拥有一批新的模块和功能。而许多流行的库依旧没有提供某种异步非阻塞接口,这样很难和NGINX兼容。或许我们需要花费很多时间和资源,开发一些我们自己的非阻塞原生库,但这样做值得么?随着线程池特性的上线,这些库在不影响模块性能的前提下会更加相对简单易用。

声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部