Varnish的子進程 VCL varnish的有限狀態機 客戶端和後端工作線程的詳細的varnish請求流程 VCL語法 VCL函數、關鍵字和變數 ...
Varnish的子進程
VCL
Varnish配置語言VCL是一種“域”專有類型的配置語言,用於描述Varnish Cache的請求處理和文檔高速緩存策略。
當載入新配置時,Manager進程會創建VCC進程,然後將VCL代碼轉換為C代碼,C代碼被gcc編譯成共用對象,然後共用對象被載入到cacher進程中。
VCL有多個狀態引擎(state engine),狀態之間存在相關性,但狀態引擎彼此間互相隔離。
每個狀態引擎可使用return(x)指明關聯至哪個下一級引擎,每個狀態引擎對應於vcl文件中的一個配置段,即為subroutine。
vcl_hash --> return(hit) --> vcl_hit
varnish的有限狀態機
實際上
vcl_recv ——>vcl_purge/vcl_pipe/vcl_hash
vcl_hash——>vcl_miss/vcl_hit
其他的如圖所示
vcl_hash --> return(X) --> vcl_X
VCL工作流看作是一個有限狀態機 。
每個請求被分開處理,每個請求在任何給定的時間都是獨立於其他人的,狀態是相關的但也是孤立的。
內置的VCL代碼始終存在,並附加在您自己的VCL下麵。
當Varnish處理請求時,它首先解析請求本身,稍後Varnish將請求方法從頭文件中分離出來,驗證它是否是有效的HTTP請求等等。
當基本解析完成後,首先檢查策略以作出決定,策略是VCL代碼用於做出決定的一組規則。
每個狀態都在VCL編碼中有對應的可用參數,在VCL上的狀態被概念化為子進程,在VCL中採用的等待狀態描述中的等待狀態即不是參數也不是返回值。
每個內置的子程式以首碼vcl_開始,被return(action)終止,退出當前狀態並指示varnish進入下一個狀態,其中action是一個關鍵詞用於指定期望的輸出。
子進程可以檢查和操控http報文頭部區域和各種其他方面的請求,並指示如何處理請求。
varnish創建的子進程被掛在varnish的工作中,這些子進程被以vcl_為首碼來命名的,而我們自己的進程就不能以其為首碼命名。
我們稱這些有關狀態的子進程為狀態引擎(state engine),VCL有多個狀態引擎,狀態之間存在相關性,但狀態引擎彼此間互相隔離,每個狀態引擎可使用return(x)指明關聯至哪個下一級引擎,每個狀態引擎對應於vcl文件中的一個配置段,即為subroutine。
vcl_recv的預設配置:
sub vcl_recv {
if (req.method == "PRI") {
/* We do not support SPDY or HTTP/2.0 */
return (synth(405));
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "DELETE") {
/* Non-RFC2616 or CONNECT which is weird. */
return (pipe);
}
if (req.method != "GET" && req.method != "HEAD") {
/* We only deal with GET and HEAD by default */
return (pass);
}
if (req.http.Authorization || req.http.Cookie) {
/* Not cacheable by default */
return (pass);
}
return (hash);
}
}
客戶端和後端工作線程的詳細的varnish請求流程
Client Side:
vcl_recv, vcl_pass, vcl_hit, vcl_miss, vcl_pipe, vcl_purge, vcl_synth, vcl_deliver
Backend Side:
vcl_backend_fetch, vcl_backend_response, vcl_backend_error
vcl_recv:
hash:vcl_hash
pass: vcl_pass
pipe: vcl_pipe
synth: vcl_synth
purge: vcl_hash --> vcl_purge
vcl_hash:
lookup:
hit: vcl_hit
miss: vcl_miss
pass, hit_for_pass: vcl_pass
purge: vcl_purge
兩個特殊的引擎:
vcl_init:在處理任何請求之前要執行的vcl代碼:主要用於初始化VMODs;
vcl_fini:所有的請求都已經結束,在vcl配置被丟棄時調用;主要用於清理VMODs;
VCL語法
VCL文件以vcl 4.0開始;
//,#和/ * * /是註釋
子進程用sub關鍵字聲明
沒有迴圈,狀態有限的變數
用下一個關鍵字作為return()函數的參數來終止語句,即:return(action)
特定領域
從Varnish 4.0開始,每個VCL文件必須首先在文件頂部用一個特殊的標記聲明它的版本。
塊由花括弧分隔,以分號結尾。
VCL中的子程式既不帶參數,也不返回值。
VCL中的子程式只能通過HTTP頭交換數據。
VCL有終止語句,而不是傳統的返回值。
子程式在執行return(*action*)語句時結束執行。
該行action告訴varnish下一步該做什麼。
VCL有兩個指令來使用來自另一個文件的內容,這些指令是include和import,並用於不同的目的。
include用於從另一個文件插入VCL代碼,Varnish查找被varnishd的vcl_dir參數指定目錄中的文件,請註意include語法中的引號。
import用於載入VMOD並將其功能提供給VCL代碼,Varnish將查找VMOD以載入到由varnishd的vmod_dir參數指定的目錄。
請註意import語法中缺少引號,你可以使用varnishtest中的include和import。
三類主要語法:
sub subroutine {
...
}
if CONDITION {
...
} else {
...
}
return(), hash_data()
VCL函數、關鍵字和變數
函數:
regsub(str, regex, sub)
regsuball(str, regex, sub)
ban(boolean expression)
hash_data(input)
synthetic(str)
Keywords:
call subroutine
return(action)
new
set
unset
操作符:
==, !=, ~, >, >=, <, <=
邏輯操作符:&&, ||, !
變數賦值:=
舉例:
obj.hits是內建變數,用於保存某緩存項的從緩存中命中的次數;
if (obj.hits>0) {
set resp.http.X-Cache = "HIT via " + server.ip;
} else {
set resp.http.X-Cache = "MISS from " + server.ip;
}
變數類型:
內建變數:
req.*:request,表示由客戶端發來的請求報文相關;
req.http.*
req.http.User-Agent, req.http.Referer, ...
bereq.*:由varnish發往BE主機的httpd請求相關;
bereq.http.*
beresp.*:由BE主機響應給varnish的響應報文相關;
beresp.http.*
resp.*:由varnish響應給client相關;
obj.*:存儲在緩存空間中的緩存對象的屬性;只讀;
常用變數:
bereq.*, req.*:
bereq.http.HEADERS
bereq.request:請求方法;
bereq.url:請求的url;
bereq.proto:請求的協議版本;
bereq.backend:指明要調用的後端主機;
req.http.Cookie:客戶端的請求報文中Cookie首部的值;
req.http.User-Agent ~ "chrome"
beresp.*, resp.*:
beresp.http.HEADERS
beresp.status:響應的狀態碼;
reresp.proto:協議版本;
beresp.backend.name:BE主機的主機名;
beresp.ttl:BE主機響應的內容的餘下的可緩存時長;
obj.*
obj.hits:此對象從緩存中命中的次數;
obj.ttl:對象的ttl值
server.*
server.ip:varnish主機的IP;
server.hostname:varnish主機的Hostname;
client.*
client.ip:發請求至varnish主機的客戶端IP;
用戶自定義:
set
unset
vcl_backend_response
覆蓋某些URL的緩存時間
剝離Set-Cookie不需要的頭部欄位
剝離Vary頭部欄位
將helper-headers添加到對象以用於禁止
清理伺服器響應
應用其他緩存策略
vcl_backend_response採用以下其中之一會被終止:deliver,retry,abandon。
deliver終止動作可以或者可以不依賴於後端的響應插入對象到緩存中。
retry操作使Varnish通過調用vcl_backend_fetch子程式再次將請求傳輸到後端。
abandon操作會放棄來自後端的任何響應。
後端可能會響應一個304HTTP頭部,當有時間戳if-modified-since在http頭部,且請求對象沒能被修改時304響應會發生。
如果請求觸及一個非新鮮的對象,Varnish將If-Modified-Since頭的值添加t_origin到請求中,並將其發送到後端。
304響應不包含消息正文,因此Varnish使用緩存中的實體構建響應,304響應更新緩存對象的屬性。
內建vcl_backend_response
sub vcl_backend_response {
if (beresp.ttl <= 0s ||
beresp.http.Set-Cookie ||
beresp.http.Surrogate-control ~ "no-store" ||
(!beresp.http.Surrogate-Control &&
beresp.http.Cache-Control ~ "no-cache|no-store|private") ||
beresp.http.Vary == "*") {
/*
* Mark as "Hit-For-Pass" for the next 2 minutes
*/
set beresp.ttl = 120s;
set beresp.uncacheable = true;
}
return (deliver);
}
vcl_backend_response內建子進程被設計於避免緩存那些不希望的情況。
例如,避免緩存cookies響應,帶有set-cookie http頭域的響應,這個內建子進程也避免請求serialization,這個在waiting state選項中有描述。
為避免請求serialization,beresp.uncacheable被設定為true,並輪流創建以hit-fot-pass對象。
hti-fot-pass詳細解釋了這個對象的類型。
如果你仍然決定通過採用自己設定的來跳過內建vcl_backend_response子進程,請確保絕不要設定beresp.ttl為0。
如果你跳過內建子進程並設置TLL值為0,可以有效地從緩存中刪除最終有可能用於避免請求serialization的對象。
berep.ttl的初始值
在varnish運行vcl_backend_response前,beresp.ttl變數就已經被設定了。
beresp.ttl用它在下麵找到的第一個值進行初始化:
Cache-Control響應頭欄位中的s-maxage變數
Cache-Control響應頭欄位中的max-age變數
Expires響應報頭欄位
default_ttl參數
預設情況,下麵的狀態碼會被緩存:
200:ok
203:非權威信息
300:多種選擇
301:永久移動
302:暫時移動
304:沒有修改
307:臨時重定向
410:gone
404: Not Found
你可以不採用上面列出的而緩存其他狀態碼,但你需要在vcl_backend_response中給beresp.ttl設置一個正值。
因為beresp.ttl的設置是在vcl_backend_response執行之前,你可以修改cache-control頭域的導引而不英雄beresp.ttl,反之亦然。
後端響應可能包括共用緩存s-maxage的最大響應頭欄位,通過所有varnish服務該欄位覆蓋了所有max-age值。
例如,如果後端發送cache-control:max-age=300,s-maxage=3600,所有varnish installations將緩存帶有一個age值大於等於3600秒的緩存對象,這就意味著在age為301到3600s間的響應將不會被客戶端web瀏覽器緩存,這是因為age的值超過了max-age。
一個明智的方法是使用s-maxage指令來指示varnish緩存響應。然後,在遞送響應前使用vcl_backend_response上的regsub()來刪除s-maxage指定。採用這個方法,你可以為varnish servers安全地使用s-maxage,併為客戶端設置max-age為持續緩存。
警告 :
請記住,刪除或更改Age響應標題欄位可能會影響響應在下游的處理方式。刪除Age欄位的影響取決於下游中間件或客戶端的HTTP實施。例如,假設您有三個varnish伺服器串列設置。如果您刪除Age第一個Varnish伺服器中的欄位,則第二個Varnish伺服器將假定Age=0。在這種情況下,您可能會無意中將陳舊的對象傳遞給客戶端。
示例:
1.設置.jpg urls的TTL設置為60秒
sub vcl_backend_response {
if (bereq.url ~ "\.jpg$") {
set beresp.ttl = 60s;
}
}
上面的例子將以.jpg結尾的所有URL緩存60秒。請記住,內置的VCL仍然被執行。這意味著帶有Set-Cookie欄位的圖像不會被緩存。
2.緩存.JPG 60秒僅當s-maxage不存在
sub vcl_backend_response {
if (beresp.http.cache-control !~ "s-maxage" && bereq.url ~ "\.jpg$") {
set beresp.ttl = 60s;
}
}
VCL子程式
VCL子進程,在其中定製Varnish的行為。
VCL子常式可用於:
添加自定義標頭,更改Varnish錯誤消息的外觀,在Varnish中添加HTTP重定向功能,清除內容以及定義緩存對象的哪些部分是唯一的。
註意
強烈建議儘可能讓預設的內置子程式,內置子程式的設計考慮到安全性,這通常意味著它們可以合理的方式處理VCL代碼中的任何缺陷。
vcl_recv
規範化客戶端輸入
選擇一個後端Web伺服器
重新編寫Web應用程式的客戶端數據
根據客戶端輸入決定緩存策略
訪問控制列表(ACL)
安全屏障,例如針對SQL註入攻擊
修複錯誤,例如index.htlm- >index.html
vcl_recv是Varnish第一個VCL子進程,將客戶端請求解析為其基本數據結構之後執行。
vcl_recv有四個主要用途:
修改客戶端數據以減少緩存的多樣性。
決定使用哪個Web伺服器。
根據客戶端數據決定緩存策略。
執行特定Web應用程式所需的重寫規則。
在vcl_recv你可以執行以下終止操作:
pass:它通過緩存查找,但它執行Varnish請求流的其餘部分。 pass不會將來自後端的響應存儲在緩存中。
pipe:此操作創建一個全雙工管道,將客戶端請求轉發到後端,且不查看其內容。後端回覆被轉發回客戶端且不緩存其內容。由於Varnish不再嘗試將內容映射到請求上,因此任何子進程的請求發送給活動連接將被通過pipe轉發。pipe請求不會出現在任何日誌中。
hash:它在緩存中查找請求。
purge:它在緩存中查找請求以便刪除它。
synth -從Varnish生成合成響應。這種合成響應通常是一個帶有錯誤信息的網頁。 synth也可以用來重定向客戶端請求。
同樣可以使用vcl_recv來設置以下安全措施。varnish不是入侵檢測系統的替代品,但仍可以用來提前阻止一些典型的攻擊。簡單訪問控制列表(ACL)也可以應用到vcl_recv上。
內建的vcl_recv子進程不會緩存所有你想要的,同時也最好不要緩存錯誤內容而是把它們發送給錯誤的用戶。
重新訪問內置的vcl_recv:
sub vcl_recv {
if (req.method == "PRI") {
/* We do not support SPDY or HTTP/2.0 */
return (synth(405));
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "DELETE") {
/* Non-RFC2616 or CONNECT which is weird. */
return (pipe);
}
if (req.method != "GET" && req.method != "HEAD") {
/* We only deal with GET and HEAD by default */
return (pass);
}
if (req.http.Authorization || req.http.Cookie) {
/* Not cacheable by default */
return (pass);
}
return (hash);
}
例子:
基本設備檢測
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_pass
進入pass模式是調用
sub vcl_pass {
return (fetch);
}
當上一層子進程返回pass動作後才會調用vcl_pass子進程,這動作的請求是在pass模式中設置的,vcl_pass通常作為一個重要的catch-all,服務於vcl_hit和vcl_miss執行結果。
vcl_pass可能會返回是三個動作:fetch、synth、或者是restart。
當返回的的是fetch時,正在進行的請求就採用pass模式。
採用pass模式從請求中抓取的對象不被緩存,但會傳遞到客戶端。
synth和restart返回的動作會調用相關的子進程。
hit-for-pass
當一個對象不應該被緩存是使用
hit-for-pass對象取代抓取的對象
存在TTL
一些請求就不應該被緩存,一個典型的例子就是當一個請求頁中含有set-cookie響應頭部時,且必須並只能把它遞送給所需的客戶端。
因此你可以告訴varnish創建個hit-for-pass的對象並存儲這個對象到緩存,而不是存儲抓取的這個對象,分散式的請求被採用pass模式處理。
當一個對象不需要被緩存是,beresp.uncacheable變數會設置為true。
結果,cacher進程會保持對hit-for-pass對象的hash散列應用,這種情況下,對請求的查找操作會傳遞給hash來找個hit-for-pass對象,如此類的請求會被vclpass子進程中的pass模式給處理。
如同其他緩存對象一樣,hit-for-pass對象也有一個TTL(生命周期)。一旦生命周期過了,這個對象就會從緩存上刪除。
vcl_backend_fetch
sub vcl_backend_fetch {
return (fetch);
}
vcl_backend_fetch 可以從vcl_miss或vcl_pass中調用。當vcl_backend_fetch從vcl_miss中調用時,抓取的對象會被緩存。如果vcl_backend_fetch被從vcl_pass中調用時,抓取的對象也不會被緩存的,即使是obj.ttl或obj.keep變數的值比0大。
一個相關的變數是bereq.uncacheable,這個變數指示出從後端來的對象請求是否被緩存。當然從pass請求中來的對象是絕不被緩存的。
vcl_backend_fetch有倆個可能的終端操作,fetch或abandon。fetch動作發送請求給後動,abandon動作調用vcl_synth子進程。內建vcl_bakend_fetch子進程只返回fetch動作。
後端響應被vcl_backend-response還是vcl_backend_error處理取決於響應來之於那個服務。如果Varnish收到語法正確的HTTP響應,則Varnish將控制權交給vcl_backend_response。語法正確的HTTP響應包括HTTP 5xx錯誤代碼。如果Varnish沒有收到HTTP響應,則將控制權交給vcl_backend_error。
vcl_hash
定義什麼是唯一的請求
vcl_hash終是在vcl_recv後,或者另個子進程範圍hash動作關鍵詞。
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
return (lookup);
}
vcl_hash定義要用於緩存對象的hash key。
Hash key將一個緩存對象與另一個緩存對象區分開來,預設的VCL為vcl_hash添加主機名或ip地址,同時添加請求的url給cache hash。
vcl_hash的一個用法是在cache hash上添加用戶名來識別用戶指定的數據,當然緩存用戶數據時應該謹慎進行,一個更好的選擇可能是hash每個會話緩存對象。
vcl_hash子進程返回lookup操作關鍵字,不像其他動作關鍵詞,lookup是一個操作,而不是子進程,在vcl_hash後的下個狀態取決於在緩存中lookup的查找。
當lookup操作沒能匹配到任何hash時,它會創建一個帶有busy標誌的對象並存儲在緩存中,然後,請求會被髮送到vcl_miss子進程中,一旦請求被處理busy標誌會被刪除,並從後端的響應中更新對象。
隨後遇到busy標記的對象請求將被髮送到等待列表中,這個等待名單旨在提高響應性能,這個在waiting state 選項中有解釋。
註意:一個高速緩存散列可以指代一個或多個對象變數。對象變數是基於Vary頭域的創建的。在一個緩存散列下保留多個變數是比較好的做法,而不是每個變數創建一個散列。
vcl_hit
在lookup操作之後執行,調用vcl_hash,找到(hits)在緩存上的對象。
sub vcl_hit {
if (obj.ttl >= 0s) {
// A pure unadultered hit, deliver it
return (deliver);
}
if (obj.ttl + obj.grace > 0s) {
// Object is in grace, deliver it
// Automatically triggers a background fetch
return (deliver);
}
// fetch & deliver once we get the result
return (fetch);
}
vcl_hit子進程通常通過調用含有deliver,restart或者synth的return()來進行終止。
如果對象的TTL+grace time沒有過時的話,返回的deliver會控制vcl_deliver。
如果過時時間超過了TTL,但沒有超過TTL+grace time,deliver會調用與vcl_deliver同步的background fetch。
background fetch是一種非同步調用,用來插入一個新的請求對象到緩存中。grace time會在grace模式選項中有解釋。
restart重啟傳輸,並增加重啟計數器設定值。如果重啟的次數比max_restarts設定的值要大,varnish會發出一個guru mediation的錯誤。
synth(status code,reason)返回指定狀態碼給客戶端並丟棄請求。
vcl_miss
如果一個請求對象沒有被lookup操作找到時子進程會被調用。
包含有是否嘗試從後端檢索文檔以及使用那個後端的策略。
sub vcl_miss {
return (fetch);
}
子進程vcl_hit和vcl_miss是相關的。你很少調用他們,因為HTTP請求投吧的修改通常是在vcl_recv中進行。但是,如果你不希望發送X-Varnish頭部給後端服務,你可以把它移動動vcl_miss或vcl_pass中。基於這種情況,你可以使用unset bereq ,http,x-varnish。
vcl_deliver
所有請求流程的公共最後退出點,除了通過vcl_pipe的請求。
經常用於添加和移除debug-headers。
sub vcl_deliver {
return (deliver);
}
vcl_deliver子進程是簡單的,同樣也是對修改varnish的輸出很有用的。如果你需要刪除一個頭部,或添加一個不應該存儲在cache中的頭部,vcl_deliver可以勝任這個工作。
在vcl_deliver中常用的且被可被修改的變數是:
resp.http.*:發送個客戶端的頭部,它們可以被set和unset。
resp.status:狀態碼為200,404,503等
resp.reason:被返回給客戶端的http狀態信息
obj.hit:在對象上的cache-hits的數。因此,0代表miss,可以評估這個變數來輕鬆地顯示響應是來自緩存命中還是未命中。
req.restarts:在VCL中發出的重啟次數 - 如果沒有發生,則返回0。
vcl_synth
用於在Varnish中生成內容
錯誤消息可以在這裡創建
其他用例:重定向用戶(301/302重定向)
vcl/default-vcl_synth.vcl:
sub vcl_synth {
set resp.http.Content-Type = "text/html; charset=utf-8";
set resp.http.Retry-After = "5";
synthetic( {"<!DOCTYPE html>
<html>
<head>
<title>"} + resp.status + " " + resp.reason + {"</title>
</head>
<body>
<h1>Error "} + resp.status + " " + resp.reason + {"</h1>
<p>"} + resp.reason + {"</p>
<h3>Guru Meditation:</h3>
<p>XID: "} + req.xid + {"</p>
<hr>
<p>Varnish cache server</p>
</body>
</html>
"} );
return (deliver);
}
你可以創建合成響應,例如,在vcl_synth上的個性化錯誤信息。調用這個子進程你可以做:
return (synth(status_code, "reason"));
註意synth不是一個關鍵字,而是個帶有參數的函數。
你必須為vcl_synth明確地返回status code和reason參數。在resp.http上設置合成響應的頭部。
註意:
從 vcl/default-vcl_synth.vcl註意到 {" and "}可以用於創建多行的欄位。這個不僅限於synthetic()函數,在其他地址也可以使用。
vcl_synth定義的對象絕不在緩存上存儲,對立與vcl_backend_error定義的對象。
例子:
使用vcl_synth重定向請求
sub vcl_recv {
if (req.http.host == "www.example.com") {
set req.http.location = "http://example.com" + req.url;
return (synth(750, "Permanently moved"));
}
}
sub vcl_synth {
if (resp.status == 750) {
set resp.http.location = req.http.location;
set resp.status = 301;
return (deliver);
}
}