為什麼講這麼小的一個問題呢?因為今天在進行系統上線的時候遇到了這個問題。
這次的上線動作還是比較大的,由於組織架構拆分,某個接入層服務需要在兩個部門各自獨立部署,以避免頻繁的跨部門溝通,提升該接入層服務的變更效率。
該接入層服務之前是使用cookie + 記憶體session機制的,這次要獨立部署,首先是將這種記憶體session機制改成分散式對談(使用redis),總之,就是做成無狀態的。
再其次,就是將原來的流量閘道器nginx,升級成為openresty。openresty使用lua程式碼,判斷請求應該分發到我們部門的接入層服務,還是另一個部門的接入層服務。
升級成openresty,這塊涉及到兩件事情,一個是openresty的安裝,再一個是修改了原來的nginx.conf。今天升級後,大部分業務驗證ok,唯獨兩三個業務報錯,由於是app報錯,所以是要了對方的使用者名稱密碼在我手機上登入,在電腦上charles抓包,發現報錯的原因,竟然是個這。
先給大家看下,nginx中原始的設定:
location ~ (^/(cgi-bin|servlet|chart)/|\.jsp$)
{
proxy_pass http://backendServer;
include proxy.conf;
}
這個location會匹配/servlet/json
這樣的請求,我們這次就是對這個請求做了改造,用lua判斷應該反向代理到什麼地方,如下:
我們最終的openresty(就是增強版本的ng,組態檔都是基本相容的)設定大概如下:
location ~ /servlet/json {
set $target_url '';
rewrite_by_lua_block {
...
proxy_pass http://$target_url;
}
}
location /Api/ 這個是之前就有的,本次沒動
{
proxy_pass http://ApiGatewayServer/;
include proxy.conf;
}
location ~ (^/(cgi-bin|chart)/|\.jsp$)
{
proxy_pass http://info-tech-department-upstream;
include proxy.conf;
}
在我們上述這個設定裡,http://www.test.com/servlet/json 這樣的請求,就會匹配上location /servlet/json
, http://www.test.com/Api 這樣的請求,就會匹配上location /Api
,但是,我抓包後,發現竟然報錯的請求長這樣:
http://www.test.com/Api/servlet/json
而檢視openresty紀錄檔,發現其匹配上了location /servlet/json
,而不是預期的location /Api/
,這自然就是問題所在了。
http://www.test.com/Api/servlet/json 這樣一個請求,能匹配上下面這個location,我覺得正常:
location /Api/ 這個是之前就有的,本次沒動
{
proxy_pass http://ApiGatewayServer/;
include proxy.conf;
}
但是,竟然也匹配上了下面這個location:
location ~ /servlet/json {
set $target_url '';
rewrite_by_lua_block {
...
proxy_pass http://$target_url;
}
}
此時,我才開始搜尋,這個 location 後面的 ~ 符號的確切意思,我知道這個表示正則,但是沒想到,這種請求路徑/Api/servlet/json
包含了/servlet/json
也能匹配上。
我剛開始以為是這種匹配上了多個,那我是不是換下順序就好了,把/Api
那個location放到了檔案最前面:
location /Api/ 這個是之前就有的,本次沒動
{
proxy_pass http://ApiGatewayServer/;
include proxy.conf;
}
location ~ /servlet/json {
set $target_url '';
rewrite_by_lua_block {
...
proxy_pass http://$target_url;
}
}
結果還是沒效果。
沒效果的話,我最終解決的辦法就是,修改location ~ /servlet/json
為只匹配/servlet/json
開頭的那些請求。
最終的改動如下:
// ^在正則中,一般表示匹配一行的開頭,所以,我這裡加了^
location ~ ^/servlet/json {
}
終於ok了。
說起來,確實是對原nginx的組態檔遷移出的這個問題,人家以前是:
location ~ (^/(cgi-bin|servlet|chart)/|\.jsp$)
{
proxy_pass http://backendServer;
include proxy.conf;
}
這個是匹配/cgi-bin、/servlet、/chart開頭的請求,或者是jsp結尾的請求,我一遷移,就把意思整錯了。
那這塊的匹配機制到底是怎樣的呢?
官網:
https://nginx.org/en/docs/http/ngx_http_core_module.html
語法如下:
Syntax: location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
Default: —
Context: server, location
按檔案的說法,location 和 uri之間,可以什麼都沒有,如我們上面的:
location /Api/
這種呢,就算是字首匹配。
比如,
location / {
[ configuration B ]
}
/index.html 這種url就可以匹配。
當然,也可以在location和uri中間加如下幾種符號:
=
完全匹配,比如,
location = / {
[ configuration A ]
}
只能匹配「/
」 這個請求,其他請求都不能匹配,這個優先順序最高
~ (uri部分為:大小寫敏感的正則)或者 ~* (uri部分:大小寫不敏感的正則)
這種就是正則匹配,也就是我們前面的
location ~ /servlet/json {
這種,居然就匹配上了/Api/servlet/json
,我不是很理解,但大家要謹慎。好好學習下這塊的正規表示式怎麼寫。
^~
這種,一會再講。
匹配邏輯:
首先,對uri進行normalize,也就是,比如url有特殊字元的話,一般瀏覽器會自動編碼成%XX這種,另外,可能url中也有相對路徑,或者有重複的斜槓,都要處理。
The matching is performed against a normalized URI, after decoding the text encoded in the 「%XX」 form, resolving references to relative path components 「.」 and 「..」, and possible compression of two or more adjacent slashes into a single slash.
接下來,nginx首先會找出整個server塊中,字首匹配的所有location(就是location和uri中間啥都不加的那種),然後挨個匹配,找出最長字首匹配的那個location,在我們前面的例子中,就會找到:
location /Api/
但是,找到了之後,不會結束;還要繼續找正則型別的location,這次就是按照檔案的順序,從上到下找,
如果找到了,直接使用;如果沒找到,則使用最長字首的那個。
這次,在我們的例子中,就會找到如下部分,且直接使用這個location,不再繼續找。
location ~ /servlet/json {
set $target_url '';
rewrite_by_lua_block {
...
proxy_pass http://$target_url;
}
}
所以,這裡的邏輯就是:
1、先找 = 這種完全匹配的,找到就結束;
2、開始找字首匹配這種的,沒找到就算了,找到了也只是做個標記,把最長字首那個location記錄下來,假設為location A;
3、開始找正則這種型別的location,從上到下找,找到就直接停止,就用這個;沒找到就繼續找下一個正則型別;如果最終,完全沒找到正則型別的,就用第二步裡找到的location A
當然了,對於這個機制,有個小例外,就是有一種符號,可以打破這種機制:
^~
這個符號加在字首型別的location上,如果最長字首的那個location,加了這個符號,直接結束,不找正則型別的了。原文:
If the longest matching prefix location has the 「^~」 modifier then regular expressions are not checked.
這個符號,感覺很容易誤用,一開始沒研究之前,我以為^是一行的開頭的意思,危險。
我以前,以為字首這種優先順序很高,沒想到,比正則要低,被正則壓著打啊。
location ~ /servlet/json {
proxy_pass http://localhost:8082;
}
location /Api/
{
proxy_pass http://localhost:8083;
}
這邊有個網站,可以貼設定進去,檢測到底匹配上哪個:
大家也可以看下參考檔案:
https://stackoverflow.com/questions/5238377/nginx-location-priority