眾所周知nginx使用異步,事件驅動方法處理連接。這意味著nginx使用一個worker進程處理多個連接和請求,而不是每一個請求有一個專門的進程或著線程處理(像傳統架構的服務器那樣,例如apache)。為了實現這個目的,nginx使用非阻塞模式的socket和高效的方法epoll和kqueue。
因為高負荷進程的數量少且相對不變(通常1個cpu核心配1個進程),它內存消耗少,cpu時間沒有浪費在任務切換上。這種處理請求的方式的優勢也因為nginx而被大家所熟知。nginx能夠成功處理數百萬并發請求同時擴展性非常好。
不過異步,事件驅動方法還是有一個問題。或者像我想的那樣有一個“敵人”。這個敵人的名字是:阻塞。不幸的是,許多第三方的模塊使用阻塞調用,同時用戶(甚至作為模塊的開發者)也沒意識到缺點。阻塞操作能毀掉nginx的性能,需要不惜一切代價避免阻塞。
甚至當前nginx的官方代碼中也沒有完全避免阻塞操作在所有的例子中,為了解決這個問題,新的“thread pools”機制在nginx1.7.11中被實現了。線程池是什么以及它如何使用,一會我們就講到。
問題
首先,為了更好的理解問題,再簡單介紹一下nginx的原理。
一般,nginx是一個事件handler,一個controller接收來自內核的信息關于所有連接上發生的事件和給操作系統發送命令告訴它做什么。事實上,nginx做了所有困難的工作通過組織操作系統,而操作系統只做讀和寫字節的常規工作。所以對于nginx來說快速響應式非常重要的。
事件會超時,sockets的讀和寫通知,發生錯誤的通知。nginx收到許多事件然后一個一個處理它們。因此所有事情在一個簡單的循環在一個隊列上通過一個線程處理。nginx從隊列上取出一個消息事件然后做出反應通過寫或讀一個socket。在大多數情況下,這是相當快的(或許只需要幾個cpu時鐘去拷貝數據到內存)同時nginx繼續立刻處理隊列里的其他事件。
但是如果某個長和重的操作出現的時候會發生什么呢?整個事件處理循環將會被卡住等待這個操作的完成。
所以,一個阻塞的操作是指任何能夠停止事件處理循環許多時間的操作。操作被阻塞有很多原因。例如,nginx可能忙于處理長時的cpu密集操作,或者需要等待訪問一個資源(如一個硬盤,一個鎖,一個同步方式的方法調用返回訪問數據庫),主要的問題是當處理這些操作的時候,nginx的worker進程不能夠做其它的事情,也不能處理事件,即使有許多系統資源仍然可用,那些隊列里的事件可以用這些資源來處理。
想象一個商店的銷售面前排了很長的隊伍,隊首的人想要一個不在商店但在倉庫里的東西。銷售人員去庫房取東西。那么整個隊列會等幾個小時來取這個東西,隊列里的所有人都會不高興。你能想象這些人的反應嗎?隊列每個人的等待時間都增加了幾個小時,但是它們需要的東西就在店里,很快就能完成購買。
幾乎會出現同樣的事情當nginx想要讀一個不在內存中緩存的大文件,需要從磁盤中讀取的時候,硬盤很慢,然后隊列中等待的其它請求并不一定需要訪問硬盤,但他們被強制等待。結果等待時間增加,系統資源沒有完全使用。
某些操作系統提供了異步的接口來發送文件,nginx可以使用這些接口。一個好的例子是FreeBSD。不幸的是,linux不能這樣。雖然linux提供了異步接口來讀文件,它有許多缺點。第一就是訪問文件和緩存的對齊需求,nginx能夠處理它。第二個就更糟糕一些,異步接口需要O_DIRECT標識被設置在文件描述符上,這意味著任何對文件的訪問將會越過內存中的緩存,會增加磁盤的負載。所以并不是最優的在許多場景下。
為了解決這個問題,線程池在nginx1.7.11中被引進,它默認沒有被加入到nginx plus中。
下面介紹一下線程池時什么和它時如何工作的。
待續。。。
原文
http://nginx.com/blog/thread-pools-boost-performance-9x/