一、問題
一般情況下,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標識,這意味著任何對這個文件的訪問會跳過緩存直接訪問磁盤上的文件。在大多數情況下,這不是訪問文件的最佳方法。
二、線程池
為了解決這個問題,Nginx 1.7.11 引入了線程池概念。現在讓我們瞭解一下線程池是怎樣工作的。
在nginx中,線程池執行的是分發服務,他由一個任務隊列和一些執行任務的線程組成。當一個工作線程在執行一個可能會存在潛在長時間操作的任務時,這個任務會被”卸下“並重新放到任務隊列中去,這個被”卸下“的任務可能會被其他線程再執行。
現在,只有2個基礎操作會造成“卸下任務”到任務隊列:
- 在大多操作系統上的read()系統調用
- linux系統的sendfile()
如果這個機制被證實是有益於nginx的,我們以後還會添加其他的操作。
三、線程池並非靈丹妙藥
大多數讀寫文件操作都需要通過緩慢的磁盤。如果有充足的內存來存儲數據,那麼操作系統會緩存頻繁使用的文件,也就是“頁面緩存”(page cache)機制。
由於頁面緩存機制,nginx幾乎在所有情況下都能體現非常好的性能。通過頁面緩存讀取數據非常快,並且不會阻塞。另一方面,卸下任務到任務池是有瓶頸的。所以在內存充足並且使用的數據不是非常大的時候,nginx即使不使用線程池也是幾乎工作在最佳狀態。
卸下寫操作到任務池中,是一個適用於特殊場景的處理方案,適用於大量無法使用VM緩存的請求操作。例如一個高負荷的基於Nginx的視頻流服務器。另外FreeBSD的用戶不需要擔心這些,因為FreeBSD已經有很好的異步讀操作接口,無需使用線程池。
配置線程池
Nginx是由Igor Sysoev為俄羅斯的Rambler.ru站點開發的一個高性能的HTTP和反向代理服務器,也是現在中國互聯網公司使用多的代理軟件,利用Nginx與各個模塊的整合可以實現高效的WEB處理能力
要想合理的配置線程池,就必須首先分析任務特性,可以從以下幾個角度來進行分析:
1.任務的性質:
CPU密集型任務,IO密集型任務和混合型任務。
2.任務的優先級:
高,中和低。
3.任務的執行時間:
長,中和短。
4.任務的依賴性:
是否依賴其他系統資源,如數據庫連接。
任務性質不同的任務可以用不同規模的線程池分開處理。CPU密集型任務配置儘可能少的線程數量,如配置Ncpu+1個線程的線程池。IO密集型任務則由於需要等待IO操作,線程並不是一直在執行任務,則配置儘可能多的線程,如2*Ncpu。混合型的任務,如果可以拆分,則將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐率要高於串行執行的吞吐率,如果這兩個任務執行時間相差太大,則沒必要進行分解。我們可以通過Runtime.getRuntime().availableProcessors()方法獲得當前設備的CPU個數。
優先級不同的任務可以使用優先級隊列PriorityBlockingQueue來處理。它可以讓優先級高的任務先得到執行,需要注意的是如果一直有優先級高的任務提交到隊列裡,那麼優先級低的任務可能永遠不能執行。
執行時間不同的任務可以交給不同規模的線程池來處理,或者也可以使用優先級隊列,讓執行時間短的任務先執行。
依賴數據庫連接池的任務,因為線程提交SQL後需要等待數據庫返回結果,如果等待的時間越長CPU空閒時間就越長,那麼線程數應該設置越大,這樣才能更好的利用CPU。
一般總結哦,有其他更好的方式,希望各位留言,謝謝。
CPU密集型時,任務可以少配置線程數,大概和機器的cpu核數相當,這樣可以使得每個線程都在執行任務
IO密集型時,大部分線程都阻塞,故需要多配置線程數,2*cpu核數
操作系統之名稱解釋:
某些進程花費了絕大多數時間在計算上,而其他則在等待I/O上花費了大多是時間,
前者稱為計算密集型(CPU密集型)computer-bound,後者稱為I/O密集型,I/O-bound。
結論
線程池機制是一個非常好的機制,通過解決大量數據情況下導致的阻塞問題,使得nginx的性能達到一個新的高度。如之前提到的,接下來會有新的接口可能會實現在不損耗性能的情況下實現”卸下“任務機制。
資料推薦;內容包括:C/C++,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,嵌入式 等。。。後臺私信;資料;兩個字可以免費領取
閱讀更多 編程資料庫 的文章