OpenResty實戰系列 | 執行流程與階段詳解
場景
我們先定義一個location塊級指令phase_echo來處理客戶端發過來請求URI處理。
location /phase_echo {
set $name "Tinywan";
echo $name;
set $name "開源技術小棧";
echo $name;
set $name "Tinywan 開源技術小棧";
echo $name;
}
請求訪問輸出結果:
PS C:\Users\Tinywan\Desktop> curl -i http://openresty.tinywan.com/phase_echo
HTTP/1.1 200 OK
Server: openresty/1.17.8.2
Date: Sun, 14 Jul 2024 00:31:18 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
Tinywan 開源技術小棧
Tinywan 開源技術小棧
Tinywan 開源技術小棧
為什么輸出全部是Tinywan 開源技術小棧。也就是最后一個設置的變量值呢?前面設置的怎么都沒生效嗎?
這是因為Nginx處理每一個用戶請求時,都是按照若干個不同階段依次處理的,而不是根據配置文件上的順序。以上配置涉及到了 兩個階段 rewrite和content階段。
- set屬于rewrite階段
- echo屬于content階段
而實際執行執行是 rewrite階段的指令在 content階段指令之前執行。實際的執行順序應當是以下這樣子的。
set $name "Tinywan";
set $name "開源技術小棧";
set $name "Tinywan 開源技術小棧";
echo $name;
echo $name;
echo $name;
所以這就是為什么最終會輸出Tinywan 開源技術小棧。帶著以上配置文件中執行的指令,讓我們進入Nginx執行流程與階段詳解。
Nginx執行階段
Nginx處理請求的過程一共劃分為11個階段,按照執行順序依次是post-read、server-rewrite、find-config、rewrite、post-rewrite、 preaccess、access、post-access、try-files、content、log。
所以整個請求的過程,是按照不同的階段執行的,在某個階段執行完該階段的指令之后,再進行下一個階段的指令執行。
執行階段示例
圖片
- post-read:讀取請求內容階段,nginx讀取并解析完請求頭之后就立即開始運行。例如模塊 ngx_realip 就在 post-read 階段注冊了處理程序。它的功能是迫使 Nginx 認為當前請求的來源地址是指定的某一個請求頭的值。
- server-rewrite:server請求地址重寫階段,當ngx_rewrite模塊的set配置指令直接書寫在server配置塊中時,基本上都是運行在server-rewrite階段。
- find-config:配置查找階段,這個階段并不支持Nginx模塊注冊處理程序,而是由Nginx核心來完成當前請求與location配置塊之間的配對工作。
- rewrite:location請求地址重寫階段,當ngx_rewrite指令用于location中,就是再這個階段運行的。另外ngx_set_misc(設置md5、encode_base64等)模塊的指令,還有ngx_lua模塊的set_by_lua指令和rewrite_by_lua指令也在此階段。
- post-rewrite:請求地址重寫提交階段,當nginx完成rewrite階段所要求的內部跳轉動作,如果rewrite階段有這個要求的話。
- preaccess:訪問權限檢查準備階段,ngx_limit_req和ngx_limit_zone在這個階段運行,ngx_limit_req可以控制請求的訪問頻率,ngx_limit_zone可以控制訪問的并發度;
- access:訪問權限檢查階段,標準模塊ngx_access、第三方模塊ngx_auth_request以及第三方模塊ngx_lua的access_by_lua 指令就運行在這個階段。配置指令多是執行訪問控制相關的任務,如檢查用戶的訪問權限,檢查用戶的來源IP是否合法。
- post-access:訪問權限檢查提交階段;主要用于配合access階段實現標準ngx_http_core模塊提供的配置指令satisfy的功能。satisfy all(與關系),satisfy any(或關系)
- try-files:配置項try_files處理階段;專門用于實現標準配置指令try_files的功能,如果前N-1個參數所對應的文件系統對象都不存在,try-files 階段就會立即發起內部跳轉到最后一個參數(即第 N 個參數)所指定的URI.
- content:內容產生階段,是所有請求處理階段中最為重要的階段,因為這個階段的指令通常是用來生成HTTP響應內容并輸出 HTTP 響應的使命.
- log:日志模塊處理階段,記錄日志
OpenResty 的運行機制
圖片
圖片來源:https://yxudong.github.io。
OpenResty 執行階段
OpenResty發起一個請求時,會有相應的執行流程,Nginx與Lua編寫腳本的基本構建塊是指令執行順序的。
從圖中可知OpenResty 處理請求大致分為4個大階段,11個小階段。
四個大階段
- 初始化階段(Initialization Phase) master進程啟動預加載/生成worker進程預加載
- 重寫、轉發、訪問階段(Rewrite / Access Phase) url轉發,權限判斷
- 內容處理/生成階段(Content Phase) 內容生成
- 日志階段(Log Phase)日志記錄
七個小階段
- init_by_lua_file:master-initing 階段,初始化全局配置或模塊
- init_worker_by_lua_file:worker-initing 階段,初始化進程專用功能
- ssl_certificate_by_lua_file:ssl 階段,在握手時設置安全證書
- set_by_lua_file:rewrite 階段,改寫 Nginx 變量
- rewrite_by_lua_file:rewrite 階段,改寫 URI ,實現跳轉或重定向
- access_by_lua_file:access 階段,訪問控制或限速
- content_by_lua_file:content 階段,產生響應內容
- balancer_by_lua_file:content 階段,反向代理時選擇后端服務器
- header_filter_by_lua_file:filter 階段,加工處理響應頭
- body_filter_by_lua_file:filter 階段,加工處理響應體
- log_by_lua_file:log 階段,記錄日志或其他的收尾工作
這些指令通常有三種形式
- xxx_by_lua:執行字符串形式的 Lua 代碼:
- xxx_by_lua_block:功能相同,但指令后是{ ...}的 Lua 代碼塊
- xxx_by_lua_file:功能相同,但執行磁盤上的 Lua 源碼文件。
這邊推薦使用 xxx_by_lua_file,它徹底分離了配置文件與業務代碼,讓兩者可以獨立部署,而且文件形式也讓我們更容易以模塊的方式管理組織 Lua 程序。
OpenResty 執行階段和 Nginx 的對照
圖片
圖片來源:https://blog.51cto.com/lisea/2425794。
server {
listen 80;
server_name openresty.tinywan.com;
location /run_phase {
set_by_lua_block $a {
ngx.log(ngx.ERR, "Tinywan is set_by_lua_block phase")
}
rewrite_by_lua_block {
ngx.log(ngx.ERR, "Tinywan is rewrite_by_lua_block phase")
}
access_by_lua_block {
ngx.log(ngx.ERR, "Tinywan is access_by_lua_block phase")
}
content_by_lua_block {
ngx.log(ngx.ERR, "Tinywan is content_by_lua_block phase")
}
header_filter_by_lua_block {
ngx.log(ngx.ERR, "Tinywan is header_filter_by_lua_block phase")
}
body_filter_by_lua_block {
ngx.log(ngx.ERR, "Tinywan is body_filter_by_lua_block phase")
}
log_by_lua_block {
ngx.log(ngx.ERR, "Tinywan is log_by_lua_block phase")
}
}
}
執行請求訪問:
curl -i http://openresty.tinywan.com/run_phase
查看錯誤日志文件內容:
2024/07/13 12:38:43 [error] 7#7: *2 [lua] set_by_lua:2: Tinywan is set_by_lua_block phase, client: 172.18.0.1, server: openresty.tinywan.com, request: "GET /run_phase HTTP/1.1", host: "openresty.tinywan.com"
2024/07/13 12:38:43 [error] 7#7: *2 [lua] rewrite_by_lua(openresty.tinywan.com.conf:18):2: Tinywan is rewrite_by_lua_block phase, client: 172.18.0.1, server: openresty.tinywan.com, request: "GET /run_phase HTTP/1.1", host: "openresty.tinywan.com"
2024/07/13 12:38:43 [error] 7#7: *2 [lua] access_by_lua(openresty.tinywan.com.conf:22):2: Tinywan is access_by_lua_block phase, client: 172.18.0.1, server: openresty.tinywan.com, request: "GET /run_phase HTTP/1.1", host: "openresty.tinywan.com"
2024/07/13 12:38:43 [error] 7#7: *2 [lua] content_by_lua(openresty.tinywan.com.conf:26):2: Tinywan is content_by_lua_block phase, client: 172.18.0.1, server: openresty.tinywan.com, request: "GET /run_phase HTTP/1.1", host: "openresty.tinywan.com"
2024/07/13 12:38:43 [error] 7#7: *2 [lua] header_filter_by_lua:2: Tinywan is header_filter_by_lua_block phase, client: 172.18.0.1, server: openresty.tinywan.com, request: "GET /run_phase HTTP/1.1", host: "openresty.tinywan.com"
2024/07/13 12:38:43 [error] 7#7: *2 [lua] body_filter_by_lua:2: Tinywan is body_filter_by_lua_block phase, client: 172.18.0.1, server: openresty.tinywan.com, request: "GET /run_phase HTTP/1.1", host: "openresty.tinywan.com"
2024/07/13 12:38:43 [error] 7#7: *2 [lua] log_by_lua(openresty.tinywan.com.conf:38):2: Tinywan is log_by_lua_block phase while logging request, client: 172.18.0.1, server: openresty.tinywan.com, request: "GET /run_phase HTTP/1.1", host: "openresty.tinywan.com"
通過日志文件記錄可以看到執行是按照階段順序進行輸出。