线程池机制使nginx性能提高9倍

1053810529 Nginx536,72326字数 3545阅读11分49秒阅读模式
摘要

一般情况下,nginx 是一个事件处理器,一个从内核获取连接事件并告诉系统如何处理的控制器。
实际上,在操作系统做读写数据调度的时候,nginx是协同系统工作的,所以nginx能越快响应越好。

nginx task pool

一、问题

一般情况下,nginx 是一个事件处理器,一个从内核获取连接事件并告诉系统如何处理的控制器。实际上,在操作系统做读写数据调度的时候,nginx是协同系统工作的,所以nginx能越快响应越好。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

 文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

nginx处理的事件可以是 超时通知、socket可读写的通知 或 错误通知。nginx 接收到这些消息后,会逐一进行处理。但是所有处理过程都是在一个简单的线程循环中完成的。nginx 从消息队列中取出一条event后执行,例如 读写socket的event。在大多数情况下这很快,Nginx瞬间就处理完了。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

 文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

如果有耗时长的操作发生怎么办?整个消息处理的循环都必须等待这个耗时长的操作完成,才能继续处理其他消息。所以,我们说的“阻塞操作”其实意思是长时间占用消息循环的操作。操作系统可能被各种各样的原因阻塞,或者等待资源的访问,例如硬盘、互斥锁、数据库同步操作等。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

 文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

例如,当nginx 想要读取没有缓存在内存中的文件时,则要从磁盘读取。但磁盘是比较缓慢的,即使是其他后续的事件不需要访问磁盘,他们也得等待本次事件的访问磁盘结束。结果就是延迟增加和系统资源没有被充分利用。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

 文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

有些操作系统提供了异步读写文件接口,在nginx中可以使用这些接口(http://nginx.org/en/docs/http/ngx_http_core_module.html?&&&_ga=1.197764335.1343221768.1436170723#aio)。例如FreeBSD就是一个较好的例子,但不幸的是,linux提供的一系列异步读文件接口有不少缺陷。其中一个问题是:文件访问和缓冲需要队列,但是Nginx已经很好解决了。但是还有一个更严重的问题:使用异步接口需要对文件描述符设置O_DIRECT标识,这意味着任何对这个文件的访问会跳过缓存直接访问磁盘上的文件。在大多数情况下,这不是访问文件的最佳方法。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

二、线程池

为了解决这个问题,Nginx 1.7.11 引入了线程池概念。现在让我们了解一下线程池是怎样工作的。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

 文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

在nginx中,线程池执行的是分发服务,他由一个任务队列和一些执行任务的线程组成。当一个工作线程在执行一个可能会存在潜在长时间操作的任务时,这个任务会被”卸下“并重新放到任务队列中去,这个被”卸下“的任务可能会被其他线程再执行。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

 文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

现在,只有2个基础操作会造成“卸下任务”到任务队列:文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

  • 在大多操作系统上的read()系统调用
  • linux系统的sendfile()
    如果这个机制被证实是有益于nginx的,我们以后还会添加其他的操作。

三、线程池并非灵丹妙药

大多数读写文件操作都需要通过缓慢的磁盘。如果有充足的内存来存储数据,那么操作系统会缓存频繁使用的文件,也就是“页面缓存”(page cache)机制。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

 文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

由于页面缓存机制,nginx几乎在所有情况下都能体现非常好的性能。通过页面缓存读取数据非常快,并且不会阻塞。另一方面,卸下任务到任务池是有瓶颈的。所以在内存充足并且使用的数据不是非常大的时候,nginx即使不使用线程池也是几乎工作在最佳状态。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

 文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

卸下写操作到任务池中,是一个适用于特殊场景的处理方案,适用于大量无法使用VM缓存的请求操作。
例如一个高负荷的基于Nginx的视频流服务器。另外FreeBSD的用户不需要担心这些,因为FreeBSD已经有很好的异步读操作接口,无需使用线程池。文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

四、配置线程池

如果你确定在你的场景中适合使用线程池,那么一起看看如何配置线程池。准备工作步骤如下:文章源自运维生存时间-https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/

  • 使用 nginx 1.7.11 或更新的版本
  • 使用--with-threads参数编译nginx

最简单的例子,添加一个aio线程标识(可以添加到http、server 或 location段中):

aio threads;

这是一个最简单的配置例子,等于以下的配置:

# in the 'main' context
 thread_pool default threads=32 max_queue=65536;
# in the 'http', 'server', or 'location' context
 aio threads=default;

以上配置定义了一个叫 default 的线程池,有32个工作线程,任务队列最大存放65536个任务。如果任务队列满了,nginx会抛弃任务并打印以下日志:

thread pool "NAME" queue overflow: N tasks waiting

当出现了这个日志,这意为着你可以调大你的任务队列,或者你的系统无法处理这么多任务。所以综上所述,你可以配置你的 线程数,任务队列长度,线程池名称。你也可以设置多个线程池,用在不同的地方:

# in the 'main' context
 thread_pool one threads=128 max_queue=0;
 thread_pool two threads=32;
http {
 server {
 location /one {
 aio threads=one;
 }
location /two {
 aio threads=two;
 }
}
 …
 }

如果max_queue,也就是任务队列长度未指定,那么长度默认为65536.max_queue也可以设置为0,这样线程池只能处理和线程数一样多的任务,不会有任务存储在任务队列中。

 

现在假设你有一台有3个硬盘的服务器,你希望这台服务器作为缓存代理使用,这是你CDN的一个缓存节点,缓存的数据已经超过了可用内存。在这个场景中最重要的事情就是提高磁盘读写的性能。一个方案就是使用RAID,另一个方案就是使用Nginx:

# We assume that each of the hard drives is mounted on one of these directories:
 # /mnt/disk1, /mnt/disk2, or /mnt/disk3
# in the 'main' context
 thread_pool pool_1 threads=16;
 thread_pool pool_2 threads=16;
 thread_pool pool_3 threads=16;
http {
 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;
split_clients $request_uri $disk {
 33.3% 1;
 33.3% 2;
 * 3;
 }
server {
 …
 location / {
 proxy_pass http://backend;
 proxy_cache_key $request_uri;
 proxy_cache cache_$disk;
 aio threads=pool_$disk;
 sendfile on;
 }
 }
 }

在配置中,thread_pool 指令给每个磁盘定义了独立的线程池;proxy_cache_path指令给每个磁盘定义独立的缓存路径、参数;split_clients 模块用于多个缓存(也就是多个磁盘)的负载均衡,这个解决方案很符合该使用场景;proxy_cache_path中的use_temp_path=off参数让nginx存储临时文件到缓存目录,这可以避免更新缓存时的磁盘间数据拷贝。

 

以上的例子说明可以根据自身硬件灵活调整nginx,通过细微调整,可以让你的软件、操作系统、硬件协同工作在最佳状态,尽可能的利用所有资源。

结论

线程池机制是一个非常好的机制,通过解决大量数据情况下导致的阻塞问题,使得nginx的性能达到一个新的高度。如之前提到的,接下来会有新的接口可能会实现在不损耗性能的情况下实现”卸下“任务机制。注:原文中的一些翻译
(1)offloading 翻译为 卸下,其实就是把一个任务塞回到任务池中;
(2)原文中有提到把任务offloading到thread pool中,但其实是task是存放在task pool中,所以我译为”把任务卸下到任务池中“;
(3)性能测试阶段译文略过,可以参考原帖

投稿信息

原文标题:Thread Pools in NGINX Boost Performance 9x!
原文官方地址:https://www.nginx.com/blog/thread-pools-boost-performance-9x/

本文为译文,非直译。感谢"solohac"的投稿

weinxin
我的微信
微信公众号
扫一扫关注运维生存时间公众号,获取最新技术文章~
1053810529
  • 本文由 投稿,于08/07/2015 22:08:50发表
  • 转载请务必保留本文链接:https://www.ttlsa.com/nginx/thread-pools-boost-performance-9x/
  • nginx task pool
评论  5  访客  5
    • test啊
      test啊 9

      test啊

      • 默北
        默北 6

        nginx的线程池主要是为了规避磁盘IO阻塞。 –with-threads选项可能对某些第三方模块不兼容error: unknown type name ‘ngx_thread_task_t’,如ngx_echo模块,需要打上补丁。

        • 锟斤拷
          锟斤拷 9

          不错的文章

          • 默北
            默北 6

            赞一个

              • 匿名
                匿名 9

                @ 默北 去问问

            评论已关闭!