Author:
bugall
Wechat:bugallF
Email:769088641@qq.com
Github: https://github.com/bugalllinux
衆所周知Node是單線程異步,其實這個是相對於Node這層來講是沒有問題的。可是若是總體來看其實仍是有個thread_pool的概念,我更喜歡把Node看作一個膠水層,把libuv與v8粘合在一塊兒。v8做爲js執行的引擎,libuv封裝了一些c++代碼來實現一些內核調用,同時把同類功能接口作抽象,知足跨平臺的需求。我以爲寫Node有兩條主線,一個是對js語法的使用和理解好比[1]==[1] //false
, 另一條主線就是對libuv的理解,由於libuv中的event_loop實現的機制,也是Node在某些場景性能出色的緣由。c++
一般咱們若是在建立一個socket請求的時候,內核會爲這個socket建立一個標示,咱們一般稱爲文件描述符(file descriptor),文件描述符實際上是一個索引值,索引值對應的實際存儲是一個關於文件的一些元數據的數據結構,好比這個文件的操做權限,建立時間,文件是否可讀可寫等,總而言之這個結構裏存儲了一個文件的全部源信息(metadata)。假如咱們如今有10個socket請求進來,首先咱們內核要分配10個文件描述符,那這些被建立的文件描述符必定要有個地方存儲才行,在linux有一個文件描述符表是用來管理這些文件描述符信息的,爲了方便理解咱們就把這個文件描述符表想象成一個數組(這裏須要補充說明下,一般狀況硬件相對於內核來講是異步的),這時候咱們想知道哪些socket有數據到達?比較笨的方法就是枚舉每一個文件描述符對應的源數據,而後查看他們對應狀態,若是可讀咱們就能夠從對應的buffer中讀取數據,可是這有一個問題,假如咱們的socket請求線性增加,那麼遍歷一次數組的時間也會跟着線性增加,這種就是poll
,select
的實現。後來爲了解決這個問題有了epoll
git
這裏補充一個點,epoll的IO多路複用與iocp的IO多路複用雖然功能看似相同,可是底層的實現原理大相徑庭,epoll之因此高效要歸功於linux內核的基於事件的實現機制,好比當網卡在加載數據的時候是不會佔用cpu的,由於硬件對於內核實現來講是異步的,當網卡在加載完數據後會發送一個中斷
給內核,假如你表明一個線程,你在看電視的時候,你媽媽在廚房作飯,當飯作好的時候媽媽會說:「飯好了吃飯了」。那你如今能夠選擇是過去吃飯仍是繼續看電視,至少飯作好了這個事件你是知道的,等你看完電視的時候你會想到還有吃飯這個事情沒完成。對於咱們的應用來講可能會有不少個事件會在未來的某個時間發生,咱們須要把這些事件存儲起來,一個一個處理。而windows的內核實現並非基於事件的因此iocp採用的是多線程輪訓的方式實現的IO多路複用,因此這也是爲何不建議用windows跑Node服務。github
epoll中主要有三個方法epoll_create
,epoll_ctl
,epoll_wait
. create就是建立一個epoll實例,ctl就是準備讓epoll監聽哪一個文件描述符上的哪些事件,wait獲取被激活的事件。按照上面的例子來理解:create就至關於我如今要準備看電視了,準備好本子記錄未來要發生時的事情和未處理的事情,ctl就是在本子上記錄:當媽媽在作飯的時候,若是媽媽作好了這個事件發生請告訴我,wait就是看下哪些事件被激活,看看媽媽飯有沒有作好。epoll_wait中存儲的那個被激活的事件列表就是咱們常說的event_pool
docker
上面說了,在libuv中文件操做和DNS解析都是用線程池實現的,接下來咱們看下緣由windows
剛開始我也很鬱悶這個問題,也在stackoverflow上發了帖子,自己是想刨根問底的從技術實現找到答案的,可是這個沒有明確的答案和文檔。我本身的想法就是: 其實磁盤IO大多數時間是花在磁盤尋道上,一旦開始讀取數據讀寫量會很大,不像socket網絡延遲高,每次收到的IP分段後的數據包小,當buffer被寫滿後纔會通知內核處理,因此socket是能夠用epoll來實現的。可是文件一旦開始讀取,那麼buffer瞬間會被寫滿,epoll中的被激活的事件列表中一直會有這個文件可讀這個事件。若是是一個很是大的文件的話會形成epoll的event_pool阻塞。數組
一般咱們在發送http請求的時候首先須要對host進行解析獲取域名對應的ip的地址,這樣咱們才能真正的發送請求。因此說這個過程必定是個同步的過程,由於在DNS解析沒有成功的時候發送http請求是沒有意義的。在http中默認調用的是dns.lookup方法。這個方法會去讀取/etc/hosts
這個文件,我想而後查本地的DNS緩存,若是都沒有而後再發請求向DNS解析服務器查詢。這個查詢有個超時時間,若是dns解析失敗那麼這個請求也就失敗了。緩存
由於我負責的項目天天有3000w的外網http請求和3億次的文件讀寫(用Node寫我也是醉了)。天天凌晨docker重啓容器的時候會形成本地緩存的DNS失效,而後瞬間大量的外網http請求須要走DNS解析,Node默認設置libuv的thread_pool是3,結果在重啓完成的幾分鐘內會有不少報錯。後來我把線程調整到128明顯好不少。服務器
這個問題我如今也沒有明確的認知和測試結果。後期我會盡快完成。網絡
能夠確定的是,多進程對epoll沒有影響。
對thread_poll更沒有多大影響,由於使用thread_poll的場景是文件操做與DNS解析這兩個
由於文件操做的性能取決於磁盤性能,就算你有16核16個進程,當磁盤到達性能閾值值的時候再多核也沒有意義。
惟一能提高性能的地方就是主線程。也就是v8執行這裏,由於咱們從event_loop中獲得的被激活的事件是爲了作一些事件對應的邏輯也就是咱們所說的callback,咱們會把這些被激活的事件對應的callback放到一個叫作任務隊列的地方中去,主線程處理的就是任務隊列的東西,假如咱們把一個任務隊列須要處理的code分散到4個進程和4個任務隊列中去性能是有提升的。