1、varnish 概述:
varnish是一款高性能且開源的方向代理服務器和HTTP加速器,它的開發者poul-Henning kamp FreeBSD 核心的開發人員之一。varnish采用全新的軟件體系機構,和現在的硬件體系配合緊密,
varnish是一個輕量級的cache和反向代理軟件。先進的設計理念和成熟的設計框架式varnish的主要特點。現在的varnish總共代碼量不大,雖然功能在不斷改進,但是還需要繼續豐富加強
2、vanish系統架構:官方架構如如下:

varnish主要運行兩個進程:Management進程和Child進程(也叫Cache進程)。
Management進程主要實現應用新的配置、編譯VCL、監控varnish、初始化varnish以及提供一個命令行接口等。Management進程會每隔幾秒鍾探測一下Child進程以判斷其是否正常運行,如果在指定的時長內未得到Child進程的回應,Management將會重啟此Child進程。
Child進程包含多種類型的線程,常見的如:
Acceptor線程:接收新的連接請求並響應;
Worker線程:child進程會為每個會話啟動一個worker線程,因此,在高並發的場景中可能會出現數百個worker線程甚至更多;
Expiry線程:從緩存中清理過期內容;
Varnish依賴“工作區(workspace)”以降低線程在申請或修改內存時出現競爭的可能性。在varnish內部有多種不同的工作區,其中最關鍵的當屬用於管理會話數據的session工作區。
# ps -aux | grep varnish root 5436 0.0 0.2 112300 1196 ? Ss 04:19 0:00 /usr/sbin/varnishd -P /var/run/varnish.pid -a :80 -f /etc/varnish/default.vcl -T 127.0.0.1:6082 -t 120 -w 50,1000sh -g varnish -S /etc/varnish/secret -s malloc,100M varnish 5437 0.0 0.7 1242232 3464 ? Sl 04:19 0:00 /usr/sbin/varnishd -P /var/run/varnish.pid -a :80 -f /etc/varnish/default.vcl -T 127.0.0.1:6082 -t 120 -w 50,1000sh -g varnish -S /etc/varnish/secret -s malloc,100M
3、varnish日志:
為了與系統的其它部分進行交互,Child進程使用了可以通過文件系統接口進行訪問的共享內存日志(shared memory log),因此,如果某線程需要記錄信息,其僅需要持有一個鎖,而后向共享內存中的某內存區域寫入數據,再釋放持有的鎖即可。而為了減少競爭,每個worker線程都使用了日志數據緩存。
共享內存日志大小一般為90M,其分為兩部分,前一部分為計數器,后半部分為客戶端請求的數據。varnish提供了多個不同的工具如varnishlog、varnishncsa或varnishstat等來分析共享內存日志中的信息並能夠以指定的方式進行顯示。
# rpm -ql varnish /etc/logrotate.d/varnish # varnish 默認啟用了日志滾動的功能;日志滾動的腳本文件; /etc/rc.d/init.d/varnish # varnish 的服務啟動腳本文件; /etc/rc.d/init.d/varnishlog # 分析共享內存日志中信息工具的服務啟動腳本; /etc/rc.d/init.d/varnishncsa /etc/sysconfig/varnish # varnish 全局配置文件 /etc/varnish # varnish 主配置文件目錄 /etc/varnish/default.vcl # varnish 默認的配置文件 /usr/bin/varnish_reload_vcl # 相關的一些二進制程序
varnishstat: 因為這幾個參數比較重要;所以這這里列出了;
client connections accepted : 表示客戶端反向代理服務器成功發起HTTP請求的數量 client request recceived: 表示到現在為止:瀏覽器向反向代理服務器發送HTTP請求累計數,由於可能使用長連接因此這個值一般會大於"client connections accepted的值" cache his : 表示反向代理服務器在緩存區中查找並且命中的次數。 cache misses: 表示直接訪問后端主機的請求數量:也就是非命中數: N struct object : 表示當前的緩存內容數量; N expired object : 表示當過期的緩存內容數量; N LRU moved objects : 表示淘汰的緩存內容個數;
varnishlog 自帶的參數如下:
-a 當把日志寫到文件里時,使用附加,而不是覆蓋。 -b 只顯示 varnishd 和后端服務器的日志。 -C 匹配正則表達式的時候,忽略大小寫差異。 -c 只顯示 varnishd 和客戶端的日志。 -D 以進程方式運行 -d 在啟動過程中處理舊的日志,一般情況下,varnishhist 只會在進程寫入日志后啟動。 -I regex 匹配正則表達式的日志,如果沒有使用-i 或者-I,那么所有的日志都會匹配。 -i tag 匹配指定的 tag,如果沒有使用-i 或者-I,那么所有的日志都會被匹配。 -k num 只顯示開始的 num 個日志記錄。 -n 指定 varnish 實例的名字,用來獲取日志,如果沒有指定,默認使用主機名。 -o 以請求 ID 給日志分組,這個功能沒多大用。如果要寫到一個文件里使用 -w 選項。 -P file 記錄 PID 號的文件 -r file 從一個文件讀取日志,而不是從共享內存讀取。 -s sum 跳過開始的 num 條日志。 -u 無緩沖的輸出。 -V 顯示版本,然后退出。 -w file 把日志寫到一個文件里代替顯示他們,如果不是用-a 參數就會發生覆蓋,如果 varnishlog 在寫日志時,接收到一個 SIGHUP 信號,他會創建一個新的文件,老的文件可以移走。 -X regex 排除匹配正則表達式的日志。
varnishncsa 工具詳解:
-a 當把日志寫到文件里時,使用附加,而不是覆蓋。 -b 只顯示varnishd和后端服務器的日志。 -C 匹配正則表達式的時候,忽略大小寫差異。 -c 只顯示varnishd和客戶端的日志。 -D 以進程方式運行 -d 在啟動過程中處理舊的日志,一般情況下,varnishhist只會在進程寫入日志后啟動。 -f 在日志輸出中使用X-Forwarded-ForHTTP頭代替client.ip。 -I regex 匹配正則表達式的日志,如果沒有使用-i或者-I,那么所有的日志都會匹配。 -i tag 匹配指定的tag,如果沒有使用-i或者-I,那么所有的日志都會被匹配。 -n 指定varnish實例的名字,用來獲取日志,如果沒有指定,默認使用主機名。 -P file記錄PID號的文件 -r file從一個文件讀取日志,而不是從共享內存讀取。 -w file把日志寫到一個文件里代替顯示他們,如果不是用-a參數就會發生覆蓋,如果varnishlog在寫日志時,接收到一個SIGHUP信號,他會創建一個新的文件,老的文件可以移走。 -X regex 排除匹配正則表達式的日志。 -x tag 排除匹配tag的日志。
varnishd命令參# varnishd -help:
At least one of -d, -b, -f, -M, -S or -T must be specified
usage: varnishd [options]
-a address:port # HTTP listen address and port # 表示Varnish對httpd的監聽地址及端口
-b address:port # backend address and port # 表示后端服務器地址及端口
# -b <hostname_or_IP>
# -b '<hostname_or_IP>:<port_or_service>'
-C # print VCL code compiled to C language
-d # debug #表示后端服務器地址及端口
-f file # VCL script # 指定vanish服務器的配置文件
-F # Run in foreground
-h kind[,hashoptions] # Hash specification
# -h critbit [default]
# -h simple_list
# -h classic
# -h classic,<buckets>
-i identity # Identity of varnish instance
-l shl,free,fill # Size of shared memory file
# shl: space for SHL records [80m]
# free: space for other allocations [1m]
# fill: prefill new file [+]
-M address:port # Reverse CLI destination.
-n dir # varnishd working directory # 指定Varnish服務器的配賢文件 指定服務器參數,用來優化Varnish性能
-P file # PID file # varnish進程pid文件存放路徑
-p param=value # set parameter # 指定服務器參數,用來優化vanish性能
-s kind[,storageoptions] # Backend storage specification
# -s malloc
# -s file [default: use /tmp]
# -s file,<dir_or_file>
# -s file,<dir_or_file>,<size>
# -s persist{experimenta}
# -s file,<dir_or_file>,<size>,<granularity>
-t # Default TTL # 指定默認ttl值;單位為s;
-S secret-file # Secret file for CLI authentication # 指定認證文件
-T address:port # Telnet listen address and port # 設定varnish的telnet管理地址及端口 telnet 交互式模式調試服務器
-V # version # 顯示varnish版本號和版權信息
-w int[,int[,int]] # Number of worker threads # 設定Varnish的工作線程數;常用的方式有:
# -w <fixed_count> -w min,max
# -w min,max 如:-w5 ,512000,30
# -w min,max,timeout [default: -w2,500,300]
-u user # Priviledge separation user id
4、VCL:
Varnish Configuration Language (VCL)是varnish配置緩存策略的工具,它是一種基於“域”(domain specific)的簡單編程語言,它支持有限的算術運算和邏輯運算操作、允許使用正則表達式進行字符串匹配、允許用戶使用set自定義變量、支持if判斷語句,也有內置的函數和變量等。使用VCL編寫的緩存策略通常保存至.vcl文件中,其需要編譯成二進制的格式后才能由varnish調用。事實上,整個緩存策略就是由幾個特定的子例程如vcl_recv、vcl_fetch等組成,它們分別在不同的位置(或時間)執行,如果沒有事先為某個位置自定義子例程,varnish將會執行默認的定義。
VCL策略在啟用前,會由management進程將其轉換為C代碼,而后再由gcc編譯器將C代碼編譯成二進制程序。編譯完成后,management負責將其連接至varnish實例,即child進程。正是由於編譯工作在child進程之外完成,它避免了裝載錯誤格式VCL的風險。因此,varnish修改配置的開銷非常小,其可以同時保有幾份尚在引用的舊版本配置,也能夠讓新的配置即刻生效。編譯后的舊版本配置通常在varnish重啟時才會被丟棄,如果需要手動清理,則可以使用varnishadm的vcl.discard命令完成。
5、varnish的后端存儲:
varnish支持多種不同類型的后端存儲,這可以在varnishd啟動時使用-s選項指定。后端存儲的類型包括:
(1)file:使用特定的文件存儲全部的緩存數據,並通過操作系統的mmap()系統調用將整個緩存文件映射至內存區域(如果條件允許);
(2)malloc:使用malloc()庫調用在varnish啟動時向操作系統申請指定大小的內存空間以存儲緩存對象;
(3)persistent(experimental):與file的功能相同,但可以持久存儲數據(即重啟varnish數據時不會被清除);仍處於測試期;
varnish無法追蹤某緩存對象是否存入了緩存文件,從而也就無從得知磁盤上的緩存文件是否可用,因此,file存儲方法在varnish停止或重啟時會清除數據。而persistent方法的出現對此有了一個彌補,但persistent仍處於測試階段,例如目前尚無法有效處理要緩存對象總體大小超出緩存空間的情況,所以,其僅適用於有着巨大緩存空間的場景。
選擇使用合適的存儲方式有助於提升系統性,從經驗的角度來看,建議在內存空間足以存儲所有的緩存對象時使用malloc的方法,反之,file存儲將有着更好的性能的表現。然而,需要注意的是,varnishd實際上使用的空間比使用-s選項指定的緩存空間更大,一般說來,其需要為每個緩存對象多使用差不多1K左右的存儲空間,這意味着,對於100萬個緩存對象的場景來說,其使用的緩存空間將超出指定大小1G左右。另外,為了保存數據結構等,varnish自身也會占去不小的內存空間。
為varnishd指定使用的緩存類型時,-s選項可接受的參數格式如下:
malloc[,size] 或
file[,path[,size[,granularity]]] 或
persistent,path,size {experimental}
file中的granularity用於設定緩存空間分配單位,默認單位是字節,所有其它的大小都會被圓整。
6、varnish的特點:
1、基於內存進行緩存,重啟后數據將消失。
2、利用虛擬內存方式,I\O性能好。
3、支持設置0~60秒的精確緩存時間。
4、VCL配置管理比較靈活。
5、具有強大的管理功能,例如top、stat、admin、list 等。
6、狀態機設計巧妙、結構清晰。
7、利用二叉堆管理緩存文件,可達到積極刪除目的。
7、開始安裝varnish
varnish的安裝非常簡單,下面逐步介紹;
7.1、接着,建立Vanish用於以及用戶組,並且創建Varnish緩存目錄和日志目錄
# useradd -s /sbin/nologin varnish # mkdir /data/vanish/cache -pv # mkdir /data/vanish/log # chown -R varnish:varnish /data/vanish/cache/ # chown -R varnish:varnish /data/vanish/log/
7.2、獲取Varnish軟件
Varnish的官方網址為 https://www.varnish-cache.org/ ,這里有varnish的最新說明文檔及版本升級記錄,在此網站中可以找到varnish在SourceForge中的下載鏈接。目前,The current stable release of Varnish Cache 3 is 3.0.5,下載完成后包名為varnish-3.0.5.tar.gz,這里以此版本為例,進行安裝配置。
7.3、 安裝pcre 官方站點: http://www.pcre.org/ 下載完成的包名為 pcre-8.35.zip
如果沒有安裝pcre,在編譯varnish-3.0.5.tar.gz 以上版本時,會提示找不到pcre庫,而pcre庫是為了兼容正則表達式,所以必須安裝pcre庫。下面是pcre的安裝過程:
如果安裝pcre出現如下錯誤時:
# useradd -s /sbin/nologin varnish # mkdir /data/vanish/cache -pv # mkdir /data/vanish/log # chown -R varnish:varnish /data/vanish/cache/ # chown -R varnish:varnish /data/vanish/log/
解決辦法: 按提示應該是文件時間問題,新創建的時間既然比現在的文件時間晚,系統時間問題
hwclock --set --date="月/日/年 小時:分鍾:秒鍾" hwclock --hctosys # hwclock --set --date "04/09/2014 00:00:00" # hwclock --hctosys # unzip pcre-8.35.zip # cd pcre-8.35 # ./configure --prefix=/usr/local/pcre/ # make && make install
hwclock --hctosys是讓上面設置的硬件時間同系統時間同步
7.4、安裝Varnish
這里講Varnish 安裝到/usr/loca 目錄下,操作如下:
# tar xvf varnish-3.0.5.tar.gz # cd varnish-3.0.5 # export PKG_CONFIG PATH=/usr/local/pcre/lib/pkgconfig # ./configure --prefix=/usr/local/varnish \ --enable-dependency-trackin \ --enable-debugging-symbols \ --enable-developer-warnings # cp redhat/varnish.initrc /etc/init.d/vanish # cp redhat/varnish.sysconfig /etc/sysconfig/varnish
其 中,"PKG_CONFIG_PATH" 是指定Varnish查找pcre庫的路徑。如果pcre安裝在其它路徑下,在這里指定相應路徑即 可,varnish默認查找pcre庫的路徑為/usr/local/lib/pkgconfig。最后兩步操作時復制一些Varnish守護進程的初始 化腳本文件,這些腳本文件用戶varnish的啟動,關閉等方面。
至此,varnish安裝完畢了!
8、配置Varnish
8.1、 VCL 使用說明
VCL 即 為 varnish configuration Language,用來定義varnish的存取策略。VCL 語法比較簡單,跟C和perl比較相 似,可以使用指定運算符"="、比較運算符"="、邏輯運算符"!&&!!"等形式;還支持正則表達式和用"~"進行ACL匹配運算;還 可以使用"set"這樣的關鍵字指定變量。 VCL 的語法遵循特定的格式:

VCL內置函數
(1)
用於接收和處理請求。當請求到達並被成功接收后調用,通過判斷請求的數據來決定如何處理請求
pass:表示進入pass模式,把請求控制權交給val_pass函數。
pipe:表示進入pipe模式,把請求控制權交個vcl_pipe函數。
error code[reason]:表示返回"code"給客戶端,並放棄處理該請求。"code"是錯誤標識,例如200和405等。"reason"是錯誤提示信息。
(2)vcl_pipe函數
此函數在進入pipe模式時被調用,用戶將請求直接傳遞至后端主機,在請求和返回的內容沒有改變的情況下,將不變的內容返回給客戶端,直到這個鏈接被關閉。
此函數一般以如下幾個關鍵字結束。
erro code[reason]
pipe
(3) vcl_pass 函數
此函數在進入pass模式時被調用,用戶將請求直接傳遞至后端主機。后端主機在應答數據后將應答數據發送給客戶端,但不進行任何緩存,在當前鏈接下每次都返回最新的內容。
此函數一般以如下幾個關鍵字結束。
error code [reason]
pass
(4)lookup
表示在緩存中查找被請求的對象,並且根據查找的結果把控制權交給函數vcl_hit 或函數vcl_miss
(5)vcl_hit 函數
在執行lookip指令后,在緩存中找到請求的內容后將自動調用該函數。此函數一般以如下幾個關鍵字結束。
deliver:表示將找到的內容發送給客戶端,並把控制權交個函數vcl_deliver
error code[reason]
pass
(6) vcl_miss 函數
在執行lookup指令后,在緩存中沒有找到請求的內容時自動調用該方法。此函數可用於判斷是否需要從后端服務器獲取內容
此函數一般如下幾個關鍵字結束。
fetch:表示從后端獲取請求的內容,並把控制權交個vcl_fetch函數
error code [reason]
pass
(7)vcl_fetch函數
在后端主機更新緩存並且獲取內容后調用該方法,接着通過判斷獲取的內容來決定是將內容放入緩存,還是直接返回給客戶端。
此函數一般以如下幾個關鍵字結束。
error code [reason]
pass
deliver
(8)vcl_deliver函數
將在緩存中找到請求的內容發送給客戶端前調用此方法。
此函數一般以如下幾個關鍵字結束。
error code [reason]
deliver
(9)vcl_timeout 函數
在緩存內同到期前吊桶此函數。
此函數一般以如下幾個關鍵字結束。
discard:表示從緩存中清除該內同
fetch。
(10)vcl_discard函數
在緩存內容到期后或緩存空間不夠時,自動吊桶該函數。
此函數一般如下幾個關鍵字結束。
keep:表示將內容繼續保存在緩存中。
discard。
8.2、VCL處理流程圖
通過以上對VCL函數的介紹,其實你們應該都發現了,其實每個函數之間都是相互關聯的。 如下如所示:Varnish處理HTTP請求的運行流程圖:

Varnish處理HTTP請求的過程大致分為如下幾個步驟;
(1)Receive狀態。也就是請求處理的入口狀態,根據VCL規則判斷該請求應該pass或者pipe,還是進入lookup(本地查詢)
(2)Lookup狀態。進入此狀態后,會在hash表中查找數據,若找到,則進入Hit狀態,否則進入Miss狀態。
(3)Pass狀態。在此狀態下,會進入后端請求,即進入fetch狀態。
(4)Fetch狀態。在fetch狀態下,對請求進行后端獲取,發送請求,獲得數據,並進行本地存儲。
(5)Deliver狀態。將獲取到的數據發送給客戶端,然后完成本次請求。
8.3、內置公用變量
VCL內置的公用變量可以在不同的VCL函數中。下面根據這些公用變量使用的不同階段依次介紹。當請求到達后,可以使用的公用變量如下表:
| 公用變量名稱 | 含義 |
| req.backend | 指定對應后端主機 |
| server.ip | 表示服務器IP |
| client.ip | 表示客戶端IP |
| req.request | 指定請求的類型,例如GET、HEAD和POST等 |
| req.url | 指定請求的地址 |
| req.proto | 表示客戶端發起請求的HTTP協議版本 |
| req.http.header | 表示對應請求中的HTTP頭部信息 |
| req.restarts | 表示請求重啟的次數,默認最大值為4 |
Varnish 在向后端主機請求時,可以使用的公用變量如下表:
| 公用變量名稱 | 含義 |
| beresp.request | 指定請求的類型,例如GET或HEAD等 |
| beresp.url | 指定請求的地址 |
| beresp.proto | 表示客戶端發起請求中的HTTP協議版本 |
| beresp.http.header | 表示對應請求中的HTTP頭部信息 |
| beresp.ttl | 表示緩存的生存周期,也就是cache保留多長時間單位是秒 |
從cache或后端主機獲取內容后,可以使用的公用變量如下表所示:
| 公用變量名稱 | 含義 |
| obj.status | 表示返回內容的請求狀態碼,例如200、302、504等 |
| obj.cacheable | 表示返回的內容是否可以緩存,也就是說,如果HTTP返回的是200、203、300、301、302、404或410等,並且有非0的生存期,則可以緩存 |
| obj.valid | 表示是否是有效的HTTP應答 |
| obj.response | 表示返回內容的請求狀態信息 |
| obj.proto | 表示返回內容的HTTP協議版本 |
| obj.ttl | 表示返回內容的生存周期,也就是緩存時間,單位是秒 |
| obj.lastuse | 表示返回上一次請求到現在的間隔時間,單位是秒 |
對客戶端應答時,可以使用的公用變量,如下表所示:
| 公用變量名稱 | 含義 |
| resp.status | 表示返回客戶端的HTTP狀態代碼 |
| resp.proto | 表示返回客戶端的HTTP協議版本 |
| resp.http.header | 表示返回客戶端的HTTP頭部信息 |
| resp.response | 表示返回客戶端的HTTP狀態信息 |
九、HTTP協議與varnish
1、緩存相關的HTTP首部
HTTP協議提供了多個首部用以實現頁面緩存及緩存失效的相關功能,這其中最常用的有:
(1)Expires:用於指定某web對象的過期日期/時間,通常為GMT格式;一般不應該將此設定的未來過長的時間,一年的長度對大多場景來說足矣;其常用於為純靜態內容如JavaScripts樣式表或圖片指定緩存周期;
(2)Cache-Control:用於定義所有的緩存機制都必須遵循的緩存指示,這些指示是一些特定的指令,包括public、private、no-cache(表示可以存儲,但在重新驗正其有效性之前不能用於響應客戶端請求)、no-store、max-age、s-maxage以及must-revalidate等;Cache-Control中設定的時間會覆蓋Expires中指定的時間;
(3)Etag:響應首部,用於在響應報文中為某web資源定義版本標識符;
(4)Last-Mofified:響應首部,用於回應客戶端關於Last-Modified-Since或If-None-Match首部的請求,以通知客戶端其請求的web對象最近的修改時間;
(5)If-Modified-Since:條件式請求首部,如果在此首部指定的時間后其請求的web內容發生了更改,則服務器響應更改后的內容,否則,則響應304(not modified);
(6)If-None-Match:條件式請求首部;web服務器為某web內容定義了Etag首部,客戶端請求時能獲取並保存這個首部的值(即標簽);而后在后續的請求中會通過If-None-Match首部附加其認可的標簽列表並讓服務器端檢驗其原始內容是否有可以與此列表中的某標簽匹配的標簽;如果有,則響應304,否則,則返回原始內容;
(7)Vary:響應首部,原始服務器根據請求來源的不同響應的可能會有所不同的首部,最常用的是Vary: Accept-Encoding,用於通知緩存機制其內容看起來可能不同於用戶請求時Accept-Encoding-header首部標識的編碼格式;
(8)Age:緩存服務器可以發送的一個額外的響應首部,用於指定響應的有效期限;瀏覽器通常根據此首部決定內容的緩存時長;如果響應報文首部還使用了max-age指令,那么緩存的有效時長為“max-age減去Age”的結果;
十、Varnish狀態引擎(state engine)
VCL用於讓管理員定義緩存策略,而定義好的策略將由varnish的management進程分析、轉換成C代碼、編譯成二進制程序並連接至child進程。varnish內部有幾個所謂的狀態(state),在這些狀態上可以附加通過VCL定義的策略以完成相應的緩存處理機制,因此VCL也經常被稱作“域專用”語言或狀態引擎,“域專用”指的是有些數據僅出現於特定的狀態中。
1、VCL狀態引擎
在VCL狀態引擎中,狀態之間具有相關性,但彼此間互相隔離,每個引擎使用return(x)來退出當前狀態並指示varnish進入下一個狀態。
varnish開始處理一個請求時,首先需要分析HTTP請求本身,比如從首部獲取請求方法、驗正其是否為一個合法的HTT請求等。當這些基本分析結束后就需要做出第一個決策,即varnish是否從緩存中查找請求的資源。這個決定的實現則需要由VCL來完成,簡單來說,要由vcl_recv方法來完成。如果管理員沒有自定義vcl_recv函數,varnish將會執行默認的vcl_recv函數。然而,即便管理員自定義了vcl_recv,但如果沒有為自定義的vcl_recv函數指定其終止操作(terminating),其仍將執行默認的vcl_recv函數。事實上,varnish官方強烈建議讓varnish執行默認的vcl_recv以便處理自定義vcl_recv函數中的可能出現的漏洞。
2、VCL語法
VCL的設計參考了C和Perl語言,因此,對有着C或Perl編程經驗者來說,其非常易於理解。其基本語法說明如下:
(1)//、#或/* comment */用於注釋
(2)sub $name 定義函數
(3)不支持循環,有內置變量
(4)使用終止語句,沒有返回值
(5)域專用
(6)操作符:=(賦值)、==(等值比較)、~(模式匹配)、!(取反)、&&(邏輯與)、||(邏輯或)
VCL的函數不接受參數並且沒有返回值,因此,其並非真正意義上的函數,這也限定了VCL內部的數據傳遞只能隱藏在HTTP首部內部進行。VCL的return語句用於將控制權從VCL狀態引擎返回給Varnish,而非默認函數,這就是為什么VCL只有終止語句而沒有返回值的原因。同時,對於每個“域”來說,可以定義一個或多個終止語句,以告訴Varnish下一步采取何種操作,如查詢緩存或不查詢緩存等。
3、VCL的內置函數
VCL提供了幾個函數來實現字符串的修改,添加bans,重啟VCL狀態引擎以及將控制權轉回Varnish等。
regsub(str,regex,sub)
regsuball(str,regex,sub):這兩個用於基於正則表達式搜索指定的字符串並將其替換為指定的字符串;但regsuball()可以將str中能夠被regex匹配到的字符串統統替換為sub,regsub()只替換一次;
ban(expression):
ban_url(regex):Bans所有其URL能夠由regex匹配的緩存對象;
purge:從緩存中挑選出某對象以及其相關變種一並刪除,這可以通過HTTP協議的PURGE方法完成;
hash_data(str):
return():當某VCL域運行結束時將控制權返回給Varnish,並指示Varnish如何進行后續的動作;其可以返回的指令包括:lookup、pass、pipe、hit_for_pass、fetch、deliver和hash等;但某特定域可能僅能返回某些特定的指令,而非前面列出的全部指令;
return(restart):重新運行整個VCL,即重新從vcl_recv開始進行處理;每一次重啟都會增加req.restarts變量中的值,而max_restarts參數則用於限定最大重啟次數。
4、vcl_recv
vcl_recv是在Varnish完成對請求報文的解碼為基本數據結構后第一個要執行的子例程,它通常有四個主要用途:
(1)修改客戶端數據以減少緩存對象差異性;比如刪除URL中的www.等字符;
(2)基於客戶端數據選用緩存策略;比如僅緩存特定的URL請求、不緩存POST請求等;
(3)為某web應用程序執行URL重寫規則;
(4)挑選合適的后端Web服務器;
可以使用下面的終止語句,即通過return()向Varnish返回的指示操作:
pass:繞過緩存,即不從緩存中查詢內容或不將內容存儲至緩存中;
pipe:不對客戶端進行檢查或做出任何操作,而是在客戶端與后端服務器之間建立專用“管道”,並直接將數據在二者之間進行傳送;此時,keep-alive連接中后續傳送的數據也都將通過此管道進行直接傳送,並不會出現在任何日志中;
lookup:在緩存中查找用戶請求的對象,如果緩存中沒有其請求的對象,后續操作很可能會將其請求的對象進行緩存;
error:由Varnish自己合成一個響應報文,一般是響應一個錯誤類信息、重定向類信息或負載均衡器返回的后端web服務器健康狀態檢查類信息;
vcl_recv也可以通過精巧的策略完成一定意義上的安全功能,以將某些特定的攻擊扼殺於搖籃中。同時,它也可以檢查出一些拼寫類的錯誤並將其進行修正等。
Varnish默認的vcl_recv專門設計用來實現安全的緩存策略,它主要完成兩種功能:
(1)僅處理可以識別的HTTP方法,並且只緩存GET和HEAD方法;
(2)不緩存任何用戶特有的數據;
安全起見,一般在自定義的vcl_recv中不要使用return()終止語句,而是再由默認vcl_recv進行處理,並由其做出相應的處理決策。
下面是一個自定義的使用示例:
sub vcl_recv { if (req.http.User-Agent ~ "iPad" || req.http.User-Agent ~ "iPhone" || req.http.User-Agent ~ "Android") { set req.http.X-Device = "mobile"; } else { set req.http.X-Device = "desktop"; } }
此例中的VCL創建一個X-Device請求首部,其值可能為mobile或desktop,於是web服務器可以基於此完成不同類型的響應,以提高用戶體驗。
5、vcl_fetch
如前面所述,相對於vcl_recv是根據客戶端的請求作出緩存決策來說,vcl_fetch則是根據服務器端的響應作出緩存決策。在任何VCL狀態引擎中返回的pass操作都將由vcl_fetch進行后續處理。vcl_fetch中有許多可用的內置變量,比如最常用的用於定義某對象緩存時長的beresp.ttl變量。通過return()返回給varnish的操作指示有:
(1)deliver:緩存此對象,並將其發送給客戶端(經由vcl_deliver);
(2)hit_for_pass:不緩存此對象,但可以導致后續對此對象的請求直接送達到vcl_pass進行處理;
(3)restart:重啟整個VCL,並增加重啟計數;超出max_restarts限定的最大重啟次數后將會返回錯誤信息;
(4)error code [reason]:返回指定的錯誤代碼給客戶端並丟棄此請求;
默認的vcl_fetch放棄了緩存任何使用了Set-Cookie首部的響應。
十一、修剪緩存對象
1、緩存內容修剪
提高緩存命中率的最有效途徑之一是增加緩存對象的生存時間(TTL),但是這也可能會帶來副作用,比如緩存的內容在到達為其指定的有效期之間已經失效。因此,手動檢驗緩存對象的有效性或者刷新緩存是緩存很有可能成為服務器管理員的日常工作之一,相應地,Varnish為完成這類的任務提供了三種途徑:HTTP 修剪(HTTP purging)、禁用某類緩存對象(banning)和強制緩存未命令(forced cache misses)。
這里需要特殊說明的是,Varnish 2中的purge()操作在Varnish 3中被替換為了ban()操作,而Varnish 3也使用了purge操作,但為其賦予了新的功能,且只能用於vcl_hit或vcl_miss中替換Varnish 2中常用的set obj.ttl=0s。
在具體執行某清理工作時,需要事先確定如下問題:
(1)僅需要檢驗一個特定的緩存對象,還是多個?
(2)目的是釋放內存空間,還是僅替換緩存的內容?
(3)是不是需要很長時間才能完成內容替換?
(4)這類操作是個日常工作,還是僅此一次的特殊需求?
2、移除單個緩存對象
purge用於清理緩存中的某特定對象及其變種(variants),因此,在有着明確要修剪的緩存對象時可以使用此種方式。HTTP協議的PURGE方法可以實現purge功能,不過,其僅能用於vcl_hit和vcl_miss中,它會釋放內存工作並移除指定緩存對象的所有Vary:-變種,並等待下一個針對此內容的客戶端請求到達時刷新此內容。另外,其一般要與return(restart)一起使用。下面是個在VCL中配置的示例。
acl purgers { "127.0.0.1"; "192.168.0.0"/24; } sub vcl_recv { if (req.request == "PURGE") { if (!client.ip ~ purgers) { error 405 "Method not allowed"; } return (lookup); } } sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged"; } } sub vcl_miss { if (req.request == "PURGE") { purge; error 404 "Not in cache"; } } sub vcl_pass { if (req.request == "PURGE") { error 502 "PURGE on a passed object"; } }
客戶端在發起HTTP請求時,只需要為所請求的URL使用PURGE方法即可,其命令使用方式如下:
# curl -I -X PURGE http://varniship/path/to/someurl
3、強制緩存未命中
在vcl_recv中使用return(pass)能夠強制到上游服務器取得請求的內容,但這也會導致無法將其緩存。使用purge會移除舊的緩存對象,但如果上游服務器宕機而無法取得新版本的內容時,此內容將無法再響應給客戶端。使用req.has_always_miss=ture,可以讓Varnish在緩存中搜尋相應的內容但卻總是回應“未命中”,於是vcl_miss將后續地負責啟動vcl_fetch從上游服務器取得新內容,並以新內容緩存覆蓋舊內容。此時,如果上游服務器宕機或未響應,舊的內容將保持原狀,並能夠繼續服務於那些未使用req.has_always_miss=true的客戶端,直到其過期失效或由其它方法移除。
4、Banning
ban()是一種從已緩存對象中過濾(filter)出某此特定的對象並將其移除的緩存內容刷新機制,不過,它並不阻止新的內容進入緩存或響應於請求。在Varnish中,ban的實現是指將一個ban添加至ban列表(ban-list)中,這可以通過命令行接口或VCL實現,它們的使用語法是相同的。ban本身就是一個或多個VCL風格的語句,它會在Varnish從緩存哈希(cache hash)中查找某緩存對象時對搜尋的對象進行比較測試,因此,一個ban語句就是類似匹配所有“以/downloads開頭的URL”,或“響應首部中包含nginx的對象”。例如:
ban req.http.host == "magedu.com" && req.url ~ "\.gif$"
定義好的所有ban語句會生成一個ban列表(ban-list),新添加的ban語句會被放置在列表的首部。緩存中的所有對象在響應給客戶端之前都會被ban列表檢查至少一次,檢查完成后將會為每個緩存創建一個指向與其匹配的ban語句的指針。Varnish在從緩存中獲取對象時,總是會檢查此緩存對象的指針是否指向了ban列表的首部。如果沒有指向ban列表的首部,其將對使用所有的新添加的ban語句對此緩存對象進行測試,如果沒有任何ban語句能夠匹配,則更新ban列表。
對ban這種實現方式持反對意見有有之,持贊成意見者亦有之。反對意見主要有兩種,一是ban不會釋放內存,緩存對象僅在有客戶端訪問時被測試一次;二是如果緩存對象曾經被訪問到,但卻很少被再次訪問時ban列表將會變得非常大。贊成的意見則主要集中在ban可以讓Varnish在恆定的時間內完成向ban列表添加ban的操作,例如在有着數百萬個緩存對象的場景中,添加一個ban也只需要在恆定的時間內即可完成。其實現方法本處不再詳細說明。
十二、Varnish檢測后端主機的健康狀態
Varnish可以檢測后端主機的健康狀態,在判定后端主機失效時能自動將其從可用后端主機列表中移除,而一旦其重新變得可用還可以自動將其設定為可用。為了避免誤判,Varnish在探測后端主機的健康狀態發生轉變時(比如某次探測時某后端主機突然成為不可用狀態),通常需要連續執行幾次探測均為新狀態才將其標記為轉換后的狀態。
每個后端服務器當前探測的健康狀態探測方法通過.probe進行設定,其結果可由req.backend.healthy變量獲取,也可通過varnishlog中的Backend_health查看或varnishadm的debug.health查看。
backend web1 { .host = "www.magedu.com"; .probe = { .url = "/.healthtest.html"; .interval = 1s; .window = 5; .threshold = 2; } }
.probe中的探測指令常用的有:
(1) .url:探測后端主機健康狀態時請求的URL,默認為“/”; (2) .request: 探測后端主機健康狀態時所請求內容的詳細格式,定義后,它會替換.url指定的探測方式;比如: .request = "GET /.healthtest.html HTTP/1.1" "Host: www.magedu.com" "Connection: close"; (3) .window:設定在判定后端主機健康狀態時基於最近多少次的探測進行,默認是8; (4) .threshold:在.window中指定的次數中,至少有多少次是成功的才判定后端主機正健康運行;默認是3; (5) .initial:Varnish啟動時對后端主機至少需要多少次的成功探測,默認同.threshold; (6) .expected_response:期望后端主機響應的狀態碼,默認為200; (7) .interval:探測請求的發送周期,默認為5秒; (8) .timeout:每次探測請求的過期時長,默認為2秒;
因此,如上示例中表示每隔1秒對此后端主機www.magedu.com探測一次,請求的URL為http://www.magedu.com/.healthtest.html,在最近5次的探測請求中至少有2次是成功的(響應碼為200)就判定此后端主機為正常工作狀態。
如果Varnish在某時刻沒有任何可用的后端主機,它將嘗試使用緩存對象的“寬容副本”(graced copy),當然,此時VCL中的各種規則依然有效。因此,更好的辦法是在VCL規則中判斷req.backend.healthy變量顯示某后端主機不可用時,為此后端主機增大req.grace變量的值以設定適用的寬容期限長度。
七、Varnish使用多台后端主機
Varnish中可以使用director指令將一個或多個近似的后端主機定義為一個邏輯組,並可以指定的調度方式(也叫挑選方法)來輪流將請求發送至這些主機上。不同的director可以使用同一個后端主機,而某director也可以使用“匿名”后端主機(在director中直接進行定義)。每個director都必須有其專用名,且在定義后必須在VCL中進行調用,VCL中任何可以指定后端主機的位置均可以按需將其替換為調用某已定義的director。
backend web1 {
.host = "backweb1.magedu.com";
.port = "80";
}
director webservers random {
.retries = 5;
{
.backend = web1;
.weight = 2;
}
{
.backend = {
.host = "backweb2.magedu.com";
.port = "80";
}
.weight = 3;
}
}
如上示例中,web1為顯式定義的后端主機,而webservers這個directors還包含了一個“匿名”后端主機(backweb2.magedu.com)。webservers從這兩個后端主機中挑選一個主機的方法為random,即以隨機方式挑選。
Varnish的director支持的挑選方法中比較簡單的有round-robin和random兩種。其中,round-robin類型沒有任何參數,只需要為其指定各后端主機即可,挑選方式為“輪叫”,並在某后端主機故障時不再將其視作挑選對象;random方法隨機從可用后端主機中進行挑選,每一個后端主機都需要一個.weight參數以指定其權重,同時還可以director級別使用.retires參數來設定查找一個健康后端主機時的嘗試次數。
Varnish 2.1.0后,random挑選方法又多了兩種變化形式client和hash。client類型的director使用client.identity作為挑選因子,這意味着client.identity相同的請求都將被發送至同一個后端主機。client.identity默認為client.ip,但也可以在VCL中將其修改為所需要的標識符。類似地,hash類型的director使用hash數據作為挑選因子,這意味着對同一個URL的請求將被發往同一個后端主機,其常用於多級緩存的場景中。然而,無論是client還hash,當其傾向於使用后端主機不可用時將會重新挑選新的后端其機。
另外還有一種稱作fallback的director,用於定義備用服務器,如下所示:
director b3 fallback {
{ .backend = www1; }
{ .backend = www2; } // will only be used if www1 is unhealthy.
{ .backend = www3; } // will only be used if both www1 and www2
// are unhealthy.
}
set client.identity = req.http.cookie
十三、varnish管理進階
1、可調參數
Varnish有許多參數,雖然大多數場景中這些參數的默認值都可以工作得很好,然而特定的工作場景中要想有着更好的性能的表現,則需要調整某些參數。可以在管理接口中使用param.show命令查看這些參數,而使用param.set則能修改這些參數的值。然而,在命令行接口中進行的修改不會保存至任何位置,因此,重啟varnish后這些設定會消失。此時,可以通過啟動腳本使用-p選項在varnishd啟動時為其設定參數的值。然而,除非特別需要對其進行修改,保持這些參數為默認值可以有效降低管理復雜度。
2、共享內存日志
共享內存日志(shared memory log)通常被簡稱為shm-log,它用於記錄日志相關的數據,大小為80M。varnish以輪轉(round-robin)的方式使用其存儲空間。一般不需要對shm-log做出更多的設定,但應該避免其產生I/O,這可以使用tmpfs實現,其方法為在/etc/fstab中設定一個掛載至/var/lib/varnish目錄(或其它自定義的位置)臨時文件系統即可。
3、線程模型(Trheading model)
varnish的child進程由多種不同的線程組成,分別用於完成不同的工作。例如:
cache-worker線程:每連接一個,用於處理請求;
cache-main線程:全局只有一個,用於啟動cache;
ban lurker線程:一個,用於清理bans;
acceptor線程:一個,用於接收新的連接請求;
epoll/kqueue線程:數量可配置,默認為2,用於管理線程池;
expire線程:一個,用於移除老化的內容;
backend poll線程:每個后端服務器一個,用於檢測后端服務器的健康狀況;
在配置varnish時,一般只需為關注cache-worker線程,而且也只能配置其線程池的數量,而除此之外的其它均非可配置參數。與此同時,線程池的數量也只能在流量較大的場景下才需要增加,而且經驗表明其多於2個對提升性能並無益處。
4、線程相關的參數(Threading parameters)
varnish為每個連接使用一個線程,因此,其worker線程的最大數決定了varnish的並發響應能力。下面是線程池相關的各參數及其配置:
thread_pool_add_delay 2 [milliseconds] thread_pool_add_threshold 2 [requests] thread_pool_fail_delay 200 [milliseconds] thread_pool_max 500 [threads] thread_pool_min 5 [threads] thread_pool_purge_delay 1000 [milliseconds] thread_pool_stack 65536 [bytes] thread_pool_timeout 120 [seconds] thread_pool_workspace 16384 [bytes] thread_pools 2 [pools] thread_stats_rate 10 [requests]
其中最關鍵的當屬thread_pool_max和thread_pool_min,它們分別用於定義每個線程池中的最大線程數和最少線程數。因此,在某個時刻,至少有thread_pool_min*thread_pools個worker線程在運行,但至多不能超出thread_pool_max*thread_pools個。根據需要,這兩個參數的數量可以進行調整,varnishstat命令的n_wrk_queued可以顯示當前varnish的線程數量是否足夠,如果隊列中始終有不少的線程等待運行,則可以適當調大thread_pool_max參數的值。但一般建議每台varnish服務器上最多運行的worker線程數不要超出5000個。
當某連接請求到達時,varnish選擇一個線程池負責處理此請求。而如果此線程池中的線程數量已經達到最大值,新的請求將會被放置於隊列中或被直接丟棄。默認線程池的數量為2,這對最繁忙的varnish服務器來說也已經足夠。
十四、Varnish的命令行工具
1、varnishadm命令
命令語法:varnishadm [-t timeout] [-S secret_file] [-T address:port] [-n name] [command [...]]
通過命令行的方式連接至varnishd進行管理操作的工具,指定要連接的varnish實例的方法有兩種:
-n name —— 連接至名稱為“name”的實例;
-T address:port —— 連接至指定套接字上的實例;
其運行模式有兩種,當不在命令行中給出要執行的"command"時,其將進入交互式模式;否則,varnishadm將執行指定的"command"並退出。要查看本地啟用的緩存,可使用如下命令進行。
# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 storage.list
