摘要:將訂單傳遞給運(yùn)送服務(wù)從而解除阻塞隊(duì)列在方面,線程池正在執(zhí)行運(yùn)送服務(wù)的功能。我們將繼續(xù)測(cè)試和評(píng)估,如果有明顯的好處,我們可能會(huì)在未來(lái)的版本中將其他操作也提交到線程池。
在nginx的官網(wǎng)看到一篇介紹nginx原理的文章,這篇文章比較老了是15年發(fā)布的,國(guó)內(nèi)有人翻譯過(guò)但是有些小瑕疵,這里更正出來(lái)發(fā)布在我個(gè)人的文章里供大家參考,這篇文章詳細(xì)的介紹了nginx線程池的原理以及設(shè)計(jì)思路,在最后通過(guò)詳細(xì)的實(shí)驗(yàn)數(shù)據(jù)來(lái)告訴我們通過(guò)線程池提升的性能以及分析了應(yīng)該使用線程池的場(chǎng)景。在日后的其他領(lǐng)域依然很有借鑒意義。
點(diǎn)我看源文
大家都知道NGINX使用異步以及事件驅(qū)動(dòng)的方式來(lái)處理請(qǐng)求,這意味著,我們不會(huì)為每個(gè)請(qǐng)求創(chuàng)建另一個(gè)專用的進(jìn)程或線程(比如像那些使用了傳統(tǒng)架構(gòu)的服務(wù)器)。而是選擇一個(gè)工作進(jìn)程來(lái)處理多個(gè)連接請(qǐng)求。為了實(shí)現(xiàn)這樣的特性,NGINX使用非阻塞模式下的socket以及選擇了更有效率的系統(tǒng)調(diào)用比如epoll和kqueue。
滿負(fù)載的進(jìn)程數(shù)量很少(通常是每個(gè)cpu核心只占一個(gè))而且是恒定的,這樣消耗了更少的內(nèi)存以及cpu時(shí)間片沒有被浪費(fèi)在任務(wù)切換上。這個(gè)方法的優(yōu)點(diǎn)可以通過(guò)nginx這個(gè)例子來(lái)反映出來(lái)。它可以非常好的并發(fā)處理上百萬(wàn)的請(qǐng)求規(guī)模并且處理的效果還不錯(cuò)。
每個(gè)進(jìn)程消耗額外的內(nèi)存,每次進(jìn)程之間的切換會(huì)消耗CPU周期以及產(chǎn)生cpu緩存垃圾
但是異步,事件驅(qū)動(dòng)這類模型同樣存在問題?;蛘呶腋矚g把這樣的問題稱作敵人。敵人的名字叫做:阻塞。不幸的是,許多第三方模塊都使用了阻塞式的調(diào)用,而且用戶(有時(shí)候甚至模塊的開發(fā)者)沒有意識(shí)到這么做的弊端。阻塞式的操作會(huì)毀掉NGINX的性能所以無(wú)論如何一定要被阻止。
但是在現(xiàn)在的官方版本的NGINX源代碼中也不可能在任何情況下避免阻塞,為了解決這個(gè)問題新的“線程池”特性被引入到NGINX version 1.7.11以及NGINX PLUS Release 7當(dāng)中來(lái).它是什么以及它如何使用這個(gè)我們稍后討論,現(xiàn)在我們來(lái)面對(duì)我們的敵人了。
編輯注-想對(duì)NGINX PLUS R7有個(gè)大概了解可以在我們的博客看到更多
想看NGINX PLUS R7其他特性的具體分析,可以看下邊列出來(lái)的博客:
HTTP/2 Now Fully Supported in NGINX Plus
Socket Sharding in NGINX
The New NGINX Plus Dashboard in Release 7
TCP Load Balancing in NGINX Plus R7
問題首先,為了更好的了解NGINX我們會(huì)用幾句話解釋一下它是如何工作的。
大體來(lái)說(shuō),NGINX是一個(gè)事件處理器,一個(gè)從內(nèi)核接收目前的連接信息然后發(fā)送接下來(lái)做的什么命令給操作系統(tǒng)的控制器。實(shí)際上NGINX做的臟活累活是通過(guò)協(xié)調(diào)操作系統(tǒng)來(lái)做的,本質(zhì)上是操作系統(tǒng)在做周期性的讀或者寫。所以對(duì)NGINX來(lái)說(shuō)反應(yīng)的快速及時(shí)是很重要的。
工作進(jìn)程監(jiān)聽以及處理從內(nèi)核傳過(guò)來(lái)的事件
這些事件可能會(huì)超時(shí),通知socket讀就緒或者寫就緒,或者通知一個(gè)錯(cuò)誤的產(chǎn)生。NGINX接收到一串事件接著一個(gè)一個(gè)的處理它們。所以所有的操作可以在一個(gè)線程的一個(gè)隊(duì)列的一次簡(jiǎn)單循環(huán)當(dāng)中完成。NGINX從隊(duì)列當(dāng)中彈出一個(gè)事件然后通過(guò)比如讀寫socket來(lái)做后續(xù)處理。在大多數(shù)情況下,這個(gè)操作會(huì)很快(也許這個(gè)操作只需要很少的cpu時(shí)間片去從內(nèi)存當(dāng)中copy一些數(shù)據(jù))并且NGINX可以用很短的時(shí)間在這個(gè)隊(duì)列當(dāng)中處理完所有的事件。
所有的操作都在一個(gè)線程的簡(jiǎn)單循環(huán)當(dāng)中做完了。
但是如果一個(gè)長(zhǎng)時(shí)間并且重量級(jí)的操作到來(lái)會(huì)發(fā)生啥呢?答案顯而易見,整個(gè)事件處理的循環(huán)都會(huì)被這個(gè)操作所阻塞直到這個(gè)操作完成。
因此,所謂“阻塞操作”是指任何導(dǎo)致事件處理循環(huán)顯著停止一段時(shí)間的操作。操作會(huì)因?yàn)楦鞣N各樣的原因而被阻塞。比如說(shuō),NGINX可能忙于處理冗長(zhǎng)的CPU密集型處理,或者可能需要等待訪問資源(例如硬盤驅(qū)動(dòng)器,或一個(gè)庫(kù)函數(shù)以同步方式從數(shù)據(jù)庫(kù)獲取響應(yīng),等等等等)。關(guān)鍵的問題在于,處理這樣的事情,工作線程不能做其他別的事情,即使其他的系統(tǒng)資源可以獲取到而且隊(duì)列當(dāng)中的其他一些事件會(huì)用到這些資源。
想象一下商店銷售員面前有長(zhǎng)長(zhǎng)的一長(zhǎng)隊(duì)人。 隊(duì)列中的第一個(gè)人需要一個(gè)不在商店但是在倉(cāng)庫(kù)里的東西。 銷售人員跑到倉(cāng)庫(kù)去提貨。 現(xiàn)在,整個(gè)隊(duì)列必須等待幾個(gè)小時(shí)才能進(jìn)行交付,排隊(duì)當(dāng)中的每個(gè)人都不滿意。 想想如果是你你會(huì)如何反應(yīng)? 隊(duì)員中的每個(gè)人的等待時(shí)間都增加了這幾個(gè)小時(shí),但他們打算買的東西有可能就在店里。
隊(duì)列里的每個(gè)人都必須因?yàn)榈谝粋€(gè)的訂單而等待。
相同的情況發(fā)生在NGINX當(dāng)中,想想當(dāng)它想要讀一個(gè)沒有緩存在內(nèi)存中的文件而不得不去訪問硬盤的時(shí)候。硬盤驅(qū)動(dòng)器很慢(特別是機(jī)械硬盤),而等待隊(duì)列中的其他請(qǐng)求可能不需要訪問驅(qū)動(dòng)器,所以它們也是被迫等待的。 因此,延遲增加,系統(tǒng)資源未得到充分利用。
只有一個(gè)阻塞會(huì)大幅延遲所有的接下來(lái)所有的操作
一些操作系統(tǒng)提供用于讀取和發(fā)送文件的異步接口,NGINX可以使用此接口(請(qǐng)參閱aio指令)。 這里有個(gè)好例子就是FreeBSD。坑爹的是,Linux可能不如左邊這位那么友好。 雖然Linux提供了一種用于讀取文件的異步接口,但它有一些顯著的缺點(diǎn)。 其中一個(gè)是文件訪問和緩沖區(qū)的對(duì)齊要求,當(dāng)然NGINX可以把這個(gè)問題處理得很好。 但第二個(gè)問題更糟糕,異步接口需要在文件描述符上設(shè)置O_DIRECT標(biāo)志,這意味著對(duì)文件的任何訪問將繞過(guò)內(nèi)存中的緩存并增加硬盤上的負(fù)載。這無(wú)形中干掉了很多原本可以使用這個(gè)調(diào)用的場(chǎng)景。
為了特別解決這個(gè)問題,NGINX 1.7.11和NGINX Plus Release 7中引入了線程池。
現(xiàn)在我們來(lái)看看什么線程池是關(guān)于它們以及它們的工作原理。
線程池讓我們回到上個(gè)問題,倒霉的銷售助理從遙遠(yuǎn)的倉(cāng)庫(kù)配貨這個(gè)用例。 這次他變得更聰明(也許是被憤怒的客戶群毆后變得更聰明了),他雇傭了送貨服務(wù)。 現(xiàn)在,當(dāng)有人需要遙遠(yuǎn)的倉(cāng)庫(kù)里的一些東西的時(shí)候,他不會(huì)親自去倉(cāng)庫(kù)而只不過(guò)下了一個(gè)訂單到送貨服務(wù),他們會(huì)處理訂單,而我們的銷售助理會(huì)繼續(xù)為其他客戶服務(wù)。 因此,只有那些貨物不在商店的客戶正在等待交貨,而售貨員可以馬上繼續(xù)為其他客戶提供服務(wù)。
將訂單傳遞給運(yùn)送服務(wù)從而解除阻塞隊(duì)列
在NGINX方面,線程池正在執(zhí)行運(yùn)送服務(wù)的功能。 它由一個(gè)任務(wù)隊(duì)列和多個(gè)處理隊(duì)列的線程組成。 當(dāng)一個(gè)工作進(jìn)程需要做一個(gè)潛在的長(zhǎng)時(shí)間操作時(shí),它不會(huì)自己處理這個(gè)操作,而是將一個(gè)任務(wù)放在線程池的隊(duì)列中,任何空閑的線程都可以從中進(jìn)行處理。
工作進(jìn)程將阻塞操作裝載到線程池
看來(lái)我們還有一個(gè)隊(duì)列。是的,但是在這種情況下,隊(duì)列受到特定資源的限制。我們從磁盤讀取資源速度永遠(yuǎn)比磁盤生成數(shù)據(jù)要慢。但是現(xiàn)在至少磁盤操作不會(huì)延遲其他事件的處理,只有需要訪問文件的請(qǐng)求正在等待。
通常將“從磁盤讀取”操作用作阻塞操作的最常見示例,但實(shí)際上NGINX中的線程池實(shí)現(xiàn)適用于任何不適合在主工作循環(huán)中處理的任務(wù)。
目前,提交到線程池僅用于三個(gè)基本操作:大多數(shù)操作系統(tǒng)上的read()系統(tǒng)調(diào)用,Linux上的sendfile()和Linux上的在編寫一些臨時(shí)文件比如緩存時(shí)使用到的aio_write()。我們將繼續(xù)測(cè)試和評(píng)估,如果有明顯的好處,我們可能會(huì)在未來(lái)的版本中將其他操作也提交到線程池。
編輯注: 在NGINX 1.9.13和NGINX Plus R9中添加了對(duì)aio_write()系統(tǒng)調(diào)用的支持。
評(píng)估基準(zhǔn)現(xiàn)在到了理論通往實(shí)踐的時(shí)候了。 為了演示使用線程池的效果,我們將執(zhí)行一個(gè)合成基準(zhǔn),模擬阻塞和非阻塞操作的最糟糕組合。
它需要一個(gè)確保不適合內(nèi)存貯存的數(shù)據(jù)集。 在具有48 GB內(nèi)存的機(jī)器上,我們已經(jīng)生成了256 GB的隨機(jī)4M分割數(shù)據(jù),然后配置了NGINX version 1.9.0來(lái)為其提供服務(wù)。
配置非常簡(jiǎn)單:
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; } } }
可以看到的是,為了獲得更好的性能,一些調(diào)優(yōu)已經(jīng)提前做完:logging和accept_mutex被禁用,sendfile被啟用,并且sendfile_max_chunk被設(shè)置。 最后一個(gè)指令可以減少阻止sendfile()調(diào)用所花費(fèi)的最大時(shí)間,因?yàn)镹GINX不會(huì)一次嘗試發(fā)送整個(gè)文件,而是分割成512 KB的數(shù)據(jù)塊來(lái)執(zhí)行相應(yīng)操作。
該機(jī)器有兩塊Intel Xeon E5645(12核24線程)處理器和10 Gbps網(wǎng)絡(luò)接口。 磁盤子系統(tǒng)由安裝在RAID10陣列中的四個(gè)西數(shù)WD1003FBYX硬盤驅(qū)動(dòng)器表示。 操作系統(tǒng)是Ubuntu Server 14.04.1 LTS。
相應(yīng)基準(zhǔn)下負(fù)載生成和NGINX的配置。
客戶由兩臺(tái)相同的規(guī)格的機(jī)器組成。其中一個(gè)機(jī)器上,wrk使用Lua腳本創(chuàng)建負(fù)載。腳本以200的并發(fā)連接從服務(wù)器以隨機(jī)順序請(qǐng)求文件,和每個(gè)請(qǐng)求都可能會(huì)導(dǎo)致緩存缺失從而導(dǎo)致從磁盤讀取產(chǎn)生的阻塞。我們就叫它“加載隨機(jī)載荷”。
第二客戶端機(jī)器我們將運(yùn)行另一個(gè)副本的wrk,但是這個(gè)腳本我們使用50的并發(fā)連接來(lái)請(qǐng)求相同的文件。因?yàn)檫@個(gè)文件被經(jīng)常訪問的,它將保持在內(nèi)存中。在正常情況下,NGINX很快的處理這些請(qǐng)求,但是工作線程如果被其他的請(qǐng)求阻塞性能將會(huì)下降。所以我們暫且叫它“加載恒定負(fù)載”。
性能將由服務(wù)器上ifstat監(jiān)測(cè)的吞吐率(throughput)和從第二臺(tái)客戶端獲取的wrk結(jié)果來(lái)度量。
現(xiàn)在,第一次沒有線程池給了我們不是那么讓人賽艇的結(jié)果:
% 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
如你所見,上述的配置可以產(chǎn)生一共1G的流量,從top命令上我們可以看到所有的工作線程在阻塞io上花費(fèi)了大量的時(shí)間(下圖D狀態(tài)):
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
在這種情況下,吞吐率受限于磁盤子系統(tǒng),而CPU在大部分時(shí)間里是空轉(zhuǎn)狀態(tài)的。從wrk獲得的結(jié)果來(lái)看也非常低:
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
請(qǐng)記住,文件是從內(nèi)存送達(dá)的!第一個(gè)客戶端的200個(gè)連接創(chuàng)建的隨機(jī)負(fù)載,使服務(wù)器端的全部的工作進(jìn)程忙于從磁盤讀取文件,因此產(chǎn)生了過(guò)大的延遲,并且無(wú)法在合適的時(shí)間內(nèi)處理我們的請(qǐng)求。
然后亮出線程池了。為此,我們只需在location塊中添加aio threads指令:
location / { root /storage; aio threads; }
接著,執(zhí)行NGINX reload重新加載配置。
然后,我們重復(fù)上述的測(cè)試:
% 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
現(xiàn)在我們的服務(wù)器產(chǎn)生9.5 Gbps的流量,對(duì)比之前沒有線程池時(shí)的1 Gbps高下立判!
理論上還可以產(chǎn)生更多的流量,但是這已經(jīng)達(dá)到了機(jī)器的最大網(wǎng)絡(luò)吞吐能力,所以在這次NGINX的測(cè)試中,NGINX受限于網(wǎng)絡(luò)接口。工作進(jìn)程的大部分時(shí)間只是休眠和等待新的事件(它們?cè)谙聢D處于top的S狀態(tài)):
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
現(xiàn)在仍然有充足的CPU資源可以利用
下邊是wrk的結(jié)果:
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
服務(wù)器處理4MB文件的平均時(shí)間從7.42秒降到226.32毫秒(減少了33倍),每秒請(qǐng)求處理數(shù)提升了31倍(250 vs 8)!
對(duì)此,我們的解釋是請(qǐng)求不再因?yàn)楣ぷ鬟M(jìn)程被阻塞在讀文件而滯留在事件隊(duì)列中等待處理,它們可以被空閑的線程(線程池當(dāng)中的)處理掉。只要磁盤子系統(tǒng)能撐住第一個(gè)客戶端上的隨機(jī)負(fù)載,NGINX可以使用剩余的CPU資源和網(wǎng)絡(luò)容量,從內(nèi)存中讀取,以服務(wù)于上述的第二個(gè)客戶端的請(qǐng)求。
沒有什么靈丹妙藥在拋出我們對(duì)阻塞操作的擔(dān)憂并給出一些令人振奮的結(jié)果后,可能大部分人已經(jīng)打算在你的服務(wù)器上配置線程池了。但是先別著急。
實(shí)際上很幸運(yùn)大多數(shù)的讀或者寫文件操作都不會(huì)和硬盤打交道。如果我們有足夠的內(nèi)存來(lái)存儲(chǔ)數(shù)據(jù)集,那么操作系統(tǒng)會(huì)聰明地在被稱作“頁(yè)面緩存”的地方緩存那些頻繁使用的文件。
“頁(yè)面緩存”的效果很好,可以讓NGINX在幾乎所有常見的用例中展示優(yōu)異的性能。從頁(yè)面緩存中讀取比較快,沒有人會(huì)說(shuō)這種操作是“阻塞”。另一方面,裝載任務(wù)到線程池是有一定開銷的。
因此,如果你的機(jī)器有合理的大小的內(nèi)存并且待處理的數(shù)據(jù)集不是很大的話,那么無(wú)需使用線程池,NGINX已經(jīng)工作在最優(yōu)化的方式下。
裝載讀操作到線程池是一種適用于非常特殊任務(wù)的技巧。只有當(dāng)經(jīng)常請(qǐng)求的內(nèi)容的大小不適合操作系統(tǒng)的虛擬機(jī)緩存時(shí),這種技術(shù)才是最有用的。至于可能適用的場(chǎng)景,比如,基于NGINX的高負(fù)載流媒體服務(wù)器。這正是我們已經(jīng)上文模擬的基準(zhǔn)測(cè)試的場(chǎng)景。
我們?nèi)绻梢愿倪M(jìn)裝載讀操作到線程池,將會(huì)非常有意義。我們只需要知道所需的文件數(shù)據(jù)是否在內(nèi)存中,只有不在內(nèi)存中的時(shí)候讀操作才應(yīng)該裝載到線程池的某個(gè)多帶帶的線程中。
再回到售貨員的場(chǎng)景中,這回售貨員不知道要買的商品是否在店里,他必須要么總是將所有的訂單提交給運(yùn)貨服務(wù),要么總是親自處理它們。
是的,問題的本質(zhì)就是操作系統(tǒng)沒有這樣的特性。2010年人們第一次試圖把這個(gè)功能作為fincore()系統(tǒng)調(diào)用加入到Linux當(dāng)中,但是沒有成功。后來(lái)還有一些是使用RWF_NONBLOCK標(biāo)記作為preadv2()系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)這一功能的嘗試(詳情見LWN.net上的非阻塞緩沖文件讀取操作和異步緩沖讀操作)。但所有這些補(bǔ)丁的命運(yùn)目前還不明朗。悲催的是,這些補(bǔ)丁尚沒有被內(nèi)核接受的主要原因你可以看這里:(bikeshedding)。
譯者著:我覺得沒加入內(nèi)核完全就是開發(fā)組里面一派人有類似“要啥自行車”這樣的想法.....
另一方面,F(xiàn)reeBSD的用戶完全不必?fù)?dān)心。FreeBSD已經(jīng)具備足夠好的異步讀取文件接口,我們應(yīng)該用它而不是線程池。
配置線程池所以如果你確信在你的用例中使用線程池會(huì)帶來(lái)好處,那么現(xiàn)在就是時(shí)候深入了解線程池的配置了。
線程池的配置非常簡(jiǎn)單、靈活。首先,獲取NGINX 1.7.11或更高版本的源代碼,使用--with-threads配置參數(shù)編譯。在最簡(jiǎn)單的場(chǎng)景中,配置也看起來(lái)很簡(jiǎn)單。所有你需要的所有事就是在合適的情況下把a(bǔ)io線程的指令include進(jìn)來(lái):
# in the "http", "server", or "location" context aio threads;
這是線程池的最簡(jiǎn)配置。實(shí)際上邊的配置是下邊的精簡(jiǎn)版:
# in the "main" context thread_pool default threads=32 max_queue=65536; # in the "http", "server", or "location" context aio threads=default;
這里定義了一個(gè)名為“default”,包含32個(gè)線程,任務(wù)隊(duì)列最多支持65536個(gè)請(qǐng)求的線程池。如果任務(wù)隊(duì)列過(guò)載,NGINX將拒絕請(qǐng)求并輸出如下錯(cuò)誤日志:
thread pool "NAME" queue overflow: N tasks waiting
錯(cuò)誤輸出意味著線程處理作業(yè)的速度有可能低于任務(wù)入隊(duì)的速度了。你可以嘗試增加隊(duì)列的最大值,但是如果這無(wú)濟(jì)于事,那這意味著你的系統(tǒng)沒有能力處理這么多的請(qǐng)求了。
正如你已經(jīng)注意到的,你可以使用thread_pool指令,配置線程的數(shù)量、隊(duì)列的最大長(zhǎng)度,以及特定線程池的名稱。最后要說(shuō)明的是,可以配置多個(gè)相互獨(dú)立的線程池,并在配置文件的不同位置使用它們來(lái)滿足不同的用途:
# 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參數(shù)的值,默認(rèn)使用的值是65536。如上所示,可以設(shè)置max_queue為0。在這種情況下,線程池將使用配置中全部數(shù)量的線程來(lái)盡可能地同時(shí)處理多個(gè)任務(wù);隊(duì)列中不會(huì)有等待的任務(wù)。
現(xiàn)在,假設(shè)我們有一臺(tái)服務(wù)器,掛了3塊硬盤,我們希望把該服務(wù)器用作“緩存代理”,緩存后端服務(wù)器的全部響應(yīng)。預(yù)期的緩存數(shù)據(jù)量遠(yuǎn)大于可用的內(nèi)存。它實(shí)際上是我們個(gè)人CDN的一個(gè)緩存節(jié)點(diǎn)。毫無(wú)疑問,在這種情況下,最重要的事情是發(fā)揮硬盤的最大性能。
我們的選擇之一是配置一個(gè)RAID陣列。這種方法毀譽(yù)參半,現(xiàn)在,有了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; } } }
在這份配置中,使用了3個(gè)獨(dú)立的緩存,每個(gè)緩存專用一塊硬盤,另外,3個(gè)獨(dú)立的線程池也各自專用一塊硬盤,proxy_cache_path指令在每個(gè)磁盤定義了一個(gè)專用、獨(dú)立的緩存
split_clients模塊用于高速緩存之間的負(fù)載平衡(以及磁盤之間的結(jié)果),它完全適合這類任務(wù)。
在 proxy_cache_path指令中設(shè)置use_temp_path=off,表示NGINX會(huì)將臨時(shí)文件保存在緩存數(shù)據(jù)的同一目錄中。這是為了避免在更新緩存時(shí),磁盤之間互相復(fù)制響應(yīng)數(shù)據(jù)。
這些調(diào)優(yōu)將發(fā)揮磁盤子系統(tǒng)的最優(yōu)性能,因?yàn)镹GINX通過(guò)多帶帶的線程池并行且獨(dú)立地與每塊磁盤交互。每個(gè)磁盤由16個(gè)獨(dú)立線程提供支持,并且線程具有用于讀取和發(fā)送文件的專用任務(wù)隊(duì)列。
我們相信你的客戶會(huì)喜歡這種量身定制的方法。請(qǐng)確保你的磁盤撐得住。
這個(gè)示例很好地證明了NGINX可以為硬件專門調(diào)優(yōu)的靈活性。這就像你給NGINX下了一道命令,要求機(jī)器和數(shù)據(jù)最優(yōu)配合。而且,通過(guò)NGINX在用戶空間中細(xì)粒度的調(diào)優(yōu),我們可以確保軟件、操作系統(tǒng)和硬件工作在最優(yōu)模式下并且盡可能有效地利用系統(tǒng)資源。
總結(jié)綜上所述,線程池是個(gè)好功能,它將NGINX的性能提高到新的高度并且干掉了一個(gè)眾所周知的長(zhǎng)期隱患:阻塞,尤其是當(dāng)我們真正面對(duì)大量吞吐的情況下這種優(yōu)勢(shì)更加明顯。
但是還有更多的驚喜。正如前面所述,這種全新的接口可能允許裝載任何耗時(shí)和阻塞的操作而不會(huì)造成任何性能的損失。 NGINX在大量新模塊和功能方面開辟了新的天地。 許多受歡迎的庫(kù)仍然沒有提供異步非阻塞接口,以前這使得它們與NGINX不兼容。 我們可能花費(fèi)大量的時(shí)間和精力來(lái)開發(fā)自己的非阻塞原型庫(kù),但是這么做可能并不值得。 現(xiàn)在,使用線程池,我們可以相對(duì)容易地使用這些庫(kù),并且這些模塊不會(huì)對(duì)性能產(chǎn)生影響。
敬請(qǐng)期待下篇文章。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/39589.html
摘要:文件系統(tǒng)請(qǐng)求和相關(guān)請(qǐng)求都會(huì)放進(jìn)這個(gè)線程池處理其他的請(qǐng)求,如網(wǎng)絡(luò)平臺(tái)特性相關(guān)的請(qǐng)求會(huì)分發(fā)給相應(yīng)的系統(tǒng)處理單元參見設(shè)計(jì)概覽。 譯者按:在 Medium 上看到這篇文章,行文脈絡(luò)清晰,闡述簡(jiǎn)明利落,果斷點(diǎn)下翻譯按鈕。第一小節(jié)背景鋪陳略啰嗦,可以略過(guò)。剛開始我給這部分留了個(gè) blah blah blah 直接翻后面的,翻完之后回頭看,考慮完整性才把第一節(jié)給補(bǔ)上。接下來(lái)的內(nèi)容干貨滿滿,相信對(duì) N...
摘要:當(dāng)一個(gè)進(jìn)行需要處理阻塞操作時(shí),它會(huì)將這個(gè)任務(wù)交給線程池來(lái)完成。線程池配置如果你確信引入線程池對(duì)性能提升有效,那么咱們可以繼續(xù)了解一些調(diào)優(yōu)參數(shù)。這個(gè)錯(cuò)誤表示這個(gè)線程池消費(fèi)小于生產(chǎn),所以可以增加隊(duì)列長(zhǎng)度,如果調(diào)整無(wú)效,說(shuō)明系統(tǒng)達(dá)到了瓶頸。 五年級(jí)英語(yǔ)水平,端午家庭作業(yè)。 前言 Nginx以異步、事件驅(qū)動(dòng)的方式處理連接。傳統(tǒng)的方式是每個(gè)請(qǐng)求新起一個(gè)進(jìn)程或線程,Nginx沒這樣做,它通過(guò)非...
摘要:用線程池執(zhí)行異步任務(wù)為了減少阻塞時(shí)間,加快響應(yīng)速度,把無(wú)需返回結(jié)果的操作變成異步任務(wù),用線程池來(lái)執(zhí)行,這是提高性能的一種手段。疲于奔命的模式,做不好大型服務(wù)端開發(fā),也難以做好各種領(lǐng)域的開發(fā)。 1. 用線程池執(zhí)行異步任務(wù) 為了減少阻塞時(shí)間,加快響應(yīng)速度,把無(wú)需返回結(jié)果的操作變成異步任務(wù),用線程池來(lái)執(zhí)行,這是提高性能的一種手段。 你可能要驚訝了,這么做不對(duì)嗎? 首先,我們把異步任務(wù)分...
摘要:用線程池執(zhí)行異步任務(wù)為了減少阻塞時(shí)間,加快響應(yīng)速度,把無(wú)需返回結(jié)果的操作變成異步任務(wù),用線程池來(lái)執(zhí)行,這是提高性能的一種手段。疲于奔命的模式,做不好大型服務(wù)端開發(fā),也難以做好各種領(lǐng)域的開發(fā)。 1. 用線程池執(zhí)行異步任務(wù) 為了減少阻塞時(shí)間,加快響應(yīng)速度,把無(wú)需返回結(jié)果的操作變成異步任務(wù),用線程池來(lái)執(zhí)行,這是提高性能的一種手段。 你可能要驚訝了,這么做不對(duì)嗎? 首先,我們把異步任務(wù)分...
閱讀 1830·2021-10-20 13:49
閱讀 1370·2019-08-30 15:52
閱讀 2875·2019-08-29 16:37
閱讀 1045·2019-08-29 10:55
閱讀 3079·2019-08-26 12:14
閱讀 1658·2019-08-23 17:06
閱讀 3241·2019-08-23 16:59
閱讀 2550·2019-08-23 15:42