線程池機制使nginx性能提升9倍

原文標題:Thread Pools in NGINX Boost Performance 9x!html

原文官方地址:https://www.nginx.com/blog/thread-pools-boost-performance-9x/linux

 

本文爲譯文,非直譯。nginx


1、問題
通常狀況下,nginx 是一個事件處理器,一個從內核獲取鏈接事件並告訴系統如何處理的控制器。
實際上,在操做系統作讀寫數據調度的時候,nginx是協同系統工做的,因此nginx能越快響應越好。

nginx處理的事件能夠是 超時通知、socket可讀寫的通知 或 錯誤通知。nginx 接收到這些消息後,會逐一進行處理。
可是全部處理過程都是在一個簡單的線程循環中完成的。
nginx 從消息隊列中取出一條event後執行,例如 讀寫socket的event。在大多數狀況下這很快,Nginx瞬間就處理完了。

若是有耗時長的操做發生怎麼辦?整個消息處理的循環都必須等待這個耗時長的操做完成,才能繼續處理其餘消息。

因此,咱們說的「阻塞操做」其實意思是長時間佔用消息循環的操做。
操做系統可能被各類各樣的緣由阻塞,或者等待資源的訪問,例如硬盤、互斥鎖、數據庫同步操做等。

例如,當nginx 想要讀取沒有緩存在內存中的文件時,則要從磁盤讀取。
但磁盤是比較緩慢的,即便是其餘後續的事件不須要訪問磁盤,他們也得等待本次事件的訪問磁盤結束。
結果就是延遲增長和系統資源沒有被充分利用。

有些操做系統提供了異步讀寫文件接口,在nginx中可使用這些接口(http://nginx.org/en/docs/http/ngx_http_core_module.html?&&&_ga=1.197764335.1343221768.1436170723#aio)。
例如FreeBSD就是一個較好的例子,但不幸的是,linux提供的一系列異步讀文件接口有很多缺陷。
其中一個問題是:文件訪問和緩衝須要隊列,可是Nginx已經很好解決了。
可是還有一個更嚴重的問題:使用異步接口須要對文件描述符設置O_DIRECT標識,這意味着任何對這個文件的訪問會跳過緩存直接訪問磁盤上的文件。
在大多數狀況下,這不是訪問文件的最佳方法。

2、線程池
爲了解決這個問題,Nginx 1.7.11 引入了線程池概念。如今讓咱們瞭解一下線程池是怎樣工做的。

在nginx中,線程池執行的是分發服務,他由一個任務隊列和一些執行任務的線程組成。
當一個工做線程在執行一個可能會存在潛在長時間操做的任務時,這個任務會被」卸下「並從新放到任務隊列中去,這個被」卸下「的任務可能會被其餘線程再執行。



如今,只有2個基礎操做會形成「卸下任務」到任務隊列:
1.在大多操做系統上的read()系統調用
2.linux系統的sendfile()
若是這個機制被證明是有益於nginx的,咱們之後還會添加其餘的操做。

3、線程池並不是靈丹妙藥

大多數讀寫文件操做都須要經過緩慢的磁盤。若是有充足的內存來存儲數據,那麼操做系統會緩存頻繁使用的文件,也就是「頁面緩存」(page cache)機制。

因爲頁面緩存機制,nginx幾乎在全部狀況下都能體現很是好的性能。
經過頁面緩存讀取數據很是快,而且不會阻塞。
另外一方面,卸下任務到任務池是有瓶頸的。
因此在內存充足而且使用的數據不是很是大的時候,nginx即便不使用線程池也是幾乎工做在最佳狀態。

卸下寫操做到任務池中,是一個適用於特殊場景的處理方案,適用於大量沒法使用VM緩存的請求操做。
例如一個高負荷的基於Nginx的視頻流服務器。

另外FreeBSD的用戶不須要擔憂這些,由於FreeBSD已經有很好的異步讀操做接口,無需使用線程池。

4、配置線程池
若是你肯定在你的場景中適合使用線程池,那麼一塊兒看看如何配置線程池。
準備工做步驟以下:
1.使用 nginx 1.7.11 或更新的版本
2.使用--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)性能測試階段譯文略過,能夠參考原帖

異步

相關文章
相關標籤/搜索