Varnish的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);
					}
				}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM