前言:由於抱著對Nginx的好奇心,決心把近期的個人學習主題定為了Nginx的入門學習。學習的內容大概就是學習極客時間,陶輝老師出品的《Nginx核心知識100講》。再配合自己業務場景中的幾個實戰,得出的些許經驗在此分享給大家。在文章中有不少截圖是直接截課程中老師的ppt部分進行整理的(侵權即刪,我再另作圖)。—— 注意,這是教完全沒概念的小白如何入門。如果已經瞭解過的可以直接關閉退出,不要浪費時間
簡單入門Nginx
自定義編譯構建Nginx
一般來說,如果沒有特殊需求。Nginx的構建可以直接依賴yum install nginx
或者brew install nginx
就安裝啟動起來,非常舒服。但是當你需要新增一些模組、加一些預設不編譯進Nginx的功能、熱更新以及等等等等操作時,就會又需要重新學會自定義編譯構建。再加上自定義構建其實並沒有想象中困難,且易於後續的繼續深入學習,所以我的建議是要學會這個簡單的自定義構建方式。
獲取編譯原始檔
開啟Nginx的官網,直接找到Download標籤下下載相應的二進位制檔案。然後直接下載,或者使用wget 檔案路徑
進行下載。一般穩妥起見都是直接選擇Mainline
版本即可
開始編譯安裝
將下載下來的壓縮包進行解壓tar -xzvf nginx-1.15.12.tar.gz
.解壓完成後就能看到類似這樣的檔案目錄.
一般來說我們不需要關心這個目錄的內容,但是有興趣的可以稍作了解,進階的時候會用得到。
nginx-1.15.12/
├── auto
| ├── cc 用於編譯
| ├── lib
| ├── os 作業系統的判斷
| ├── ... 其他內容都是為了支援config檔案判定nginx是否支援特性、模組等
├── CHANGES 版本的特性以及bug修復情況重構等的概覽
├── conf 示例檔案
├── configure 指令碼,生成中間檔案,執行變編譯前的必備操作.可以使用`./configure --help`檢視支援引數
├── contrib 提供兩個.pl指令碼和vim的工具
| ├── vim 可以執行`cp -r contrib/vim/* ~/.vim/` 來支援vim的nginx語法
├── html 提供兩個標準的HTML
| ├── 50x.html 發生500錯誤時的展示檔案
| ├── index.html 歡迎介面
├── man linux對於nginx的幫助檔案
├── src nginx的原始碼
├── objs configure編譯時生成的中間檔案
| ├── ngx_modules.c 決定了編譯時(make),會被編譯進nginx的模組.可以用於檢視該nginx共編譯了什麼模組進去
| ├── nginx
| ├── src `C語音`編譯時生成的所有檔案
| ├── so 如果使用動態模組的檔案
複製程式碼
編譯的過程,我們一般都是依賴configure
檔案提供的指令進行,非常簡單。執行./configure --help
可以檢視執行的引數,自定義構建方式。比如,由於許可權原因,我沒法將nginx安裝到系統的預設目錄/etc/nginx/...
,這時候我需要自定義編譯安裝的目錄可以這樣操作
執行會校驗模組等的內容,並生成objs
編譯中間檔案,通過裡面的ngx_modules.c
檔案可以快捷地檢視到該nginx所支援的功能特性。如果沒有更多的報錯,就可以執行make
以及make install
進行安裝了。期間如果發生報錯,一般都能很簡便地從各大搜尋引擎中找到相應的解決方案。
編譯完成後,我們的操作更多就是在編譯安裝的那個nginx下了。你也可以看到相應的檔案目錄結構大抵如此,如果是直接通過yum install
的目錄結構應該是會有些許不同的。
大部分資料夾我也是沒有接觸的,這裡介紹幾個關鍵的檔案:
sbin
:存放著Nginx的命令列指令。可以將裡面的執行檔案軟鏈或者直接通過./sbin/nginx + 指令
執行logs
: 顧名思義存放著日誌檔案conf
: Nginx的配置資料夾,通過裡面的配置來告訴Nginx該如何工作(我們的主要工作目錄)
一般到這裡後執行,./sbin/nginx -s
即可啟動nginx開始愉快地玩耍了。
Nginx的配置語法
不知道我的理解會不會有所偏差,Nginx的實現其實就是在使用各個模組提供的方法指令來指導Nginx的工作,所以學會它的基本配置語法自然是至關重要的。這裡直接貼出語法例項圖,大家可以通過實際的語法檔案以及每個小箭頭的註釋來了解一個配置檔案大抵由什麼部分組成,就不細說了。
1998176F-2824-48C7-8913-F644B924D6DF.png
Nginx的指令
指令部分也是非常簡單,一般用於停止、重啟等等。
- 格式:
nginx -s reolad
- 幫助:
-? 或 -h
- 使用指定的配置檔案:
-c
- 指定配置指令:
-g
覆寫配置檔案中的配置項 - 指定執行目錄:
-p
同樣是覆寫配置檔案中的執行目錄 - 傳送訊號:
-s
- 立刻停止服務:
stop
; - 優雅的停止服務:
quit
; - 過載配置檔案:
reload
;——修改配置檔案中的值後要另其生效(不停止服務) - 重新開始記錄日誌檔案:
reopen
;
- 立刻停止服務:
- 測試配置檔案是否有語法錯誤:
-t 或 -T
- 列印nginx的版本資訊、編譯資訊等:
-v 或 -V
Nginx的文件基本閱讀和搜尋方法
全文的思路一致貫穿下來的思想就是,前期學習Nginx的過程就是學習其配置。那麼第一件事,就是要了解Nginx給我們提供了什麼樣的功能以及我們應該要怎樣合理合法正確地去使用它。
我一般的思路大概是如此,首先,開啟你的最心儀的搜尋引擎。
image-20190508202107944-7318067.png
一般來說,如果你的需求是比較普遍的,在排名前幾的教程文章中你就能搜到你想要的內容。
當然與此同時,我還會根據自己的需求,找到Nginx官方文件中的模組介紹去閱讀更加科學的指令指導。每一個模組的介紹中都會包含以下三個方面:
- Example Configuration 示例
- Directives 所有指令的相關介紹
- Embedded Variables 模組提供的變數
其中,我們著重瞭解一下指令的文件該如何閱讀
image-20190508202738735.png
每部分內容都會標明指令名稱和支援的引數以及預設值。剩餘的內容都是描述這個指令的作用功能。往往當我們懷疑這個指令的使用方式或者功效時,即可在此查閱到完美的解讀(畢竟其他教程在經過消化後的知識可能會省略掉很多細節)
ps: 官網有時候還能找到一些比較常見的配置教程(比如websocket如何配置等等,非常值得閱讀)
Nginx掌握debug日誌除錯方法
在我初學Nginx的時候,常常因為苦於沒有辦法像js
那樣打斷點、加console.log
而苦惱不已,直到我發現了開啟debug
日誌的方法,麻麻再也不用擔心我不知道自己哪裡配置錯了。
開啟debug的方法其實也很簡單,在官網中也能找到相應的示例:A Debugging log
開啟的方法要回歸到我們上面提到的自定義編譯構建,在編譯時新增一行引數,開啟debug模式
./configure --with-debug
複製程式碼
之後同樣是執行編譯安裝後,在相應想要列印錯誤日誌的地方進行相應的配置.
error_log /path/to/log debug
複製程式碼
之後重啟nginx後便可以生效了。當然官網中其實有提供更加詳細的說明,建議閱讀相應的文章進一步瞭解。
需要掌握的重要知識點
掌握了上面的基礎知識,基本上就已經具備了配置Nginx的基本能力,基於下面的路徑基本能把百分之50%的需求簡單解決(我個人的感覺)
但是其實在配置中Nginx還是有不少坑,也或者說有幾個特別重要的知識點,如果沒有掌握可能會在配置的過程中讓你痛不欲生。下面,一個一個知識點過,當然如果直接買課程聽陶輝老師講,那肯定是要深入很多.這裡,我只是從自身的角度出發來衡量知識點的重要性。Let’s go!!
指令合併與繼承
指令的合併
Nginx的指令可以分為:值指令和動作類指令
- 值指令:儲存配置項的值,可以合併。例如:
root
、gizp
- 動作類指令:指定行為,不可以合併。例如:
rewrite
、proxy_pass
指令的繼承
- 子配置不存在時,直接使用父配置塊
- 子配置存在時,直接覆蓋父配置塊
Server的匹配順序
當配置多個server
配置塊且匹配邏輯複雜,極有可能一個請求進來同時命中多個server
塊時,需要參考一下的優先順序順序來判斷進入哪個Server塊進行執行。
- 精確匹配
- *在前的泛域名
- *在後的泛域名
- 按檔案中的順序匹配正規表示式域名
- default server
- 第一個
- Listen 指定 default
Location指令 [ location [= | ~ | ~* | ^~] uri { … } 或 location @name { ... } ]
Location指令要掌握的內容其實大體和Server一致,也是匹配順序的問題。不過有一個小細節值得注意的是Location僅匹配URI,忽略引數。
image-20190509105518688-7370518.png
邪惡的If指令
當if指令塊連續出現時,最後一個為真的if指令塊將一直影響後續的處理。通常不使用連續的if,特別是連續為true的情況下會造成誤解。
造成錯誤的原因
- if指令在rewrite階段執行
- if {} 塊中的配置,會在if條件為真時,替換當前請求的配置。
- if {} 同樣向上繼承父配置
- 當rewrite介面順序執行時,每次if為真都會替換當前請求的配置
- if {} 中的配置,會影響rewrite介面之後的階段執行。
location /only-one-if {
set $true 1;
// 不生效
if ($true) {
add_headder X-First 1;
}
// 生效的模組
if ($true) {
add_header X-Second 2;
}
return 204;
}
複製程式碼
所以,if 指令塊中要確保可以正確處理請求,不依賴if {}塊外的指令。當然還可以使用break阻斷後續rewrite階段的指令執行。
Rewrite模組:Return和Rewrite的區別
return指令
命令Nginx停止處理,直接返回重定向到客戶端。return指令簡潔有效,相比之下,return的使用優先順序高於rewrite。
rewrite指令
rewrite 規則會改變部分或整個使用者請求中的 URL,主要有兩個用途:
- 相似於return的指令,通過直接返回
http://
。 - 控制Nginx的處理流程
在rewrite指令中,有一項falg的標誌位,比較重要的有兩個:
-
last: 停止處理當前的 ngx_http_rewrite_module 指令集,並開始對匹配更改後的 URI 的新 location 進行搜尋(再從 server 走一遍匹配流程)。此時對於當前
server
或location
上下文,不再處理ngx_http_rewrite_module
重寫模組的指令。 -
break: 停止處理當前的
ngx_http_rewrite_module
指令集
last
和break
的異同:
- last 重寫 url 後,會再從 server 走一遍匹配流程,而 break 終止重寫後的匹配
- break 和 last 都能阻止後面的 rewrite 指令再次執行
變數
Nginx的變數有非常多的用處,但是在使用過程中我們也務必掌握下面兩個知識點,非常簡單。
- 變數的惰性求值。相信這個概念大家都不陌生了,也就是變數只有在被使用的那一刻才會執行“解析出變數的方法”。這相當於提高效能的一個方法。
- 變數值可以為時刻變化,其值為使用那一刻的值——變數的惰性求值而帶來的一個作用。也就是變數名在Nginx啟動之初即已經定義好,但是假若在能夠獲取到相應值之前的階段使用該變數,那麼顯而易見地只能獲取到一個空值。亦或是在後續階段倘若變數被改變,也不能響應回前面的執行步驟。
有關變數變化需要更深入地去掌握Nginx的執行機制和執行步驟方可更加深入理解。
實踐
單域名反向代理支援多套應用服務
需求場景: 只提供單域名,然後需要根據路徑字首反向到具體的應用服務上。
難點:
- 路由配置反向代理都是很簡單的。難點在於應用服務並不知道自己被反向代理了,在獲取資源內容或者是發起API請求的時候會用類似這樣的URI路徑:
www.myapp.com/public/xxxxx
或者www.myapp.com/api/xxxx/.
對於這種通用的請求,可能每套服務上都會被用到,而我們是不便於去找回它的源請求位置的(不希望改動應用層的程式碼)
實現思路:
- 對於剛進入的服務會進行location的匹配。而在相應的location指令塊中,我們會設定相應的服務cookie值——標識該請求的目標源應用。location中可以設定重定義cookie的值,如果沒有被重定義那麼則是採用cookie中的反向代理服務。
- 由於單頁面應用如果使用
history
模式,單頁面的路徑跳轉可能改寫路徑(並不經過nginx)。所以nginx沒有時機去重寫它的請求URL,在重新整理頁面時為了方便除了開發人員去檢視當前所處系統,需要時刻注意去使用rewrite
指令重寫URL的內容。
nginx.png
直接看程式碼吧~
http {
# 避免超時的相關操作
fastcgi_connect_timeout 3600;
fastcgi_send_timeout 3600;
fastcgi_read_timeout 3600;
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
proxy_read_timeout 3600;
upstream see_test {
server 127.0.0.1:9501;
}
upstream see_poc_fjnx {
server 127.0.0.1:9502;
}
server {
listen 8890 default_server;
server_name www.myapp.com;
if ($http_cookie ~* "prefix_url=see_poc_fjnx") {
set $group see_poc_fjnx;
}
if ($http_cookie ~* "prefix_url=see_test") {
set $group see_test;
}
location /public {
proxy_pass http://$group;
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
}
location /api {
proxy_pass http://$group;
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
}
location /poc/fjnx/ {
proxy_set_header HOST $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://see_poc_fjnx/;
proxy_redirect / http://http://www.myapp.com/poc/fjnx/;
userid on;
userid_name prefix_url=see_poc_fjnx;
userid_mark =;
userid_service 1;
}
location / {
if ($group = see_poc_fjnx) {
rewrite ^/(.+) http://www.myapp.com/poc/fjnx/$1 last;
}
proxy_set_header HOST $host;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://see_test/;
proxy_redirect / http://www.myapp.com/;
userid on;
userid_name prefix_url=see_test;
userid_mark =;
userid_service 1;
}
}
}
複製程式碼
後續:其實整篇文章邏輯比較細碎和零散,對於對Nginx已經有一定了解的同學並不適用。同時如果有了一定實踐經驗,想要更加深入瞭解Nginx的話的確推薦極客時間的這門課程。
參考文件