1. 引言正如我們所知,NGINX采用了異步,、事件驅(qū)動(dòng)的方法來處理連接,。這種處理方式無需(像使用傳統(tǒng)架構(gòu)的服務(wù)器一樣)為每個(gè)請求創(chuàng)建額外的專用進(jìn)程或者線程,而是在一個(gè)工作進(jìn)程中處理多個(gè)連接和請求,。為此,,NGINX工作在非阻塞的socket模式下,并使用了epoll 和 kqueue這樣有效的方法,。 因?yàn)闈M負(fù)載進(jìn)程的數(shù)量很少(通常每核CPU只有一個(gè))而且恒定,,所以任務(wù)切換只消耗很少的內(nèi)存,而且不會(huì)浪費(fèi)CPU周期,。通過NGINX本身的實(shí)例,,這種方法的優(yōu)點(diǎn)已經(jīng)為眾人所知。NGINX可以非常好地處理百萬級規(guī)模的并發(fā)請求,。
但是,,異步,、事件驅(qū)動(dòng)方法仍然存在問題?;蛘?,我喜歡將這一問題稱為“敵兵”,這個(gè)敵兵的名字叫阻塞(blocking),。不幸的是,,很多第三方模塊使用了阻塞調(diào)用,,然而用戶(有時(shí)甚至是模塊的開發(fā)者)并不知道阻塞的缺點(diǎn)。阻塞操作可以毀掉NGINX的性能,,我們必須不惜一切代價(jià)避免使用阻塞,。 即使在當(dāng)前官方的NGINX代碼中,依然無法在全部場景中避免使用阻塞,,NGINX1.7.11中實(shí)現(xiàn)的線程池機(jī)制解決了這個(gè)問題,。我們將在后面講述這個(gè)線程池是什么以及該如何使用。現(xiàn)在,,讓我們先和我們的“敵兵”進(jìn)行一次面對面的碰撞,。 2. 問題首先,為了更好地理解這一問題,,我們用幾句話說明下NGINX是如何工作的。 通常情況下,,NGINX是一個(gè)事件處理器,,即一個(gè)接收來自內(nèi)核的所有連接事件的信息,然后向操作系統(tǒng)發(fā)出做什么指令的控制器,。實(shí)際上,,NGINX干了編排操作系統(tǒng)的全部臟活累活,而操作系統(tǒng)做的是讀取和發(fā)送字節(jié)這樣的日常工作,。所以,,對于NGINX來說,快速和及時(shí)的響應(yīng)是非常重要的,。
事件可以是超時(shí),、socket讀寫就緒的通知,或者發(fā)生錯(cuò)誤的通知,。NGINX接收大量的事件,,然后一個(gè)接一個(gè)地處理它們,并執(zhí)行必要的操作,。因此,,所有的處理過程是通過一個(gè)線程中的隊(duì)列,在一個(gè)簡單循環(huán)中完成的,。NGINX從隊(duì)列中取出一個(gè)事件并對其做出響應(yīng),,比如讀寫socket。在多數(shù)情況下,,這種方式是非??斓模ㄒ苍S只需要幾個(gè)CPU周期,將一些數(shù)據(jù)復(fù)制到內(nèi)存中),,NGINX可以在一瞬間處理掉隊(duì)列中的所有事件,。
但是,如果NGINX要處理的操作是一些又長又重的操作,,又會(huì)發(fā)生什么呢,?整個(gè)事件處理循環(huán)將會(huì)卡住,等待這個(gè)操作執(zhí)行完畢,。 因此,,所謂“阻塞操作”是指任何導(dǎo)致事件處理循環(huán)顯著停止一段時(shí)間的操作。操作可以由于各種原因成為阻塞操作,。例如,,NGINX可能因長時(shí)間、CPU密集型處理,,或者可能等待訪問某個(gè)資源(比如硬盤,,或者一個(gè)互斥體,亦或要從處于同步方式的數(shù)據(jù)庫獲得相應(yīng)的庫函數(shù)調(diào)用等)而繁忙,。關(guān)鍵是在處理這樣的操作期間,,工作進(jìn)程無法做其他事情或者處理其他事件,即使有更多的可用系統(tǒng)資源可以被隊(duì)列中的一些事件所利用,。 我們來打個(gè)比方,,一個(gè)商店的營業(yè)員要接待他面前排起的一長隊(duì)顧客。隊(duì)伍中的第一位顧客想要的某件商品不在店里而在倉庫中,。這位營業(yè)員跑去倉庫把東西拿來?,F(xiàn)在整個(gè)隊(duì)伍必須為這樣的配貨方式等待數(shù)個(gè)小時(shí),隊(duì)伍中的每個(gè)人都很不爽,。你可以想見人們的反應(yīng)吧,?隊(duì)伍中每個(gè)人的等待時(shí)間都要增加這些時(shí)間,除非他們要買的東西就在店里,。
在NGINX中會(huì)發(fā)生幾乎同樣的情況,,比如當(dāng)讀取一個(gè)文件的時(shí)候,如果該文件沒有緩存在內(nèi)存中,,就要從磁盤上讀取,。從磁盤(特別是旋轉(zhuǎn)式的磁盤)讀取是很慢的,而當(dāng)隊(duì)列中等待的其他請求可能不需要訪問磁盤時(shí),它們也得被迫等待,。導(dǎo)致的結(jié)果是,,延遲增加并且系統(tǒng)資源沒有得到充分利用。
一些操作系統(tǒng)為讀寫文件提供了異步接口,,NGINX可以使用這樣的接口(見AIO指令),。FreeBSD就是個(gè)很好的例子。不幸的是,,我們不能在Linux上得到相同的福利,。雖然Linux為讀取文件提供了一種異步接口,,但是存在明顯的缺點(diǎn)。其中之一是要求文件訪問和緩沖要對齊,,但NGINX很好地處理了這個(gè)問題,。但是,另一個(gè)缺點(diǎn)更糟糕,。異步接口要求文件描述符中要設(shè)置O_DIRECT標(biāo)記,,就是說任何對文件的訪問都將繞過內(nèi)存中的緩存,這增加了磁盤的負(fù)載,。在很多場景中,,這都絕對不是最佳選擇。 為了有針對性地解決這一問題,,在NGINX 1.7.11中引入了線程池,。默認(rèn)情況下,NGINX+還沒有包含線程池,,但是如果你想試試的話,,可以聯(lián)系銷售人員,NGINX+ R6是一個(gè)已經(jīng)啟用了線程池的構(gòu)建版本,。 現(xiàn)在,讓我們走進(jìn)線程池,,看看它是什么以及如何工作的,。 3. 線程池讓我們回到那個(gè)可憐的,要從大老遠(yuǎn)的倉庫去配貨的售貨員那兒,。這回,,他已經(jīng)變聰明了(或者也許是在一群憤怒的顧客教訓(xùn)了一番之后,他才變得聰明的,?),,雇用了一個(gè)配貨服務(wù)團(tuán)隊(duì)。現(xiàn)在,,當(dāng)任何人要買的東西在大老遠(yuǎn)的倉庫時(shí),,他不再親自去倉庫了,只需要將訂單丟給配貨服務(wù),,他們將處理訂單,,同時(shí),我們的售貨員依然可以繼續(xù)為其他顧客服務(wù),。因此,,只有那些要買倉庫里東西的顧客需要等待配貨,其他顧客可以得到即時(shí)服務(wù),。
對NGINX而言,,線程池執(zhí)行的就是配貨服務(wù)的功能,。它由一個(gè)任務(wù)隊(duì)列和一組處理這個(gè)隊(duì)列的線程組成。
那么,,這就像我們有了另外一個(gè)隊(duì)列。是這樣的,,但是在這個(gè)場景中,,隊(duì)列受限于特殊的資源。磁盤的讀取速度不能比磁盤產(chǎn)生數(shù)據(jù)的速度快,。不管怎么說,,至少現(xiàn)在磁盤不再延誤其他事件,只有訪問文件的請求需要等待,。 “從磁盤讀取”這個(gè)操作通常是阻塞操作最常見的示例,,但是實(shí)際上,NGINX中實(shí)現(xiàn)的線程池可用于處理任何不適合在主循環(huán)中執(zhí)行的任務(wù),。 目前,,卸載到線程池中執(zhí)行的兩個(gè)基本操作是大多數(shù)操作系統(tǒng)中的read()系統(tǒng)調(diào)用和Linux中的sendfile()。接下來,,我們將對線程池進(jìn)行測試(test)和基準(zhǔn)測試(benchmark),,在未來的版本中,如果有明顯的優(yōu)勢,,我們可能會(huì)卸載其他操作到線程池中,。 4. 基準(zhǔn)測試現(xiàn)在讓我們從理論過度到實(shí)踐。我們將進(jìn)行一次模擬基準(zhǔn)測試(synthetic benchmark),,模擬在阻塞操作和非阻塞操作的最差混合條件下,,使用線程池的效果。 另外,,我們需要一個(gè)內(nèi)存肯定放不下的數(shù)據(jù)集,。在一臺48GB內(nèi)存的機(jī)器上,我們已經(jīng)產(chǎn)生了每文件大小為4MB的隨機(jī)數(shù)據(jù),,總共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; } } } 如上所示,,為了達(dá)到更好的性能,我們調(diào)整了幾個(gè)參數(shù):禁用了logging和accept_mutex,,同時(shí),,啟用了sendfile并設(shè)置了sendfile_max_chunk的大小,。最后一個(gè)指令可以減少阻塞調(diào)用sendfile()所花費(fèi)的最長時(shí)間,因?yàn)镹GINX不會(huì)嘗試一次將整個(gè)文件發(fā)送出去,,而是每次發(fā)送大小為512KB的塊數(shù)據(jù),。 這臺測試服務(wù)器有2個(gè)Intel Xeon E5645處理器(共計(jì):12核、24超線程)和10-Gbps的網(wǎng)絡(luò)接口,。磁盤子系統(tǒng)是由4塊西部數(shù)據(jù)WD1003FBYX 磁盤組成的RAID10陣列,。所有這些硬件由Ubuntu服務(wù)器14.04.1 LTS供電。
客戶端有2臺服務(wù)器,,它們的規(guī)格相同,。在其中一臺上,在wrk中使用Lua腳本創(chuàng)建了負(fù)載程序,。腳本使用200個(gè)并行連接向服務(wù)器請求文件,,每個(gè)請求都可能未命中緩存而從磁盤阻塞讀取。我們將這種負(fù)載稱作隨機(jī)負(fù)載,。 在另一臺客戶端機(jī)器上,,我們將運(yùn)行wrk的另一個(gè)副本,使用50個(gè)并行連接多次請求同一個(gè)文件,。因?yàn)檫@個(gè)文件將被頻繁地訪問,,所以它會(huì)一直駐留在內(nèi)存中。在正常情況下,,NGINX能夠非??焖俚胤?wù)這些請求,但是如果工作進(jìn)程被其他請求阻塞的話,,性能將會(huì)下降。我們將這種負(fù)載稱作恒定負(fù)載,。 性能將由服務(wù)器上ifstat監(jiān)測的吞吐率(throughput)和從第二臺客戶端獲取的wrk結(jié)果來度量,。 現(xiàn)在,沒有使用線程池的第一次運(yùn)行將不會(huì)帶給我們非常振奮的結(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 如上所示,,使用這種配置,,服務(wù)器產(chǎn)生的總流量約為1Gbps。從下面所示的top輸出,,我們可以看到,,工作進(jìn)程的大部分時(shí)間花在阻塞I/O上(它們處于top的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í)間里是空閑的,。從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 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 請記住,文件是從內(nèi)存送達(dá)的,!第一個(gè)客戶端的200個(gè)連接創(chuàng)建的隨機(jī)負(fù)載,,使服務(wù)器端的全部的工作進(jìn)程忙于從磁盤讀取文件,,因此產(chǎn)生了過大的延遲,并且無法在合理的時(shí)間內(nèi)處理我們的請求,。 現(xiàn)在,,我們的線程池要登場了。為此,,我們只需在location塊中添加aio threads指令: location / { root /storage; aio threads; } 接著,,執(zhí)行NGINX reload重新加載配置。 然后,,我們重復(fù)上述的測試: % 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.5Gbps,相比之下,,沒有使用線程池時(shí)只有約1Gbps,! 理論上還可以產(chǎn)生更多的流量,但是這已經(jīng)達(dá)到了機(jī)器的最大網(wǎng)絡(luò)吞吐能力,,所以在這次NGINX的測試中,,NGINX受限于網(wǎng)絡(luò)接口。工作進(jìn)程的大部分時(shí)間只是休眠和等待新的時(shí)間(它們處于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 如上所示,,基準(zhǔ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倍),每秒請求處理數(shù)提升了31倍(250 vs 8),! 對此,,我們的解釋是請求不再因?yàn)楣ぷ鬟M(jìn)程被阻塞在讀文件,而滯留在事件隊(duì)列中,,等待處理,,它們可以被空閑的進(jìn)程處理掉。只要磁盤子系統(tǒng)能做到最好,,就能服務(wù)好第一個(gè)客戶端上的隨機(jī)負(fù)載,,NGINX可以使用剩余的CPU資源和網(wǎng)絡(luò)容量,從內(nèi)存中讀取,,以服務(wù)于上述的第二個(gè)客戶端的請求,。 5. 依然沒有銀彈在拋出我們對阻塞操作的擔(dān)憂并給出一些令人振奮的結(jié)果后,可能大部分人已經(jīng)打算在你的服務(wù)器上配置線程池了,。先別著急,。 實(shí)際上,最幸運(yùn)的情況是,,讀取和發(fā)送文件操作不去處理緩慢的硬盤驅(qū)動(dòng)器,。如果我們有足夠多的內(nèi)存來存儲數(shù)據(jù)集,那么操作系統(tǒng)將會(huì)足夠聰明地在被稱作“頁面緩存”的地方,緩存頻繁使用的文件,。 “頁面緩存”的效果很好,,可以讓NGINX在幾乎所有常見的用例中展示優(yōu)異的性能。從頁面緩存中讀取比較快,,沒有人會(huì)說這種操作是“阻塞”,。而另一方面,卸載任務(wù)到一個(gè)線程池是有一定開銷的,。 因此,,如果內(nèi)存有合理的大小并且待處理的數(shù)據(jù)集不是很大的話,那么無需使用線程池,,NGINX已經(jīng)工作在最優(yōu)化的方式下,。 卸載讀操作到線程池是一種適用于非常特殊任務(wù)的技術(shù)。只有當(dāng)經(jīng)常請求的內(nèi)容的大小,,不適合操作系統(tǒng)的虛擬機(jī)緩存時(shí),,這種技術(shù)才是最有用的。至于可能適用的場景,,比如,,基于NGINX的高負(fù)載流媒體服務(wù)器。這正是我們已經(jīng)模擬的基準(zhǔn)測試的場景,。 我們?nèi)绻梢愿倪M(jìn)卸載讀操作到線程池,,將會(huì)非常有意義。我們只需要知道所需的文件數(shù)據(jù)是否在內(nèi)存中,,只有不在內(nèi)存中時(shí),,讀操作才應(yīng)該卸載到一個(gè)單獨(dú)的線程中。 再回到售貨員那個(gè)比喻的場景中,,這回,,售貨員不知道要買的商品是否在店里,他必須要么總是將所有的訂單提交給配貨服務(wù),,要么總是親自處理它們,。 人艱不拆,操作系統(tǒng)缺少這樣的功能,。第一次嘗試是在2010年,人們試圖將這一功能添加到Linux作為fincore()系統(tǒng)調(diào)用,,但是沒有成功,。后來還有一些嘗試,是使用RWF_NONBLOCK標(biāo)記作為preadv2()系統(tǒng)調(diào)用來實(shí)現(xiàn)這一功能(詳情見LWN.net上的非阻塞緩沖文件讀取操作和異步緩沖讀操作),。但所有這些補(bǔ)丁的命運(yùn)目前還不明朗,。悲催的是,這些補(bǔ)丁尚沒有被內(nèi)核接受的主要原因,貌似是因?yàn)闀缛粘志玫乃罕拼髴?zhàn)(bikeshedding),。 另一方面,,F(xiàn)reeBSD的用戶完全不必?fù)?dān)心。FreeBSD已經(jīng)具備足夠好的讀文件取異步接口,,我們應(yīng)該用這個(gè)接口而不是線程池,。 6. 配置線程池所以,如果你確信在你的場景中使用線程池可以帶來好處,,那么現(xiàn)在是時(shí)候深入了解線程池的配置了,。 線程池的配置非常簡單、靈活,。首先,,獲取NGINX 1.7.11或更高版本的源代碼,使用--with-threads配置參數(shù)編譯,。在最簡單的場景中,,配置看起來很樸實(shí)。我們只需要在http,、 server,,或者location上下文中包含aio threads指令即可: aio threads; 這是線程池的最簡配置。實(shí)際上的精簡版本示例如下: thread_pool default threads=32 max_queue=65536; aio threads=default; 這里定義了一個(gè)名為“default”,,包含32個(gè)線程,,任務(wù)隊(duì)列最多支持65536個(gè)請求的線程池。如果任務(wù)隊(duì)列過載,,NGINX將輸出如下錯(cuò)誤日志并拒絕請求: thread pool "NAME" queue overflow: N tasks waiting 錯(cuò)誤輸出意味著線程處理作業(yè)的速度有可能低于任務(wù)入隊(duì)的速度了,。你可以嘗試增加隊(duì)列的最大值,但是如果這無濟(jì)于事,,那么這說明你的系統(tǒng)沒有能力處理如此多的請求了,。 正如你已經(jīng)注意到的,你可以使用thread_pool指令,,配置線程的數(shù)量,、隊(duì)列的最大值,以及線程池的名稱,。最后要說明的是,,可以配置多個(gè)獨(dú)立的線程池,將它們置于不同的配置文件中,,用做不同的目的: 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參數(shù)的值,,默認(rèn)使用的值是65536。如上所示,,可以設(shè)置max_queue為0,。在這種情況下,,線程池將使用配置中全部數(shù)量的線程,盡可能地同時(shí)處理多個(gè)任務(wù),;隊(duì)列中不會(huì)有等待的任務(wù),。 現(xiàn)在,假設(shè)我們有一臺服務(wù)器,,掛了3塊硬盤,,我們希望把該服務(wù)器用作“緩存代理”,緩存后端服務(wù)器的全部響應(yīng)信息,。預(yù)期的緩存數(shù)據(jù)量遠(yuǎn)大于可用的內(nèi)存,。它實(shí)際上是我們個(gè)人CDN的一個(gè)緩存節(jié)點(diǎn)。毫無疑問,,在這種情況下,,最重要的事情是發(fā)揮硬盤的最大性能。 我們的選擇之一是配置一個(gè)RAID陣列,。這種方法毀譽(yù)參半,,現(xiàn)在,有了NGINX,,我們可以有其他的選擇: 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: proxy_cache_key $request_uri; proxy_cache cache_$disk; aio threads=pool_$disk; sendfile on; } 在這份配置中,,使用了3個(gè)獨(dú)立的緩存,每個(gè)緩存專用一塊硬盤,,另外,,3個(gè)獨(dú)立的線程池也各自專用一塊硬盤。 緩存之間(其結(jié)果就是磁盤之間)的負(fù)載均衡使用split_clients模塊,,split_clients非常適用于這個(gè)任務(wù),。 在 proxy_cache_path指令中設(shè)置use_temp_path=off,表示NGINX會(huì)將臨時(shí)文件保存在緩存數(shù)據(jù)的同一目錄中,。這是為了避免在更新緩存時(shí),,磁盤之間互相復(fù)制響應(yīng)數(shù)據(jù)。 這些調(diào)優(yōu)將帶給我們磁盤子系統(tǒng)的最大性能,,因?yàn)镹GINX通過單獨(dú)的線程池并行且獨(dú)立地與每塊磁盤交互,。每塊磁盤由16個(gè)獨(dú)立線程和讀取和發(fā)送文件專用任務(wù)隊(duì)列提供服務(wù)。 我敢打賭,,你的客戶喜歡這種量身定制的方法,。請確保你的磁盤也持有同樣的觀點(diǎn)。 這個(gè)示例很好地證明了NGINX可以為硬件專門調(diào)優(yōu)的靈活性,。這就像你給NGINX下了一道命令,,讓機(jī)器和數(shù)據(jù)用最佳姿勢來搞基。而且,,通過NGINX在用戶空間中細(xì)粒度的調(diào)優(yōu),我們可以確保軟件、操作系統(tǒng)和硬件工作在最優(yōu)模式下,,盡可能有效地利用系統(tǒng)資源,。 7. 總結(jié)綜上所述,線程池是一個(gè)偉大的功能,,將NGINX推向了新的性能水平,,除掉了一個(gè)眾所周知的長期危害——阻塞——尤其是當(dāng)我們真正面對大量內(nèi)容的時(shí)候。 甚至,,還有更多的驚喜,。正如前面提到的,這個(gè)全新的接口,,有可能沒有任何性能損失地卸載任何長期阻塞操作,。NGINX在擁有大量的新模塊和新功能方面,開辟了一方新天地,。許多流行的庫仍然沒有提供異步非阻塞接口,,此前,這使得它們無法與NGINX兼容,。我們可以花大量的時(shí)間和資源,,去開發(fā)我們自己的無阻塞原型庫,但這么做始終都是值得的嗎,?現(xiàn)在,,有了線程池,我們可以相對容易地使用這些庫,,而不會(huì)影響這些模塊的性能,。 |
|