瞭解 Nginx 基本概念

瞭解 Nginx 基本概念

歡迎來我的部落格閱讀:「瞭解 Nginx 基本概念」

前言

本篇是我學習Nginx的一些筆記,主要內容講述了一些瞭解Nginx需要的基本概念。
然後探討一下Nginx的模組化的組織架構,以及各個模組的分類、工作方式、職責和提供的相關指令。
主要達到以下目的:

瞭解Nginx的大概執行原理

瞭解Nginx的基本概念

知道怎麼看官方文件。

關於Nginx

Nginx是一款面向效能設計的HTTP伺服器,能反向代理HTTP,HTTPS和郵件相關(SMTP,POP3,IMAP)的協議連結。並且提供了負載均衡以及HTTP快取。
它的設計充分使用非同步事件模型,削減上下文排程的開銷,提高伺服器併發能力。
採用了模組化設計,提供了豐富模組的第三方模組。
所以關於Nginx,有這些標籤:「非同步」「事件」「模組化」「高效能」「高併發」「反向代理」「負載均衡」

基本概念

程序模型

Nginx的程序是使用經典的「Master-Worker」模型。
Nginx在啟動後,會有一個master程序和多個worker程序。
master程序主要用來管理worker程序,包含:接收來自外界的訊號,向各worker程序傳送訊號,監控worker程序的執行狀態,當worker程序退出後(異常情況下),會自動重新啟動新的worker程序。
worker程序主要處理基本的網路事件,多個worker程序之間是對等的,他們同等競爭來自客戶端的請求,各程序互相之間是獨立的。需要注意的是,每個Worker只有主執行緒,即所謂的「單執行緒」。
一個請求,只可能在一個worker程序中處理,一個worker程序,不可能處理其它程序的請求。
worker程序的個數是可以設定的,一般會設定與機器cpu核數一致,這裡面的原因與nginx的程序模型以及事件處理模型是分不開的。nginx為了更好的利用多核特性,提供了cpu親緣性的繫結選項,我們可以將某一個程序繫結在某一個核上,這樣就不會因為程序的切換帶來cache的失效。更多的worker數,只會導致程序來競爭cpu資源。

事件模型

Nginx對於事件,以「非同步非阻塞」方式來實現。
非同步和非非同步,阻塞和非阻塞是兩組不同的概念,前者更多對於應用程式而言,而後者更多對於CPU來說:

非同步:執行一個動作之後,可以去操作別的操作,然後等待通知再回來執行剛才沒執行完的操作。

非非同步(同步):執行一個操作之後,等待結果,然後才繼續執行下面的操作。

阻塞:給CPU傳達任務之後,一直等待CPU處理完畢(即使會產生I/O),然後才執行下面操作。

非阻塞:給CPU傳達任務之後,繼續處理後面的操作,隔段時間再來詢問之前的操作是否完成。這樣的及過程也叫「輪詢」

Nginx的「非同步非阻塞」方式,具體到系統呼叫的話,就是像select/poll/epoll/kqueue這樣的系統呼叫。它們提供了一種機制,讓你可以同時監控多個事件,呼叫他們是阻塞的,但可以設定超時時間,在超時時間之內,如果有事件準備好了,就返回。

epoll是在Linux上關於事件的實現,而kqueue是OpenBSD或FreeBSD作業系統上採用類似epoll的事件模型。
所以重點講解一下epoll的模型:

該方案給是Linux下效率最高的I/O事件通知機制,在進入輪詢的時候如果沒有檢查到I/O事件,將會進入休眠,直到事件將它喚醒。它是真實利用了事件通知、執行回撥的方式,而不是遍歷查詢,所以不會浪費CPU,執行效率較高。

反向代理

要了解「反向代理」,首先需要知道什麼是「代理伺服器」和「正向代理」

代理伺服器

在網路中,客戶端發起一個請求,獲取伺服器端的資源。它們之間並不是建立一條直接的通道,而是被代理伺服器所轉發。
代理伺服器作為網路中的媒介將網際網路上獲取的資源返回給相關的客戶端。
我們通常所說的代理,一般都指的是「正向代理」,是相對於客戶端來說的。
比方說我連結了一個VPN,我訪問Google的時候,客戶端發起的請求到了VPN,VPN幫忙轉發請求Google的伺服器,然後把Google響應返回給客戶端。這個過程,VPN就充當了「正向代理伺服器」的角色。

反向代理

和「正向代理」不同,「反向代理」的說法面向於伺服器端。一個客戶端請求來到代理伺服器,代理伺服器根據客戶端的請求的不同而把請求轉發到不同的伺服器,這個過程在「負載均衡」中,也會發生兩個一樣的請求,會轉發到完全不一樣的伺服器中的情況。
「正向代理」是「負載均衡」實現的前提,正因為代理伺服器有了解析請求,分發請求的能力,才能實現負載均衡,降低每一臺伺服器的負荷。
利用「反向代理」,除了實現負載均衡,還可以實現諸如:SSL加密,靜態內容快取,gzip壓縮,減速上傳,安全等功能

負載均衡

負載均衡(Load balancing)是一種計算機網路技術,用來在多個伺服器中分配負載,以達到最佳化資源使用、最大化吞吐率、最小化響應時間、同時避免過載的目的。
使用帶有負載均衡的多個伺服器元件,取代單一的元件,可以通過冗餘提高可靠性。負載平衡服務的實現可以通過軟體和硬體來實現。
負載均衡的分發,一般都會有多套演算法來處理分發問題。

連線 Connection

在nginx中connection就是對tcp連線的封裝,其中包括連線的socket,讀事件,寫事件。利用nginx封裝的connection,我們可以很方便的使用nginx來處理與連線相關的事情,比如,建立連線,傳送與接受資料等。
而nginx中的http請求的處理就是建立在connection之上的,所以nginx不僅可以作為一個web伺服器,也可以作為郵件伺服器。
當然,利用nginx提供的connection,我們可以與任何後端服務打交道。

最大連線數

在nginx中,每個程序會有一個連線數的最大上限,這個上限與系統對fd的限制不一樣。
在作業系統中,通過ulimit -n,我們可以得到一個程序所能夠開啟的fd的最大數,即nofile,因為每個socket連線會佔用掉一個fd,所以這也會限制我們程序的最大連線數,當然也會直接影響到我們程式所能支援的最大併發數,當fd用完後,再建立socket時,就會失敗。
nginx通過設定worker_connectons來設定每個程序支援的最大連線數。如果該值大於nofile,那麼實際的最大連線數是nofile,nginx會有警告。
nginx在實現時,是通過一個連線池來管理的,每個worker程序都有一個獨立的連線池,連線池的大小是worker_connections。這裡的連線池裡面儲存的其實不是真實的連線,它只是一個worker_connections大小的一個ngx_connection_t結構的陣列。並且,nginx會通過一個連結串列free_connections來儲存所有的空閒ngx_connection_t,每次獲取一個連線時,就從空閒連線連結串列中獲取一個,用完後,再放回空閒連線連結串列裡面。

所以,一個nginx能建立的最大連線數:worker_connections * worker_processes
如果當nginx作為反向代理的話,因為一個請求nginx要建立客戶端和伺服器的請求,所以最大連線數是:worker_connections * worker_processes / 2

請求 Request

在nginx中我們指http請求,具體到nginx中的資料結構是ngx_http_request_t
它是對一個http請求的封裝,nginx通過ngx_http_request_t來儲存解析請求與輸出響應相關的資料。
一個http請求,包含請求行、請求頭、請求體、響應行、響應頭、響應體。

一般性的網路請求處理過程是:

客戶端會傳送請求過來。

然後我們讀取一行資料,分析出請求行中包含的method、uri、http_version資訊。

然後再一行一行處理請求頭,並根據請求method與請求頭的資訊來決定是否有請求體以及請求體的長度,然後再去讀取請求體。

得到請求後,我們處理請求產生需要輸出的資料,然後再生成響應行,響應頭以及響應體。

在將響應傳送給客戶端之後,一個完整的請求就處理完了。

而nginx處理請求的時候會有一些小小的區別,比如,當請求頭讀取完成後,就開始進行請求的處理了。

Nginx處理請求過程

nginx處理一個請求的抽象概念過程:

request 請求進來

初始化HTTP Request, 生成 HTTP Request物件

處理請求頭

處理請求體

呼叫與此請求關聯的handler(根據你URL或者Location配置)

依次呼叫各phase handler進行處理

獲取location配置

產生適當的響應

傳送response header

傳送response body

基本資料結構

nginx的作者為追求極致的高效,自己實現了很多頗具特色的nginx風格的資料結構以及公共函式。比如,nginx提供了帶長度的字串,根據編譯器選項優化過的字串拷貝函式ngx_copy等。
ps: 下橫線分割是C語言的變數名風格

Data StructureDescription
ngx_str_t字串封裝
ngx_pool_t提供一種機制,幫助管理一系列的資源(記憶體,檔案)
ngx_array_t陣列結構
ngx_chain_t主要用於模組之間資料傳遞的連結串列實現
ngx_buf_t就是ngx_chain_t連結串列的每個節點的實際實現,代表某種具體的資料。
ngx_list_tlist資料結構的實現,以及增強
ngx_queue_t實現的雙向連結串列
ngx_hash_thash表的實現
ngx_hash_wildcard_t為處理帶有萬用字元域名的匹配問題實現的hash表結構
ngx_combinded_t在於提供一個方便的容器包含三個型別的hash表
ngx_hash_keys_arrays_t用於構建其他型別的hash的輔助類

配置

nginx的配置系統由一個主配置檔案和其他一些輔助的配置檔案構成。這些配置檔案均是純文字檔案,全部位於nginx安裝目錄下的conf目錄下。
指令由nginx的各個模組提供,不同的模組會提供不同的指令來實現配置。
指令除了Key-Value的形式,還有作用域指令。
nginx.conf中的配置資訊,根據其邏輯上的意義,對它們進行了分類,也就是分成了多個作用域,或者稱之為配置指令上下文。不同的作用域含有一個或者多個配置項。

下面的這些上下文指令是用的比較多:

DirectiveDescriptionContains Directive
mainnginx在執行時與具體業務功能(比如http服務或者email服務代理)無關的一些引數,比如工作程序數,執行的身份等。user, worker_processes, error_log, events, http, mail
http與提供http服務相關的一些配置引數。例如:是否使用keepalive啊,是否使用gzip進行壓縮等。server
serverhttp服務上支援若干虛擬主機。每個虛擬主機一個對應的server配置項,配置項裡面包含該虛擬主機相關的配置。在提供mail服務的代理時,也可以建立若干server.每個server通過監聽的地址來區分。listen, server_name, access_log, location, protocol, proxy, smtp_auth, xclient
locationhttp服務中,某些特定的URL對應的一系列配置項。index, root
mail實現email相關的SMTP/IMAP/POP3代理時,共享的一些配置項(因為可能實現多個代理,工作在多個監聽地址上)。server, http, imap_capabilities

模組

nginx將各功能模組組織成一條鏈,當有請求到達的時候,請求依次經過這條鏈上的部分或者全部模組,進行處理。每個模組實現特定的功能。例如,實現對請求解壓縮的模組,實現SSI的模組,實現與上游伺服器進行通訊的模組,實現與FastCGI服務進行通訊的模組。
模組分三類:

核心模組

輔助模組

第三方模組

根據官方文件排版,輔助模組還分了以下幾類:

http

mail

stream

而根據其功能可以分成這幾大類:

handler模組
此型別的模組也被直接稱為handler模組。主要負責處理客戶端請求併產生待響應內容,比如ngx_http_static_module模組,負責客戶端的靜態頁面請求處理並將對應的磁碟檔案準備為響應內容輸出。

filter模組
過濾響應頭和內容的模組,可以對回覆的頭和內容進行處理。它的處理時間在獲取回覆內容之後,向使用者傳送響應之前。

upstream模組
upstream模組實現反向代理的功能,將真正的請求轉發到後端伺服器上,並從後端伺服器上讀取響應,發回客戶端。upstream模組是一種特殊的handler,只不過響應內容不是真正由自己產生的,而是從後端伺服器上讀取的。

load balance模組
負載均衡模組,實現特定的演算法,在眾多的後端伺服器中,選擇一個伺服器出來作為某個請求的轉發伺服器

結尾

本文講述了Nginx的一些基本概念。
Nginx是執行緒模型是Master-Worker模式的,每個worker是單執行緒的,也就是處理請求是單執行緒處理的。而單執行緒併發的事件模型是「非同步非阻塞I/O」模型。
並且講述了「反向代理」「負載均衡」的概念,這是nginx能高效能處理高併發的原因之一。
Nginx對於網路請求是有Connection和Request的概念和封裝的。
Nginx的原始碼組織架構是模組化的,不同的模組實現不一樣的職責,然後它們被連線起來一起幹一件大事,知道模組有哪些分類,可以讓我們知道怎麼查詢官方文件。
在沒有看過有哪些指令,哪些指令有什麼功能之前,是不能完全知道nginx提供什麼樣的功能的,那就抱著,那就抱著「能想到的別人都想到並實現了」的想法來使用nginx吧。Nginx作為一個代理服務,在中間想做什麼都可以啦。

參考

Nginx開發從入門到精通
Nginx官方網站
《計算機作業系統》
《深入淺出Node.js》