Nginx 變量漫談(四)
在設置了“取處理程序”的情況下,Nginx 變量也可以選擇將其值容器用作緩存,這樣在多次讀取變量的時候,就只需要調用“取處理程序”計算一次。我們下面就來看一個這樣的例子:
map $args $foo {
default 0;
debug 1;
}
server {
listen 8080;
location /test {
set $orig_foo $foo;
set $args debug;
echo "orginal foo: $orig_foo";
echo "foo: $foo";
}
}
這里首次用到了標準 ngx_map 模塊的 map 配置指令,我們有必要在此介紹一下。map
在英文中除了“地圖”之外,也有“映射”的意思。比方說,中學數學里講的“函數”就是一種“映射”。而 Nginx 的這個 map 指令就可以用于定義兩個 Nginx 變量之間的映射關系,或者說是函數關系。回到上面這個例子,我們用 map 指令定義了用戶變量 $foo
與 $args 內建變量之間的映射關系。特別地,用數學上的函數記法 y = f(x)
來說,我們的$args
就是“自變量” x
,而 $foo
則是“因變量” y
,即 $foo
的值是由 $args 的值來決定的,或者按照書寫順序可以說,我們將 $args 變量的值映射到了 $foo
變量上。
現在我們再來看 map 指令定義的映射規則:
map $args $foo {
default 0;
debug 1;
}
花括號中第一行的 default
是一個特殊的匹配條件,即當其他條件都不匹配的時候,這個條件才匹配。當這個默認條件匹配時,就把“因變量” $foo
映射到值 0
. 而花括號中第二行的意思是說,如果“自變量” $args
精確匹配了 debug
這個字符串,則把“因變量” $foo
映射到值 1
. 將這兩行合起來,我們就得到如下完整的映射規則:當 $args 的值等于 debug
的時候,$foo
變量的值就是 1
,否則 $foo
的值就為 0
.
明白了 map 指令的含義,再來看 location /test
. 在那里,我們先把當前 $foo
變量的值保存在另一個用戶變量 $orig_foo
中,然后再強行把 $args 的值改寫為 debug
,最后我們再用 echo 指令分別輸出 $orig_foo
和 $foo
的值。
從邏輯上看,似乎當我們強行改寫 $args 的值為 debug
之后,根據先前的 map 映射規則,$foo
變量此時的值應當自動調整為字符串 1
, 而不論 $foo
原先的值是怎樣的。然而測試結果并非如此:
$ curl 'http://localhost:8080/test'
original foo: 0
foo: 0
第一行輸出指示 $orig_foo
的值為 0
,這正是我們期望的:上面這個請求并沒有提供 URL 參數串,于是 $args最初的取值就是空,再根據我們先前定義的映射規則,$foo
變量在第一次被讀取時的值就應當是 0
(即匹配默認的那個 default
條件)。
而第二行輸出顯示,在強行改寫 $args 變量的值為字符串 debug
之后,$foo
的條件仍然是 0
,這顯然不符合映射規則,因為當 $args 為 debug
時,$foo
的值應當是 1
. 這究竟是為什么呢?
其實原因很簡單,那就是 $foo
變量在第一次讀取時,根據映射規則計算出的值被緩存住了。剛才我們說過,Nginx 模塊可以為其創建的變量選擇使用值容器,作為其“取處理程序”計算結果的緩存。顯然,ngx_map模塊認為變量間的映射計算足夠昂貴,需要自動將因變量的計算結果緩存下來,這樣在當前請求的處理過程中如果再次讀取這個因變量,Nginx 就可以直接返回緩存住的結果,而不再調用該變量的“取處理程序”再行計算了。
為了進一步驗證這一點,我們不妨在請求中直接指定 URL 參數串為 debug
:
$ curl 'http://localhost:8080/test?debug'
original foo: 1
foo: 1
我們看到,現在 $orig_foo
的值就成了 1
,因為變量 $foo
在第一次被讀取時,自變量 $args 的值就是debug
,于是按照映射規則,“取處理程序”計算返回的值便是 1
. 而后續再讀取 $foo
的值時,就總是得到被緩存住的 1
這個結果,而不論 $args 后來變成什么樣了。
map 指令其實是一個比較特殊的例子,因為它可以為用戶變量注冊“取處理程序”,而且用戶可以自己定義這個“取處理程序”的計算規則。當然,此規則在這里被限定為與另一個變量的映射關系。同時,也并非所有使用了“取處理程序”的變量都會緩存結果,例如我們前面在 (三) 中已經看到 $arg_XXX 并不會使用值容器進行緩存。
類似 ngx_map 模塊,標準的 ngx_geo 等模塊也一樣使用了變量值的緩存機制。
在上面的例子中,我們還應當注意到 map 指令是在 server
配置塊之外,也就是在最外圍的 http
配置塊中定義的。很多讀者可能會對此感到奇怪,畢竟我們只是在 location /test
中用到了它。這倒不是因為我們不想把 map
語句直接挪到 location
配置塊中,而是因為 map 指令只能在 http
塊中使用!
很多 Nginx 新手都會擔心如此“全局”范圍的 map 設置會讓訪問所有虛擬主機的所有 location
接口的請求都執行一遍變量值的映射計算,然而事實并非如此。前面我們已經了解到 map 配置指令的工作原理是為用戶變量注冊 “取處理程序”,并且實際的映射計算是在“取處理程序”中完成的,而“取處理程序”只有在該用戶變量被實際讀取時才會執行(當然,因為緩存的存在,只在請求生命期中的第一次讀取中才被執行),所以對于那些根本沒有用到相關變量的請求來說,就根本不會執行任何的無用計算。
這種只在實際使用對象時才計算對象值的技術,在計算領域被稱為“惰性求值”(lazy evaluation)。提供“惰性求值” 語義的編程語言并不多見,最經典的例子便是 Haskell. 與之相對的便是“主動求值” (eager evaluation)。我們有幸在 Nginx 中也看到了“惰性求值”的例子,但“主動求值”語義其實在 Nginx 里面更為常見,例如下面這行再普通不過的 set 語句:
set $b "$a,$a";
這里會在執行 set 規定的賦值操作時,“主動”地計算出變量 $b
的值,而不會將該求值計算延緩到變量 $b
實際被讀取的時候。
(未完待續)
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成