Nginx是一個免費開源且穩定高效的Web伺服器程式,擁有反向代理以及負載平衡的功能,經常作為最前端的伺服器。當它用作反向代理伺服器或是PHP網頁伺服器時,無論是proxy_pass還是fastcgi_pass,均有提供快取的功能。然而,免費版本的Nginx並沒有內建刪除快取的機制(早期Nginx免費版本可以使用proxy_cache_purgefastcgi_cache_purge命令來清除快取,只不過後來這功能要在商業版本上才能使用了),在沒有額外腳本或程式的幫助下,我們大概也只能用rm等Linux指令來手動刪除快取目錄中的檔案,十分不方便,而且有一定的危險性。



如果您不知道如何在Ubuntu Server架設Nginx伺服器,也不知道怎麼使用proxy_pass的快取功能的話,可以先參考這篇文章:

https://magiclen.org/ubuntu-server-nginx/

如果您是架設PHP網站的話,可以再參考這篇文章來了解fastcgi_pass的快取功能:

https://magiclen.org/ubuntu-server-nginx-php/

Nginx Cache Purge

「Nginx Cache Purge」是一個高效Nginx快取清除工具,是解決免費版本的Nginx不能使用proxy_cache_purgefastcgi_cache_purge命令的替代方案。

GitHub:

https://github.com/magiclen/nginx-cache-purge

安裝和解除安裝Nginx Cache Purge

透過Cargo

如果系統環境中有安裝「Cargo」的話,可以直接使用以下指令來下載「Nginx Cache Purge」的原始碼專案,並進行編譯安裝。

cargo install nginx-cache-purge

若要解除安裝,執行以下這個指令即可:

cargo uninstall nginx-cache-purge
透過GitHub(適用於Linux x86_64)

執行以下指令,將最新的Nginx Cache Purge執行檔從GitHub上下載下來,並移動到/usr/local/bin目錄中。

(curl -s https://api.github.com/repos/magiclen/nginx-cache-purge/releases/latest | sed -r -n 's/.*"browser_download_url": *"(.*\/nginx-cache-purge_'$(uname -m)')".*//p' | wget -i -) && sudo mv nginx-cache-purge_$(uname -m) /usr/local/bin/nginx-cache-purge && sudo chmod +x /usr/local/bin/nginx-cache-purge

若要解除安裝,執行以下這個指令即可:

sudo rm /usr/local/bin/nginx-cache-purge

使用Nginx Cache Purge

命令列介面

Nginx Cache Purge只有提供CLI(命令列介面),指令用法如下:

nginx-cache-purge <CACHE_PATH> <LEVELS> <KEY>

CACHE_PATH是使用Nginx的proxy_cache_path或是fastcgi_cache_path命令時所設定的快取儲存目錄路徑。LEVELS是使用Nginx的proxy_cache_path或是fastcgi_cache_path命令時所設定的目錄和目錄檔名結構,格式為x[:y[:z]]KEY是使用Nginx的proxy_cache_key或是fastcgi_cache_key命令時所設定的快取鍵值。

建議把快取的鍵值以Nginx的$request_uri為結尾,因為Nginx Cache Purge還有Wildcard清除功能。如果KEY的結尾字元是星號*的話,就會去尋找快取目錄中,所有鍵值前綴是KEY(去除結尾星號)的快取。

Nginx + lua-nginx-module + Nginx Cache Purge

lua-nginx-module是Nginx的Lua腳本程式語言擴充套件,可以在Nginx的設定檔中於content_by_lua_block命令形成的的區塊內直接撰寫Lua腳本。若是使用Debian或是其衍生的Linux作業系統,可以直接用以下的指令來安裝lua-nginx-module

sudo apt install lua-nginx-module

利用Lua提供的os.execute函數,可以在Nginx設定檔中去執行外部的執行檔,也就可以在指定的location區塊中,或是location區塊內的if區塊中,去執行Nginx Cache Purge。

至於要在哪個location區塊中或是if區塊中去執行Nginx Cache Purge,其實也沒有一定的標準,大致上有兩種作法。

HTTP的PURGE請求方法

在有使用到proxy_cache或是fastcgi_cache命令的區塊中,可以去判斷客戶端的請求方法是不是PURGE。雖然PURGE請求方法並不在HTTP標準中,但許多人會用它來當作清除快取的請求方法,有點約定俗成的感覺。

若要用這個方式來清除快取,可以在/etc/nginx/conf.d目錄中,新增purge.conf檔案,檔案內容如下:

map $request_method $is_purge {
    default   0;
    PURGE     1;
}

以上的map命令,可以去判斷客戶端的請求方法是不是PURGE,如果是的話,$is_purge變數的值就是1,否則是0

接著就是在location區塊內使用if命令判斷$is_purge變數,並在if區塊中使用content_by_lua_block命令執行Lua腳本。假設Nginx Cache Purge的執行檔路徑是/usr/local/bin/nginx-cache-purge,設定方式如下:

http {
    ...

    proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m;
    proxy_cache_key $scheme$proxy_host$request_uri;

    server {
        ...

        location / {
            if ($is_purge) {
                set $my_cache_key $scheme$proxy_host$request_uri;

                content_by_lua_block {
                    local exitStatus = os.execute("/usr/local/bin/nginx-cache-purge /path/to/cache 1:2 "..ngx.var.my_cache_key)

                    if exitStatus == 0 then
                        ngx.exit(ngx.HTTP_OK)
                    else
                        ngx.exit(ngx.HTTP_BAD_REQUEST)
                    end
                }
            }

            proxy_pass upstream;
            include proxy_params;
        }
    }
}

不過當然,我們還需要加入自己的存取驗證機制,避免隨便一個陌生人都可以連到我們的伺服器上清除快取。

以上的設定若成功,

  • 發送PURGE /path/to/abc請求,表示要清除GET /path/to/abc產生的快取。
  • 發送PURGE /path/to/*請求,表示要清除GET /path/to/**/*產生的快取,像是GET /path/to/GET /path/to/abcGET /path/to/123/456
專門用來清除快取的location區塊

如果不想把清除快取的功能和proxy_cache或是fastcgi_cache命令放在同一個location區塊,並使用PURGE請求方法來切換功能。也是可以另外再建立出location區塊,專門用來清除快取,不過鍵值中$request_uri的部份就可能會需要使用Lua額外處理一下。

例如:

http {
    ...

    proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m;
    proxy_cache_key $scheme$proxy_host$request_uri;

    server {
        ...

        location / {
            proxy_pass upstream;
            include proxy_params;
        }

        location /purge/ {
            set $my_cache_key_part $scheme$proxy_host;

            content_by_lua_block {
                local exitStatus = os.execute("/usr/local/bin/nginx-cache-purge /path/to/cache 1:2 "..ngx.var.my_cache_key_part..string.sub(ngx.var.request_uri, 7))

                if exitStatus == 0 then
                    ngx.exit(ngx.HTTP_OK)
                else
                    ngx.exit(ngx.HTTP_BAD_REQUEST)
                end
            }
        }
    }
}

以上設定中,利用Lua的string.sub來抓取Nginx的$request_uri變數的子字串,消除URI前面的/purge,以對應/