NGINX引入線程池 性能提升9倍

来源:http://www.cnblogs.com/liu-ke/archive/2016/12/14/6178176.html
-Advertisement-
Play Games

1. 引言 正如我們所知,NGINX採用了非同步、事件驅動的方法來處理連接。這種處理方式無需(像使用傳統架構的伺服器一樣)為每個請求創建額外的專用進程或者線程,而是在一個工作進程中處理多個連接和請求。為此,NGINX工作在非阻塞的socket模式下,並使用了epoll 和 kqueue這樣有效的方法。 ...


1. 引言

正如我們所知,NGINX採用了非同步、事件驅動的方法來處理連接。這種處理方式無需(像使用傳統架構的伺服器一樣)為每個請求創建額外的專用進程或者線程,而是在一個工作進程中處理多個連接和請求。為此,NGINX工作在非阻塞的socket模式下,並使用了epoll 和 kqueue這樣有效的方法。

因為滿負載進程的數量很少(通常每核CPU只有一個)而且恆定,所以任務切換隻消耗很少的記憶體,而且不會浪費CPU周期。通過NGINX本身的實例,這種方法的優點已經為眾人所知。NGINX可以非常好地處理百萬級規模的併發請求。

 

每個進程都消耗額外的記憶體,而且每次進程間的切換都會消耗CPU周期並丟棄CPU高速緩存中的數據。

但是,非同步、事件驅動方法仍然存在問題。或者,我喜歡將這一問題稱為“敵兵”,這個敵兵的名字叫阻塞(blocking)。不幸的是,很多第三方模塊使用了阻塞調用,然而用戶(有時甚至是模塊的開發者)並不知道阻塞的缺點。阻塞操作可以毀掉NGINX的性能,我們必須不惜一切代價避免使用阻塞。

即使在當前官方的NGINX代碼中,依然無法在全部場景中避免使用阻塞,NGINX1.7.11中實現的線程池機制解決了這個問題。我們將在後面講述這個線程池是什麼以及該如何使用。現在,讓我們先和我們的“敵兵”進行一次面對面的碰撞。

2. 問題

首先,為了更好地理解這一問題,我們用幾句話說明下NGINX是如何工作的。

通常情況下,NGINX是一個事件處理器,即一個接收來自內核的所有連接事件的信息,然後向操作系統發出做什麼指令的控制器。實際上,NGINX幹了編排操作系統的全部臟活累活,而操作系統做的是讀取和發送位元組這樣的日常工作。所以,對於NGINX來說,快速和及時的響應是非常重要的。

工作進程監聽並處理來自內核的事件

事件可以是超時、socket讀寫就緒的通知,或者發生錯誤的通知。NGINX接收大量的事件,然後一個接一個地處理它們,並執行必要的操作。因此,所有的處理過程是通過一個線程中的隊列,在一個簡單迴圈中完成的。NGINX從隊列中取出一個事件並對其做出響應,比如讀寫socket。在多數情況下,這種方式是非常快的(也許只需要幾個CPU周期,將一些數據複製到記憶體中),NGINX可以在一瞬間處理掉隊列中的所有事件。

所有處理過程是在一個簡單的迴圈中,由一個線程完成

但是,如果NGINX要處理的操作是一些又長又重的操作,又會發生什麼呢?整個事件處理迴圈將會卡住,等待這個操作執行完畢。

因此,所謂“阻塞操作”是指任何導致事件處理迴圈顯著停止一段時間的操作。操作可以由於各種原因成為阻塞操作。例如,NGINX可能因長時間、CPU密集型處理,或者可能等待訪問某個資源(比如硬碟,或者一個互斥體,亦或要從處於同步方式的資料庫獲得相應的庫函數調用等)而繁忙。關鍵是在處理這樣的操作期間,工作進程無法做其他事情或者處理其他事件,即使有更多的可用系統資源可以被隊列中的一些事件所利用。

我們來打個比方,一個商店的營業員要接待他面前排起的一長隊顧客。隊伍中的第一位顧客想要的某件商品不在店裡而在倉庫中。這位營業員跑去倉庫把東西拿來。現在整個隊伍必須為這樣的配貨方式等待數個小時,隊伍中的每個人都很不爽。你可以想見人們的反應吧?隊伍中每個人的等待時間都要增加這些時間,除非他們要買的東西就在店裡。

隊伍中的每個人不得不等待第一個人的購買

在NGINX中會發生幾乎同樣的情況,比如當讀取一個文件的時候,如果該文件沒有緩存在記憶體中,就要從磁碟上讀取。從磁碟(特別是旋轉式的磁碟)讀取是很慢的,而當隊列中等待的其他請求可能不需要訪問磁碟時,它們也得被迫等待。導致的結果是,延遲增加並且系統資源沒有得到充分利用。

一個阻塞操作足以顯著地延緩所有接下來的操作

一些操作系統為讀寫文件提供了非同步介面,NGINX可以使用這樣的介面(見AIO指令)。FreeBSD就是個很好的例子。不幸的是,我們不能在Linux上得到相同的福利。雖然Linux為讀取文件提供了一種非同步介面,但是存在明顯的缺點。其中之一是要求文件訪問和緩衝要對齊,但NGINX很好地處理了這個問題。但是,另一個缺點更糟糕。非同步介面要求文件描述符中要設置O_DIRECT標記,就是說任何對文件的訪問都將繞過記憶體中的緩存,這增加了磁碟的負載。在很多場景中,這都絕對不是最佳選擇。

為了有針對性地解決這一問題,在NGINX 1.7.11中引入了線程池。預設情況下,NGINX+還沒有包含線程池,但是如果你想試試的話,可以聯繫銷售人員,NGINX+ R6是一個已經啟用了線程池的構建版本。

現在,讓我們走進線程池,看看它是什麼以及如何工作的。

3. 線程池

讓我們回到那個可憐的,要從大老遠的倉庫去配貨的售貨員那兒。這回,他已經變聰明瞭(或者也許是在一群憤怒的顧客教訓了一番之後,他才變得聰明的?),雇用了一個配貨服務團隊。現在,當任何人要買的東西在大老遠的倉庫時,他不再親自去倉庫了,只需要將訂單丟給配貨服務,他們將處理訂單,同時,我們的售貨員依然可以繼續為其他顧客服務。因此,只有那些要買倉庫里東西的顧客需要等待配貨,其他顧客可以得到即時服務。

傳遞訂單給配貨服務不會阻塞隊伍

對NGINX而言,線程池執行的就是配貨服務的功能。它由一個任務隊列和一組處理這個隊列的線程組成。
當工作進程需要執行一個潛在的長操作時,工作進程不再自己執行這個操作,而是將任務放到線程池隊列中,任何空閑的線程都可以從隊列中獲取並執行這個任務。

工作進程將阻塞操作卸給線程池

那麼,這就像我們有了另外一個隊列。是這樣的,但是在這個場景中,隊列受限於特殊的資源。磁碟的讀取速度不能比磁碟產生數據的速度快。不管怎麼說,至少現在磁碟不再延誤其他事件,只有訪問文件的請求需要等待。

“從磁碟讀取”這個操作通常是阻塞操作最常見的示例,但是實際上,NGINX中實現的線程池可用於處理任何不適合在主迴圈中執行的任務。

目前,卸載到線程池中執行的兩個基本操作是大多數操作系統中的read()系統調用和Linux中的sendfile()。接下來,我們將對線程池進行測試(test)和基準測試(benchmark),在未來的版本中,如果有明顯的優勢,我們可能會卸載其他操作到線程池中。

4. 基準測試

現在讓我們從理論過度到實踐。我們將進行一次模擬基準測試(synthetic benchmark),模擬在阻塞操作和非阻塞操作的最差混合條件下,使用線程池的效果。

另外,我們需要一個記憶體肯定放不下的數據集。在一臺48GB記憶體的機器上,我們已經產生了每文件大小為4MB的隨機數據,總共256GB,然後配置NGINX,版本為1.9.0。

配置很簡單:

worker_processes 16;

events {
    accept_mutex off;
}

http {
    include mime.types;
    default_type application/octet-stream;

    access_log off;
    sendfile on;
    sendfile_max_chunk 512k;

    server {
        listen 8000;

        location / {
            root /storage;
        }
    }
}

 

如上所示,為了達到更好的性能,我們調整了幾個參數:禁用了loggingaccept_mutex,同時,啟用了sendfile並設置了sendfile_max_chunk的大小。最後一個指令可以減少阻塞調用sendfile()所花費的最長時間,因為NGINX不會嘗試一次將整個文件發送出去,而是每次發送大小為512KB的塊數據。

這台測試伺服器有2個Intel Xeon E5645處理器(共計:12核、24超線程)和10-Gbps的網路介面。磁碟子系統是由4塊西部數據WD1003FBYX 磁碟組成的RAID10陣列。所有這些硬體由Ubuntu伺服器14.04.1 LTS供電。

為基準測試配置負載生成器和NGINX

客戶端有2台伺服器,它們的規格相同。在其中一臺上,在wrk中使用Lua腳本創建了負載程式。腳本使用200個並行連接向伺服器請求文件,每個請求都可能未命中緩存而從磁碟阻塞讀取。我們將這種負載稱作隨機負載。

在另一臺客戶端機器上,我們將運行wrk的另一個副本,使用50個並行連接多次請求同一個文件。因為這個文件將被頻繁地訪問,所以它會一直駐留在記憶體中。在正常情況下,NGINX能夠非常快速地服務這些請求,但是如果工作進程被其他請求阻塞的話,性能將會下降。我們將這種負載稱作恆定負載。

性能將由伺服器上ifstat監測的吞吐率(throughput)和從第二台客戶端獲取的wrk結果來度量。

現在,沒有使用線程池的第一次運行將不會帶給我們非常振奮的結果:

% ifstat -bi eth2
eth2
Kbps in  Kbps out
5531.24  1.03e+06
4855.23  812922.7
5994.66  1.07e+06
5476.27  981529.3
6353.62  1.12e+06
5166.17  892770.3
5522.81  978540.8
6208.10  985466.7
6370.79  1.12e+06
6123.33  1.07e+06

 

如上所示,使用這種配置,伺服器產生的總流量約為1Gbps。從下麵所示的top輸出,我們可以看到,工作進程的大部分時間花在阻塞I/O上(它們處於top的D狀態):

top - 10:40:47 up 11 days,  1:32,  1 user,  load average: 49.61, 45.77 62.89
Tasks: 375 total,  2 running, 373 sleeping,  0 stopped,  0 zombie
%Cpu(s):  0.0 us,  0.3 sy,  0.0 ni, 67.7 id, 31.9 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:  49453440 total, 49149308 used,   304132 free,    98780 buffers
KiB Swap: 10474236 total,    20124 used, 10454112 free, 46903412 cached Mem

  PID USER     PR  NI    VIRT    RES     SHR S  %CPU %MEM    TIME+ COMMAND
 4639 vbart    20   0   47180  28152     496 D   0.7  0.1  0:00.17 nginx
 4632 vbart    20   0   47180  28196     536 D   0.3  0.1  0:00.11 nginx
 4633 vbart    20   0   47180  28324     540 D   0.3  0.1  0:00.11 nginx
 4635 vbart    20   0   47180  28136     480 D   0.3  0.1  0:00.12 nginx
 4636 vbart    20   0   47180  28208     536 D   0.3  0.1  0:00.14 nginx
 4637 vbart    20   0   47180  28208     536 D   0.3  0.1  0:00.10 nginx
 4638 vbart    20   0   47180  28204     536 D   0.3  0.1  0:00.12 nginx
 4640 vbart    20   0   47180  28324     540 D   0.3  0.1  0:00.13 nginx
 4641 vbart    20   0   47180  28324     540 D   0.3  0.1  0:00.13 nginx
 4642 vbart    20   0   47180  28208     536 D   0.3  0.1  0:00.11 nginx
 4643 vbart    20   0   47180  28276     536 D   0.3  0.1  0:00.29 nginx
 4644 vbart    20   0   47180  28204     536 D   0.3  0.1  0:00.11 nginx
 4645 vbart    20   0   47180  28204     536 D   0.3  0.1  0:00.17 nginx
 4646 vbart    20   0   47180  28204     536 D   0.3  0.1  0:00.12 nginx
 4647 vbart    20   0   47180  28208     532 D   0.3  0.1  0:00.17 nginx
 4631 vbart    20   0   47180    756     252 S   0.0  0.1  0:00.00 nginx
 4634 vbart    20   0   47180  28208     536 D   0.0  0.1  0:00.11 nginx
 4648 vbart    20   0   25232   1956    1160 R   0.0  0.0  0:00.08 top
25921 vbart    20   0  121956   2232    1056 S   0.0  0.0  0:01.97 sshd
25923 vbart    20   0   40304   4160    2208 S   0.0  0.0  0:00.53 zsh

 

在這種情況下,吞吐率受限於磁碟子系統,而CPU在大部分時間里是空閑的。從wrk獲得的結果也非常低:

Running 1m test @ http://192.0.2.1:8000/1/1/1
  12 threads and 50 connections
  Thread Stats   Avg    Stdev     Max  +/- Stdev
    Latency     7.42s  5.31s   24.41s   74.73%
    Req/Sec     0.15    0.36     1.00    84.62%
  488 requests in 1.01m, 2.01GB read
Requests/sec:      8.08
Transfer/sec:     34.07MB

 

請記住,文件是從記憶體送達的!第一個客戶端的200個連接創建的隨機負載,使伺服器端的全部的工作進程忙於從磁碟讀取文件,因此產生了過大的延遲,並且無法在合理的時間內處理我們的請求。

現在,我們的線程池要登場了。為此,我們只需在location塊中添加aio threads指令:

location / {
    root /storage;
    aio threads;
}

 

接著,執行NGINX reload重新載入配置。

然後,我們重覆上述的測試:

% ifstat -bi eth2
eth2
Kbps in  Kbps out
60915.19  9.51e+06
59978.89  9.51e+06
60122.38  9.51e+06
61179.06  9.51e+06
61798.40  9.51e+06
57072.97  9.50e+06
56072.61  9.51e+06
61279.63  9.51e+06
61243.54  9.51e+06
59632.50  9.50e+06

 

現在,我們的伺服器產生的流量是9.5Gbps,相比之下,沒有使用線程池時只有約1Gbps!

理論上還可以產生更多的流量,但是這已經達到了機器的最大網路吞吐能力,所以在這次NGINX的測試中,NGINX受限於網路介面。工作進程的大部分時間只是休眠和等待新的事件(它們處於top的S狀態):

top - 10:43:17 up 11 days,  1:35,  1 user,  load average: 172.71, 93.84, 77.90
Tasks: 376 total,  1 running, 375 sleeping,  0 stopped,  0 zombie
%Cpu(s):  0.2 us,  1.2 sy,  0.0 ni, 34.8 id, 61.5 wa,  0.0 hi,  2.3 si,  0.0 st
KiB Mem:  49453440 total, 49096836 used,   356604 free,    97236 buffers
KiB Swap: 10474236 total,    22860 used, 10451376 free, 46836580 cached Mem

  PID USER     PR  NI    VIRT    RES     SHR S  %CPU %MEM    TIME+ COMMAND
 4654 vbart    20   0  309708  28844     596 S   9.0  0.1  0:08.65 nginx
 4660 vbart    20   0  309748  28920     596 S   6.6  0.1  0:14.82 nginx
 4658 vbart    20   0  309452  28424     520 S   4.3  0.1  0:01.40 nginx
 4663 vbart    20   0  309452  28476     572 S   4.3  0.1  0:01.32 nginx
 4667 vbart    20   0  309584  28712     588 S   3.7  0.1  0:05.19 nginx
 4656 vbart    20   0  309452  28476     572 S   3.3  0.1  0:01.84 nginx
 4664 vbart    20   0  309452  28428     524 S   3.3  0.1  0:01.29 nginx
 4652 vbart    20   0  309452  28476     572 S   3.0  0.1  0:01.46 nginx
 4662 vbart    20   0  309552  28700     596 S   2.7  0.1  0:05.92 nginx
 4661 vbart    20   0  309464  28636     596 S   2.3  0.1  0:01.59 nginx
 4653 vbart    20   0  309452  28476     572 S   1.7  0.1  0:01.70 nginx
 4666 vbart    20   0  309452  28428     524 S   1.3  0.1  0:01.63 nginx
 4657 vbart    20   0  309584  28696     592 S   1.0  0.1  0:00.64 nginx
 4655 vbart    20   0  30958   28476     572 S   0.7  0.1  0:02.81 nginx
 4659 vbart    20   0  309452  28468     564 S   0.3  0.1  0:01.20 nginx
 4665 vbart    20   0  309452  28476     572 S   0.3  0.1  0:00.71 nginx
 5180 vbart    20   0   25232   1952    1156 R   0.0  0.0  0:00.45 top
 4651 vbart    20   0   20032    752     252 S   0.0  0.0  0:00.00 nginx
25921 vbart    20   0  121956   2176    1000 S   0.0  0.0  0:01.98 sshd
25923 vbart    20   0   40304   3840    2208 S   0.0  0.0  0:00.54 zsh

 

如上所示,基準測試中還有大量的CPU資源剩餘。

wrk的結果如下:

Running 1m test @ http://192.0.2.1:8000/1/1/1
  12 threads and 50 connections
  Thread Stats   Avg      Stdev     Max  +/- Stdev
    Latency   226.32ms  392.76ms   1.72s   93.48%
    Req/Sec    20.02     10.84    59.00    65.91%
  15045 requests in 1.00m, 58.86GB read
Requests/sec:    250.57
Transfer/sec:      0.98GB

 

伺服器處理4MB文件的平均時間從7.42秒降到226.32毫秒(減少了33倍),每秒請求處理數提升了31倍(250 vs 8)!

對此,我們的解釋是請求不再因為工作進程被阻塞在讀文件,而滯留在事件隊列中,等待處理,它們可以被空閑的進程處理掉。只要磁碟子系統能做到最好,就能服務好第一個客戶端上的隨機負載,NGINX可以使用剩餘的CPU資源和網路容量,從記憶體中讀取,以服務於上述的第二個客戶端的請求。

5. 依然沒有銀彈

在拋出我們對阻塞操作的擔憂並給出一些令人振奮的結果後,可能大部分人已經打算在你的伺服器上配置線程池了。先彆著急。

實際上,最幸運的情況是,讀取和發送文件操作不去處理緩慢的硬碟驅動器。如果我們有足夠多的記憶體來存儲數據集,那麼操作系統將會足夠聰明地在被稱作“頁面緩存”的地方,緩存頻繁使用的文件。

“頁面緩存”的效果很好,可以讓NGINX在幾乎所有常見的用例中展示優異的性能。從頁面緩存中讀取比較快,沒有人會說這種操作是“阻塞”。而另一方面,卸載任務到一個線程池是有一定開銷的。

因此,如果記憶體有合理的大小並且待處理的數據集不是很大的話,那麼無需使用線程池,NGINX已經工作在最優化的方式下。

卸載讀操作到線程池是一種適用於非常特殊任務的技術。只有當經常請求的內容的大小,不適合操作系統的虛擬機緩存時,這種技術才是最有用的。至於可能適用的場景,比如,基於NGINX的高負載流媒體伺服器。這正是我們已經模擬的基準測試的場景。

我們如果可以改進卸載讀操作到線程池,將會非常有意義。我們只需要知道所需的文件數據是否在記憶體中,只有不在記憶體中時,讀操作才應該卸載到一個單獨的線程中。

再回到售貨員那個比喻的場景中,這回,售貨員不知道要買的商品是否在店裡,他必須要麼總是將所有的訂單提交給配貨服務,要麼總是親自處理它們。

人艱不拆,操作系統缺少這樣的功能。第一次嘗試是在2010年,人們試圖將這一功能添加到Linux作為fincore()系統調用,但是沒有成功。後來還有一些嘗試,是使用RWF_NONBLOCK標記作為preadv2()系統調用來實現這一功能(詳情見LWN.net上的非阻塞緩衝文件讀取操作非同步緩衝讀操作)。但所有這些補丁的命運目前還不明朗。悲催的是,這些補丁尚沒有被內核接受的主要原因,貌似是因為曠日持久的撕逼大戰(bikeshedding)。

另一方面,FreeBSD的用戶完全不必擔心。FreeBSD已經具備足夠好的非同步讀取文件介面,我們應該用這個介面而不是線程池。

6. 配置線程池

所以,如果你確信在你的場景中使用線程池可以帶來好處,那麼現在是時候深入瞭解線程池的配置了。

線程池的配置非常簡單、靈活。首先,獲取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。在這種情況下,線程池將使用配置中全部數量的線程,儘可能地同時處理多個任務;隊列中不會有等待的任務。

現在,假設我們有一臺伺服器,掛了3塊硬碟,我們希望把該伺服器用作“緩存代理”,緩存後端伺服器的全部響應信息。預期的緩存數據量遠大於可用的記憶體。它實際上是我們個人CDN的一個緩存節點。毫無疑問,在這種情況下,最重要的事情是發揮硬碟的最大性能。

我們的選擇之一是配置一個RAID陣列。這種方法毀譽參半,現在,有了NGINX,我們可以有其他的選擇:

# 我們假設每塊硬碟掛載在相應的目錄中:/mnt/disk1、/mnt/disk2、/mnt/disk3

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;
}

 

在這份配置中,使用了3個獨立的緩存,每個緩存專用一塊硬碟,另外,3個獨立的線程池也各自專用一塊硬碟。

緩存之間(其結果就是磁碟之間)的負載均衡使用split_clients模塊,split_clients非常適用於這個任務。

在 proxy_cache_path指令中設置use_temp_path=off,表示NGINX會將臨時文件保存在緩存數據的同一目錄中。這是為了避免在更新緩存時,磁碟之間互相複製響應數據。

這些調優將帶給我們磁碟子系統的最大性能,因為NGINX通過單獨的線程池並行且獨立地與每塊磁碟交互。每塊磁碟由16個獨立線程和讀取和發送文件專用任務隊列提供服務。

我敢打賭,你的客戶喜歡這種量身定製的方法。請確保你的磁碟也持有同樣的觀點。

這個示例很好地證明瞭NGINX可以為硬體專門調優的靈活性。這就像你給NGINX下了一道命令,讓機器和數據用最佳姿勢來搞基。而且,通過NGINX在用戶空間中細粒度的調優,我們可以確保軟體、操作系統和硬體工作在最優模式下,儘可能有效地利用系統資源。

7. 總結

綜上所述,線程池是一個偉大的功能,將NGINX推向了新的性能水平,除掉了一個眾所周知的長期危害——阻塞——尤其是當我們真正面對大量內容的時候。

甚至,還有更多的驚喜。正如前面提到的,這個全新的介面,有可能沒有任何性能損失地卸載任何長期阻塞操作。NGINX在擁有大量的新模塊和新功能方面,開闢了一方新天地。許多流行的庫仍然沒有提供非同步非阻塞介面,此前,這使得它們無法與NGINX相容。我們可以花大量的時間和資源,去開發我們自己的無阻塞原型庫,但這麼做始終都是值得的嗎?現在,有了線程池,我們可以相對容易地使用這些庫,而不會影響這些模塊的性能。

 

 

英文原文:Thread Pools in NGINX Boost Performance 9x!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.自我介紹。2.占領腦袋和丟了腳3.物聯的現實困難4.效率與成本5.智能網關,跑Windows 10 IOT和Ubuntu Mate6.SuperIO到ServerSuperIO發展歷程和解決的實現問題7.一套設備驅動,支持多種IO通訊8.一套設備驅動,統一介面,多種平臺掛載運行9.物聯通訊的級聯... ...
  • 本文地址 原文地址 本文提綱: 1. Nginx的模塊與工作原理 2. Nginx的進程模型 3 . NginxFastCGI運行原理 3.1 什麼是 FastCGI 3.2 NginxFastCGI運行原理 3.3 spawn-fcgi與PHP-FPM 3.4 NginxPHP-FPM 4. Ng ...
  • 在Salesforce中,常常要對各種數據進行處理,已滿足業務邏輯。本篇文章會介紹如何實現從object獲取數據,然後將取得的數據進行一系列簡單處理。 第一步:SongName__c 是一個新建的object,向SongName__c object中插入數據: 列印出SongName__c obje ...
  • 設計文檔模板: 一些其他的思考: 去除一切花俏的建模技巧,我覺得最重要的方向就是去努力分析問題和事物的本質,針對這個本質進行領域建模。這個領域建模,最主要的還是鍛煉的人的事物抽象能力。10個人,建出來的領域模型都不同。本質原因就是大家對同一個問題的理解不同,對事物的本質的理解不同。雖然最終都能解決當 ...
  • package test05;import test06.Car1;public class DuoTai_Test02 { /**多個對象,一個形態 * Tiger、Lion、Snake → Animal *、多個對象,一種形態(類型)。 * 不同對象,表現出同一種形態之後。 * 可以實現同樣的功 ...
  • /** * 內部類 ?? * 定義在一個內部的類,被稱為內部類。 * 內部類里有類體,方法體 * 內部類所在的類,被稱為外部類。 * * ①內部類的意義 * 在一定程度上,起到了代碼的有效保護。 * ②語法 * class Outer{////外部類 * 成員變數 * 成員方法 * class In ...
  • 1.什麼是裝飾者模式 動態給對象增加功能,從一個對象的外部來給對象添加功能,相當於改變了對象的外觀,比用繼承的方式更加的靈活。當使用裝飾後,從外部系統的角度看,就不再是原來的那個對象了,而是使用一系列的裝飾器裝飾過後的對象。 2.結構 3.示例 下麵我們用裝飾者模式實現如下的功能: 要求用戶輸入一段 ...
  • 建造者模式:將一個複雜對象的構造與它的表示分離,使同樣的構建過程可以創建不同的表示的設計模式。 設計場景: 有一個用戶的UserInfo類,創建這個類,需要創建用戶的姓名,年齡,愛好等信息,才能獲得用戶具體的信息結果。 創建一個UserBuilder 用戶建造者類,這個類,將UserInfo複雜的創 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...