很早以前就有看nginx的衝動,可是一直被一些事耽擱着,最近在繁忙之中,抽出點時間,看了下Nginx代碼,發現總體上並非很難看懂,並且恰好想學習nginx+lua開發。python
nginx 在互聯網公司使用很廣,最重要的功能當屬反向代理和負載均衡了吧,固然還有緩存。因此有必要對 nginx 熟悉使用和深刻了解。linux
記得我以前在不少文章有提到,後臺組件框架主要有三種:redis單進程單線程,memcache單進程多線程,nginx多進程;等看了nginx以後,我也算集齊了。nginx
nginx以模塊化方式開發,好比核心模塊,event模塊,http模塊,而後爲了支持多平臺,event模塊下又有對各大平臺的封裝支持,例如linux平臺epoll,mac平臺kqueue等等;而後http模塊也被拆分紅了不少子模塊。golang
這篇文章算是我本身作的筆記吧,把以前研究的東西記錄下。web
也許是以前看過redis 和 golang 以及 python的 http 框架,nginx總體框架比較容易就看懂了,固然不少細節還需後面慢慢看。redis
這篇文章主要介紹 nginx 是如何開啓,以及請求是怎麼執行的,因此這篇文章主要就是如下兩點:數組
nginx體量很大,想要在較短期內看完全部代碼很難,並且我看得時間也不是不少,因此,這裏主要站在宏觀角度,對nginx作個總體剖析。緩存
其實若是直接從main函數直接開始看,其實也是能夠看懂大部分,可是 nginx 回調函數太多了,看着看着,忽然跑出一個回調函數,常常就懵逼了。服務器
所以,就須要用gdb來定點調試;多線程
要使用gdb,首先須要在gcc編譯時,加入-g選項,能夠以下操做:
生成可執行文件nginx以後,直接在終端運行便可,nginx會加載默認配置文件,以daemon形式運行;
nginx運行以後,便可經過gdb來調試;
按以下命令開啓gdb
而後,經過pidof命令獲取nginx進程號,便可attach,以下:
nginx默認開啓一個master進程和一個worker進程,所以上述命令會返回兩個進程號,在我主機上8125和8126,較小是master進程,較大的是worker進程;接下來,先看下master進程,
這樣就能夠直接調試nginx的worker進程,用命令bt能夠查看master進程的函數棧
nginx開啓以後,首先啓動的就是master進程,從main函數開始,
所以,master進程任務就是開啓子進程,而後管理子進程;怎麼管理了?
信號,對,就是信號;當master進程收到一個信號以後,就把這個信號傳遞給worker進程,worker進程進而根據不一樣信號分別處理。
那麼問題又來了,master進程是如何把信號傳遞給worker進程的?
管道,對,就是管道。原理和memcache的master線程和worker線程通訊機制同樣,即每一個worker進程有兩個文件描述符fd[0]和fd[1],一個讀端,一個寫端;
worker進程將讀端加入epoll事件監聽,當master進程收到一個信號後,在每一個worker進程寫端寫入一個flag,而後worker進程觸發讀事件,讀取flag,並根據flag作相應操做。
所以nginx接收客戶端請求以及處理客戶端請求,主要是在worker進程,咱們來看下,worker進程函數棧
由於 worker 進程是由 master 進程 fork 出來,所以 worker 進程包含 master 進程的函數棧;咱們直接從#5函數開始看,
子進程調用經過參數傳遞進來的回調函數ngx_worker_process_cycle正式切入子進程部分,父進程則接着設置worker進程相關屬性;
而後在一個無限循環中,函數ngx_worker_process_cycle接着調用ngx_process_events_and_timers,開啓事件監聽循環;
而後調用ngx_process_events函數,這個函數也就是ngx_epoll_process_events函數,開啓開啓事件監聽;
ok,worker 進程此時已就緒,等待客戶端鏈接以及請求數據。
爲了不驚羣現象以及實現worker進程負載均衡,每次有客戶端鏈接時,全部worker進程會先爭搶鎖,若是某個worker進程獲取到鎖,便可執行接收客戶端和客戶端請求事件;
若是worker進程沒有爭搶到鎖,只執行客戶端請求事件。
當nginx的master進程和worker進程開啓以後,客戶端便可發送請求;接下來,就看看nginx是如何處理請求的;
當客戶端發送請求以後,首先是經過tcp三次握手創建鏈接;當鏈接創建成功以後,即執行listenfd的回調函數,可是listenfd的回調函數是哪一個了?這對於新手來講,實際上是很難發現listenfd回調函數。
下面分析下:
像listenfd的回調函數以及模塊間是如何拼湊在一塊兒,這些幾乎都是在模塊初始化時完成的。對於listenfd的回調函數便是在event模塊初始化時或者調用event模塊一些設置函數時設置;
客戶端鏈接上服務器以後,服務器收到請求以後的回調函數也是在http模塊初始化時或者調用模http模塊一些設置函數時設置的。
在event模塊初始化時,調用的是ngx_event_process_init函數,下面列出這個函數最重要的代碼:
在for循環中,迭代每一個監聽套接字,recv爲listenfd鏈接對象的讀事件,這裏設置listenfd讀事件的回調函數爲ngx_event_accept函數,而後將每一個listenfd添加到事件監聽中,並設置爲可讀事件。
ok,當咱們去看ngx_add_conn和ngx_add_event的定義時,以下:
說明 ngx_add_conn 和 ngx_add_event 都是結構體 ngx_event_actions 結構體中設置的函數指針;
其實這個ngx_event_actions就是nginx跨平臺的關鍵,由於不一樣平臺使用的事件監聽器是不同的,致使ngx_event_actions也就不同。
例如linux使用的是epoll,所以ngx_event_actions結構體就是在epoll模塊加載時設置,在上述代碼前半部分。咱們來看下epoll模塊actions.init函數:
從代碼能夠看出,ngx_event_actions被設置爲ngx_epoll_module_ctx.actions,接着看下這個結構體:
所以,當調用ngx_add_conn和ngx_add_event時,分別調用的是ngx_epoll_add_connection和ngx_epoll_add_event;
如此一來,若是此時是mac平臺,那麼使用的事件監聽器是kqueue,那麼當調用ngx_add_event時,調用的就是ngx_kqueue_add_event。
若是使用的poll監聽器,那麼調用將是ngx_poll_add_event等等。
接下來,再分析一個很重要的回調函數,即客戶端連上客戶端以後,發送請求時的回調函數,先來看下,listenfd回調函數
當客戶端鏈接服務器時,首先listenfd回調函數先是調用accept函數接收客戶端請求,而後從對象池中獲取一個封裝客戶端socket鏈接對象。
若是目前使用的是epoll事件監聽器,則調用ngx_add_conn(c)放入事件監聽,最後調用ngx_listening_t的回調函數,對客戶端鏈接進一步操做;
ok,這個ls->handler(c)是個啥?我在第一次看代碼時,一臉懵逼!!!
還記得以前說的嗎?模塊之間的銜接,幾乎都是在模塊初始化或者調用模塊一些設置函數時設置的,所以接下來,就來看看http模塊初始化時作了什麼。
http模塊並無在模塊初始化函數中設置 ls->handler(c),而是在當讀取到」http」命令時,執行命令函數 ngx_http_block 中設置;
真是藏的夠深,經歷了四個函數,終於看到ls-handler設置函數了,即爲ngx_http_init_connection函數,而這個函數在http模塊,爲客戶端http請求處理的入口函數;
到此爲止,咱們能夠知道服務器在接收到客戶端以後,首先將客戶端封裝成ngx_connection_t結構體,而後交給http模塊執行http請求。
nginx處理http的請求是nginx最重要的職能,也是最複雜的一部分。能夠大概說下執行流程:
nginx把請求處理劃分紅了11個階段,也就是說當nginx讀取了請求行和請求頭以後,將請求封裝告終構體ngx_http_request_t,而後每一個階段的handler都會根據這個ngx_http_request_t,對請求進行處理,例如重寫uri,權限控制,路徑查找,生成內容以及記錄日誌等等;
多階段處理是nginx模塊最重要的部分,由於第三方模塊也是註冊在這;例若有人寫了一個利用nginx和memcache作頁面緩存的第三方模塊,也能夠把memcache換成redis集羣等等;
並且nginx多階段處理有點相似python和golang web框架的中間件,後者主要是用裝飾器模式,對handler一層一層封裝,而nginx是用數組(鏈表)形式組合多階段handler,而後按handler鏈表執行便可;
由於多階段這塊內容還沒徹底看懂,因此跟着網上教程,寫了個最簡單的第三方模塊,用於設置定點調試,觀察http階段函數執行過程,步驟以下:
而後一樣是在foo目錄下新建一個配置文件config
這樣,一個最簡單的第三方模塊就編寫完成。
上述兩個函數很好理解,一個是初始化函數,將這個模塊的 handler 註冊到某個階段中。
這個例子是在階段NGX_HTTP_CONTENT_PHASE,而後當程序執行到上述階段時,便可執行foo模塊;最後從新編譯生成可執行文件便可。
接下來,利用gdb來看下http執行過程,把定點設置在
簡要說明下上述函數,我閱讀的版本和運行版本不同,所以上述僅供參考:
所以上述while循環中,迭代全部http模塊handler,而後在handler函數中根據請求結構體ngx_http_request_t作出相應的處理;
上述gdb調試結果,能夠看出NGX_HTTP_CONTENT_PHASE 階段的 checker函數爲 ngx_http_core_content_phase,而後再在這個 checker 函數內部執行foo 模塊的 handler(ngx_http_foo_handler)。
等到多階段處理結束以後,最後再將 response 返回給客戶端。
這篇文章主要就是宏觀分析下nginx總體運行流程,由於第一次看nginx時,有不少看不懂的地方,因此這篇文章也算是作筆記吧。後續還需認真看多階段處理,由於第三方開發模塊也是註冊在多階段過程,以及熟悉ngx+lua模塊開發。
本文連接: http://luodw.cc/2017/03/17/ng...