1. 程式人生 > >33---varnish 4.0 工作機制和配置例項解析(上)

33---varnish 4.0 工作機制和配置例項解析(上)

這兩天在看varnish,感慨一句,這軟體真不錯!!! ============ 功能&程式包: 首先varnish是反向HTTP代理,是具有強大快取功能的代理,因此也被稱之為web加速器或http加速器。官網對它的描述是不僅是代理,還說它具有web應用防火牆、抵禦DDOS、熱連結保護、負載均衡等功能,看上去很厲害的樣子。下面看看它是由哪些部分組成的吧,先貼一張圖:
varnish軟體包中的關鍵程式:
1.varnishd
2.varnishadmin 3.varnish日誌管理&狀態查詢工具集 ----- 1. varnishd varnishd是主程式啟動後生成兩個程序,Manager Process(父程序)和Cacher Process(子程序),前者在啟動時讀入配置檔案(稍後會講到)並fork()出後者,同時具有CLI介面接收管理員的指令以用於管理後者,後者是真正處理快取業務的程序,其下又可以生成若干執行緒,這些執行緒各司其職,完成快取業務的處理並記錄日誌。這裡的Manager程序和Cacher程序類似於Nginx的Master和Worker,可見伺服器軟體的設計思路多麼相似。 既然Cacher Process是處理快取業務的程序,那它監聽在哪個埠?又是如何處理http請求的呢?哪些資料應該快取,哪些資料不要快取?如果請求的資料快取區中沒有又該怎麼處理?快取的物件是怎麼組織儲存的?Don't worry,下文會給出所有的答案。 兩類配置檔案
/etc/varnish/any_name.vcl  varnish是如何制定快取策略的呢?(所謂快取策略就是控制http請求要如何拿到所請求的資源,查不查快取,怎麼查,查不到怎麼辦等)就是通過varnish自己的語言VCL(Varnish Configuration Language)來控制的,我們使用這種語言把要對報文如何處理的動作寫到 .vcl 檔案中,然後讓 varnishd 載入這個檔案就ok了,下文會詳細介紹如何使用。使用yum安裝varnish後,系統會在/etc/varnish下生成一個default.vcl檔案,我們可以把vcl控制語句寫在這裡面,也可以寫在該目錄下自建的 .vcl 檔案中。該檔案的具體配置將在後文詳細說明,需要額外強調的是 .vcl 檔案需要被gcc編譯之後才能夠被載入使用,所以要一定要預先安裝gcc。 /etc/varnish/varnish.params 
主要定義了啟動的時候使用哪個vcl作為啟動時的快取策略,作為反向代理監聽在哪個IP的哪個Port上,開啟的CLI介面監聽在哪個IP的哪個Port上。啟動的時候設定Cacher Process有多少執行緒池,每個執行緒池有多少執行緒。還有很多與快取業務處理相關的引數(如 default_grace),這些引數可以在啟動時設定(有預設值),也可以在啟動後修改(啟動時也可以指定哪些引數是啟動後只讀的)。 2. varnishadmin 我們已經知道Master Process具有CLI介面,且 /etc/varnish/varnish.params 中定義了Manager Process的監聽介面,那 varnishd 啟動後如何接受管理員的調整指令如變更快取策略,增大執行緒池中最大執行緒數等指令呢?用varnishadm,varnishadm可以理解為就是 varnishd 服務的專用客戶端。不過官網強烈建議Manager Process的CLI介面僅監聽在本地127上,遠端連結的話僅有一層保障,即雙方都知道的一個Secret檔案(/etc/varnish下)。若要連線到本地的varnishd,則直接鍵入varnishadm即可;若要連線到遠端的varnishd,則需要執行 varnishadm -S path_of_secret_file -T remote_host:port, -S選項指明瞭用哪個Secret檔案驗證身份,客戶端和伺服器端要持有相同的Secret檔案。 3. 日誌管理工具集
和普通的web伺服器類似,varnish做了什麼工作,也會寫在日誌裡。由於varnish產生的日誌量較大,所以說其日誌是存在記憶體中的,這樣就減少了磁碟IO。記憶體中的日誌區預設是80M左右,分為兩部分,第一部分是counter區(計數器區),第二部分是http請求資料區。80M的空間寫滿了怎麼辦?從空間開始處繼續寫,那不就覆蓋了之前的日誌嗎?對!覆蓋了。那能否把日誌記錄在磁碟上呢?可以!不過這個不是本文重點要說的。 下面就開始看看如何配置使用varnish,實驗拓撲如下:
實驗前各個伺服器的服務都正常執行。 7A上啟動varnishd,作為快取伺服器使用。/etc/varnish/varnish.params 配置如下: RELOAD_VCL=1 //設定不重啟varnishd時可過載vcl VARNISH_VCL_CONF=/etc/varnish/test.vcl //啟動varnishd時載入的快取策略檔案,如果不給出,則varnishd啟動時會報錯。 VARNISH_LISTEN_ADDRESS=192.168.10.133 //作為反代,監聽的地址 VARNISH_LISTEN_PORT=80 //作為反代,監聽的埠,若直接面向網際網路則需要設定為80;若前面有排程器,就隨意了。 VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 //CLI管理介面監聽的IP VARNISH_ADMIN_LISTEN_PORT=6666 //CLI管理介面監聽的埠 VARNISH_STORAGE="malloc,256M" //快取物件儲存方式 VARNISH_USER=varnish //設定Cacher Process程序的uid和gid,yum安裝varnish過程中會建立varnish使用者。Manage Process的uid和gid是root。 VARNISH_GROUP=varnish DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300" //另一種形式的varnishd啟動時的引數配置,如果與上面的引數配置衝突,則以這裡的為準。預設配置中該行被註釋掉,也就是說未設定的引數使用預設配置。 補充知識點1:快取物件被快取在哪裡? varnish支援4種方式儲存快取物件: 1. malloc(key-value均儲存在記憶體中) 2. file(key儲存在記憶體中,varlue即content儲存在disk) 3. persistent(官方手冊不建議使用) 4. mse(商業版varnish plus才提供) 所以我們之關注前兩種儲存方式如何配置使用。     配置舉例:     VARNISH_STORAGE="malloc,256M"      VARNISH_STORAGE="file,/root/mycache.bin,256M" //啟動varnishd時,儲存快取物件的檔案即建立。要求所在的目錄已經存在,否則出錯。 在/etc/varnish下建立test.vcl,其內容如下: vcl 4.0;    //寫在首個非註釋行     backend default {   .host = "192.168.20.133";   .port = "80"; } 設定完成後,啟動 systemctl start varnish.service ,在6B上啟動Nginx並寫一個index.html顯示一行文字:This is Nginx test html page. 在6A上 curl 192.168.10.133,發現可以正常訪問。在7A上執行 varnishstat 觀察 MAIN.cache_miss=1,無 MAIN.cache_hit。 再在6A上執行 curl 192.168.10.133,此時7A上有MAIN.cache_hit=1 表明頁面確實快取了。(q鍵退出varnishstat) =============== 如何寫 vcl 程式碼? 理解subroutine 下面看下如何寫vcl檔案,在寫vcl檔案之前,我們得先明白什麼是“狀態引擎”,這個詞聽上去有點裝x,其實就是subroutine,也就是一段處理http請求的程式碼段而已。客戶端的http請求到了varnish伺服器,如何處理?查不查快取?從backend拿到的資料,何時快取何時不快取?這些都可以通過vcl檔案中的subroutine來控制。vcl對報文的處理顆粒度是每 req/resp/bereq/beresp 的,即客戶端與varnish伺服器間的請求/響應,varnish伺服器與backend間的請求/響應,req/resp/bereq/beresp報文各自被單獨處理,互不影響。 我們先看一段簡單的程式碼,找找感覺。 sub vcl_recv {   if(req.method != "GET" && req.method != "HEAD"){   return (pass);   }   return (hash); } 這段程式碼的意思就是vcl_recv這個subroutine對某次來自客戶端的請求http報文檢查其方法是否為GET或HEAD以判斷接下來哪一個subroutine處理,如果不是則進入pass處理,如果是則進入hash處理。return()的括號中指定的是下一步處理動作,括號中的動作一般都是指示處理流程跳轉到其他的subroutine中。程式碼中的 req.method 是內建變數,對每一個req/resp/bereq/beresp,varnish會把報文中的某些欄位和內建變數做對映,讀寫這些內建變數就是讀寫http報文內容。 現在我們可以認為subroutine要完成的工作就是處理http報文,再講的細一點就是,判斷報文中的資訊是否滿足指定條件,滿足的話如何處理,不滿足的話如何處理。所以subroutine的結構很簡單,就是判斷+下一步動作,僅此而已。 subroutine分為內建subroutine和自定義subroutine,自定義的subroutine需要在內建subroutine中使用call呼叫之才能使用。varnish內建的subroutine是以vcl_開頭的,而自定義的subroutine不能以vcl_開頭。個人感覺,如果要處理的問題不復雜,不必自定義subroutine,直接將要處理的邏輯寫在內建subroutine既可。 回頭看看我們剛才做過的實驗,在test.vcl下並沒有加vcl程式碼,為什麼頁面依然被快取了呢?就是因為有內建subroutine,而內建subroutine中有預設的vcl程式碼。用varnishadm連線到本機的varnishd,然後執行vcl.show -v boot就可以看到內建subroutine及其內部定義的預設處理程式碼了。如果我們要把自己的vcl處理程式碼寫到內建subroutine,如vcl_recv,如何操作?在test.vcl把你的處理程式碼直接加到sub vcl_recv後的{}中即可。內建subroutine中的預設vcl程式碼總在我們的程式碼之後才執行(目的就是為了兜底),當然如果我們的程式碼執行過程中return到其他subroutine就另當別論了。 sub vcl_recv {   把你的處理程式碼寫到這裡 } 內建subroutine vcl_recv vcl_deliver vcl_synth vcl_hash vcl_hit vcl_miss vcl_pass vcl_pipe vcl_purge vcl_backend_fetch vcl_backend_response vcl_backend_error 內建函式: regsub(str,regex,sub) //在字串str中匹配模式regex,並將匹配到的第一個結果替換為sub regsuball(str,regex,sub) //在字串str中匹配模式regex,並將匹配到的所有結果替換為sub ban(bollean expression) //清理快取中滿足表示式條件的快取物件 hash_data(input) systhetic(str) 內建關鍵字: call subroutine return(action) //這裡的action就是下一步要處理的動作,即哪個subroutine要接續處理。合法的action有:                         lookup,synth,purge,pass,pipe,fetch,deliver,hash,restart,retry,abandon。並非所有的action都對應一個subroutine new set unset 常見的內建變數: client.ip    //客戶端ip req.backend_hint    //要選擇哪個後端資源伺服器(或叢集)做資源fetching req.http.*    //http頭部 req.method    //請求方法 req.url    //請求的url req.restarts    //請求被重新投入狀態機處理的次數 bereq.http.*    //http頭部 bereq.method    //請求方法 bereq.url    //請求的url resp.http.*    //響應報文的http頭部 resp.status    //相應給客戶端的狀態碼 resp.reason    //相應給客戶端的原因短語 beresp.http.*    //後端返回響應報文的http頭部 beresp.status    //後端返回的狀態碼 beresp.reason    //後端返回的原因短語 beresp.backend.ip    //後端資源伺服器的ip beresp.backend.name    //後端資源伺服器的name beresp.ttl    //資源的ttl值 beresp.grace    //資源的grace值 beresp.keep    //資源的keep值 beresp.uncacheable    //置位則be-response中的物件將不被快取 obj.hits    //資源被命中的次數 obj.http.*    //資源的http頭部 obj.status    //後端返回資源時給出的狀態碼 obj.reason    //後端返回資源時給出的原因短語 obj.ttl    //資源剩餘的ttl值 obj.grace    //資源剩餘的grace值 obj.keep    //資源剩餘的keep值 obj.uncacheable    //資源是否不被快取的標誌值 一般來講變數req.*的值會賦給對應的bereq.*,而變數beresp.*的值會賦給對應的resp.* 改變前者會影響後者。obj.*的初始值大多來自beresp.*,它們可以被賦給resp.*並傳給客戶端瀏覽器快取。 寫完vcl後如何執行? 可以通過 varnishadm 連線到 varnishd,然後執行 vcl.load obj_name test.vcl 把test.vcl編譯成名為obj_name(自己取名)的策略物件,然後使用vcl.use obj_name 即可使用該策略物件。在這之前,請先使用 vcl.list 檢視服務啟動後使用的是哪一個策略物件,沒錯,名為boot的策略物件。 =====詳解內建subroutine===== 典型的內建subroutine有如下幾個: vcl_recv vcl_pass vcl_backend_fetch vcl_backend_response vcl_hash vcl_hit vcl_miss vcl_deliver vcl_synth 內建rubroutine中有基於安全考慮加進去兜底的預設vcl程式碼,所以官方手冊強烈建議使用者把自己的處理邏輯放到內建的vcl subroutine中。我們啟動varnishd後,test.vcl會被變異並載入,通過varnishadm vcl.show -v boot 即可檢視系統內建的vcl subroutine以及subroutine中預設的vcl程式碼。 -------------------------------- vcl_recv vcl_recv是客戶端請求報文被解析後第一個被執行的subroutine,在該subroutine我們可以新增vcl程式碼完成諸多功能,如: 1.快取未命中的話去後端哪臺主機請求資源    2.控制快取策略,如僅僅針對某些url做快取    3.完成url重寫    等功能。 在vcl_recv中我們可以return如下action: pass:對於http請求,跳過快取查詢這一步去後端server請求資源,雖然沒有跳過了查快取這一步,但是後續的步驟該走的還是得走,該過的subroutine還得過。這種情況下從後端server拿到的資源不會被快取。 pipe:對於http請求,跳過所有步驟,也就是不用過任何subtoutine的處理,直接去後端server請求資源,這種情況下拿到的資源也不快取,就像經過varnish搭建了一條客戶端到伺服器的一條管道。且後續同一個tcp連線的所有request都會直接被送進管道處理。被pipe處理的請求不記錄日誌。 hash:進行哈西計算並查詢快取。 purge:在快取中查詢快取物件並清除之。 synth:合成http響應報文,通常是錯誤報文,此外,synth也可以用於重定向客戶端請求。 例項A: sub vcl_recv {   if (req.httpd.User-Agent ~ "iPad"   req.httpd.User-Agent ~ "iPhone"   req.httpd.User-Agent ~ "Android") {   set req.http.X-Device = "mobile";   } else {   set req.http.X-Device = "desktop";   } } 作用:根據使用者請求報文中的User-Agent判斷使用者的瀏覽器型別為mobile或desktop,然後在請求報文中加入名為X-Device的頭部,並設定其值為mobile或desktop,表示客戶端平臺。響應報文(無論來自快取還是後端server)可以根據客戶端平臺型別構造併發送給客戶端。 例項B: sub vcl_recv {   set req.http.host = regsub(req.http.host,"^www\.",""); } 作用:把 www.xxx.yyy 按照xxx.yyy處理,如 www.web1.com 按照 web1.com 處理 例項C: sub vcl_recv {   if (req.http.host == "sport.web1.com") {   set req.http.host = "web1.com";   set req.url = "/sport" + req.url;   } } 作用:重寫 http://sport.web1.com/ 到 http://web1.com/soprt/  例項D: sub vcl_recv {   if (req.http.host ~ "^sport\.") {   set req.http.host = regsub(req.http.host,"^sport\.","");   set req.url = regsub(req.url,"^","/sport");   } } 作用:重寫 http://sport.xxxx/ 到 http://xxxx/sport/ -------------------------------- vcl_pass 當一個subroutine執行return(pass)時,即跳到vcl_pass執行處理邏輯,vcl_pass會把一個請求設定為pass模式,在vcl_pass中我們可以return: 1.fetch 2.synth 3.restart。 當return一個fetch時,被設定為pass模式的請求得到的物件不會被快取而直接響應給客戶端。返回synth時進入vcl_synth合成響應報文,返回restart則從狀態機開始處再開啟一輪處理請求報文的動作。 -------------------------------- 補充:hit-for-pass 有些請求得到的響應物件不應該被快取,一個典型的例子就是當響應報文中含有Set-Cookie頭部時,因為該響應物件僅僅是針對單個使用者的。這種場景下,我們可以設定varnish生成一個hit-for-pass物件,然後快取之,而不是直接快取響應物件。 當一個從後端server拿到的物件不需要被快取時,我們可以 set bereq.uncacheable = true 這樣的話Cacher Process就會維護一個指向hit-for-pass的鍵值對,當再次有請求查到該哈西鍵時,會找到一個hit-for-pass的快取物件,然後跳轉到vcl_pass處理,在vcl_pass中請求被設定為pass模式。 和正常的快取物件一樣,hit-for-pass也有其ttl,當ttl一過時一樣會被從快取區清理掉。 -------------------------------- vcl_backend_fetch sub vcl_backend_fetch {   return (fetch); } vcl_backend_fetch可以在vcl_miss或vcl_pass中被呼叫(當然是通過return),當其被vcl_miss呼叫時在後端server拿到的物件會被快取,而當其被vcl_pass呼叫時在後端拿到的物件就不會被快取,即使物件的obj.ttl和obj.keep變數值大於0。 還有一個與快取相關的變數 bereq.uncacheable, 該變數指明瞭後端返回的被請求的資源物件是否被快取。然而,從pass中的請求得到的物件會忽略bereq.uncacheable的值,而不快取之,上文也提到了。 vcl_backend_fetch中我們自己加入的程式碼可以return到fetch或者abandon。前者會將請求代理髮送到後端,而後者會呼叫vcl_synth。vcl_backend_fetch中的預設程式碼return的是fetch。來自後端的響應會被vcl_backend_response或vcl_backend_error處理,這取決於響應報文。 如果varnish接收到一個語法正確的http響應(含5xx錯誤碼的http響應),則進入vcl_backend_response處理。若varnish沒有收到http響應則進入vcl_backend_error處理。 -------------------------------- vcl_hash vcl_hash的作用是對一個http請求做哈西計算。 預設的vcl_recv程式碼是跳到vcl_hash的,任何subroutine都可以通過return(hash)跳轉到vcl_hash。 內建的vcl_hash程式碼如下: sub vcl_hash {   hash_date(req.url);   if (req.http.host) {   hash_data(req.http.host);   }   else {   hash_data(server.ip);   }   return (lookup); } vcl_hash為將要快取的物件定義一個哈西鍵,快取物件的鍵是特有的,不同的快取物件具有不同的鍵。vcl_hash中的內建程式碼使用請求的url和hostname/IP來計算哈西鍵。 vcl_hash的一個用途就是使用user-name來計算哈西鍵以標識一個特定使用者的資料,然而此功能應該謹慎使用。一個更好的替代方案是基於session來哈西某個物件。 vcl_hash執行到最後會return到lookup,lookup並不是subroutine,而只是一個操作。vcl_hash之後進入哪個subroutine處理,取決於lookup在快取中找到了什麼。 當lookup沒有匹配到任何哈西鍵,它會建立一個具有busy標誌的物件並把它扔到快取區。之後跳到vcl_miss處理http請求,當http請求被後端處理後,快取物件內容就會被後端返回的內容更新,同時busy標誌也會被移除。 若一個請求命中了有busy標誌的快取物件,則該請求會被送到waiting list,waiting list是為了提高響應效能而設計的。 -------------------------------- vcl_hit lookup若匹配到哈西鍵,則會跳轉到vcl_hit。vcl_hit的預設程式碼如下: sub vcl_hit {   if (obj.ttl >= 0s) {   return (deliver);   }   if (obj.ttl + obj.grace > 0s) {   return (deliver);   }   return (fetch); } vcl_hit執行到最後一般都是return: deliver,restart或synth。 deliver會使處理流程跳轉到vcl_deliver,如果該物件的ttl+grace沒有過時的話。{153頁未翻譯完} restart是把http請求扔到狀態機的入口當作一個新的http請求重新處理,同時restart counter這個計數器加一,還記得存放log的記憶體分為兩部分嗎?所有的counter都儲存在第一部分。當restart counter的值高於 max_restarts 時,varnish會丟擲一個guru meditation錯誤。(max_restarts是一個引數,可以通過varnishadm param.show max_restarts檢視其值) synth(status_code,reason)會丟棄本次request,並返回一個指定的http狀態碼給客戶端。 -------------------------------- vcl_miss lookup若沒有匹配到哈西鍵,則會跳轉到vcl_miss。 vcl_miss中可以加入程式碼,以決定是否去後端server請求資源,去哪臺後端server請求資源。 vcl_miss的預設程式碼如下: sub vcl_miss {   return (fetch); } 我們很少在vcl_hit和vcl_miss這兩個subroutine中新增自己的處理邏輯,因為對http請求頭部的修改通常都是在vcl_recv中完成的。然而,如果不想讓X-Varnish這個http頭部發送給後端server的花,可以在vcl_miss或vcl_pass中通過 unset bereq.http.x-varnish; 來實現。 -------------------------------- vcl_deliver 通常來說,對於一個http請求流程,vcl_deliver都是最後一個處理動作。但是經由vcl_pipe代理轉發到後端server的請求除外。 該subroutine常常被用來刪除debug-headers vcl_deliver的預設程式碼如下: sub vcl_deliver {   return (deliver); } 如果我們想修改響應給客戶端的報文頭部,如刪除或增加一個新頭部且不想改動後被快取,則可以在此操作。在vcl_deliver中我們常常用到 rest.http.* , resp.status , resp.reason , obj.hits , req.restarts  -------------------------------- vcl_synth 生成含有指定內容的http響應報文,並通過return(deliver)傳送給客戶端。vcl_synth的預設程式碼如下: 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); } 解釋:設定http頭部,然後呼叫synthetic()函式合成一個頁面,然後通過return到deliver把頁面傳送到客戶端。我們可以通過在指定的subroutine中return(synth(status_code,"reason_phrase")); 來呼叫vcl_synth並設定resp.http.status和resp.http.reason。需要注意的是這裡return的不是keyword,而是一個內建的具有引數的函式。 {“ 和 ”}用來指示多行字串 vcl_synth定義的頁面物件不會被快取,而vcl_backend_error定義的頁面物件會被快取。 --------------- 例項 ----------------- test.vcl中如下配置為公共部分,為節省版面,不重複寫在下文例項中: vcl 4.0; backend default {   .host = "192.168.20.133";   .port = "80"; } 例項1: sub vcl_recv {   if (req.http.host == "www.web1.com") {   return(synth(750,"Suibianxie."));   } } sub vcl_synth {   if (resp.status == 750) {   set resp.http.location = "http://web1.com" + req.url;   set resp.status = 301;   return (deliver);   } } 作用:當客戶端請求www.web1.com時,varnish會設定resp.status和resp.reason分別為750和Suibianxie,然後跳轉到vcl_synth中開始合成頁面並通過return到deliver把頁面傳送給客戶端。 例項2: sub vcl_deliver {   unset resp.http.Server;   if (obj.hits > 0) {   set resp.http.X-cache = "HIT";   } else {   set resp.http.X-cache = "MISS";   } } 作用:給客戶端的響應報文中增加一個X-cache頭部,當請求的資源命中時該頭部值為“HIT”,不命中時頭部值為“MISS”。同時把響應報文中的Server頭部去掉。 例項3: sub vcl_backend_error {   if (beresp.status == 503) {   set beresp.status = 200;   synthetic(   {"   <html>   <body>   <h1>We don't like ugly page!</h1>   </body>   </html>   "}   );   return (deliver);   } } 作用:varnish作為反代去向後端server拿資料,如果拿不到資料,則預設返回一個503的status code給客戶端,同時給出一個預定義的頁面(該頁面是寫在vcl_backend_error中的)。我們用例項3的程式碼重寫該頁面,給出200返回值和一個更加友好的頁面。 至此vcl如何使用應該有個基本的認識了,下面通過例項加深對它的掌握。 ================  vcl 程式碼使用例項: ----- 按需使快取失效(清理快取) 快取失效是快取策略的一個重要組成部分,varnish會使過期的快取物件自動失效,我們可以手動設定使快取物件失效。 1. HTTP PURGE 2. Banning 3. Force Cache Misses 強制不匹配    在vcl_recv中使用變數req.hash_always_miss  變數被設定為true,則varnish忽略已存在的物件而是從後端server請求之。  在vcl_recv中set req.hash_always_miss = true; 會使varnish去查快取,但是強制不匹配,然後想後端請求資源並快取,然後響應給客戶端。 1.PURGE 一個資源返回給不同的客戶端(手機,平板,桌面電腦)可能有不同的Vary頭部,因此同一個資源在快取中可能有多個不同的變體。通常來說HTTP PURGE會清理掉一個資源的所有的變體。就像GET一樣,PURGE也是http的一個method,當請求某資源但攜帶的是PURGE方法時,該請求也會像正常請求一樣被哈西計算,然後找到快取中的物件並清除之。PURGE只能針對單個資源的快取物件做清除,而不能使用正則表示式匹配清除一類快取物件。而且並不檢查後端server是否狀態正常就直接清除,也就是說如果你的後端server掛了,再有請求相同資源的request到來,那就真的只能呵呵了。 通過使用return(purge)來執行某次請求的已快取資源的清除,return(purge)後會進入vcl_purge繼續後續處理,vcl_purge的預設程式碼如下: sub vcl_purge {   return (synth(200,"Purged,Haha")); } 例項: acl purgers {  //設定acl,定義誰傳送的PURGE可以被處理   "127.0.0.1";   "192.168.10.0/24"; } sub vcl_recv {   if (req.method == "PURGE") {   if (!client.ip ~ purgers) {   return (synth(405,"Purge_Not_Allowed"));   }   return (purge);   } } sub vcl_purge {   set req.method = "GET";   return (restart); } 作用:清除掉某個資源的快取物件,然後把請求的方法設定為GET,再將請求重新扔到狀態機入口從頭開始新一輪的處理。這樣就會在快取區中生成一個新的快取物件。一般來說,快取物件未失效,但是後端server上的資源更新了,就可以這樣操作更新快取區的快取物件。(感覺好麻煩,直接shift+F5強制重新整理不行嗎?) 注意哦,這裡http的方法是PURGE是因為約定俗成,大家都這麼用。其實改成HAHA,或者HEHE,也可以照常工作。讓varnish檢查req.method是否為HAHA或HEHE即可。 2.Banning   使用內建的變數ban(regex)   通過匹配正則表示式來失效某個快取中的物件   通過varnishadm也可以使用banning 至此我們已經知道HTTP PURGE只能針對單個資源的快取物件做清除操作,如果我們想要對滿足某些條件的資源的快取物件做清除操作怎麼辦?用Banning,因為它支援正則表示式。兩種banning形式 a)通過varnishadm連線到varnishd,通過CLI介面執行 ban regexp,如 ban req.url ~ /image/a*\.jpg b)通過ban("regexp");實現 sub vcl_recv {   if (req.method == "BAN") {   ban ("req.http.host ==" + req.http.host + "req.url ==" + req.url);   return (tynth(200,"Ban_Rule_Added"));   } } 已快取的物件,如果被匹配到,則會像其他obj.ttl==0的快取物件一樣被清除。 Lurker-friendly Ban 正則表示式僅匹配obj.*的ban我們稱之為 Lurkerfriendly Ban。 ban lurker是Cacher Process的一個執行緒,它會定期檢查快取區中的obj是否匹配到表示式並處理之,檢查間隔通過varnishadm中 ban_lurker_sleep 引數設定,如果設定為0表示不檢查。 sub vcl_recv {   if (req.method=="BAN") {   ban("obj.http.x-url ~" + req.http.x-ban-url + "&& obj.http.x-host ~" + req.http.x-ban-host);   return (synth(200,"Ban_Added"));   } } sub vcl_backend_response {   set beresp.http.x-url=bereq.url;   set beresp.http.x-host=bereq.http.host; //beresp.*修改後會賦值給對應的obj.* } sub vcl_deliver {   unset resp.http.x-url;   unset resp.http.x-host; } 通過 curl -X Your_Method http://www.web1.com 來在訪問時指定方法,可以用cur -X來對PURGE和BAN進行測試。 ----- varnish作為排程 varnish畢竟不能快取所有的內容,所以總會有到後端server取資料的時候,這時候就牽扯到排程問題,下面我們就來說說排程。 varnish的強項是快取而非排程,所以它支援的排程演算法有限,有如下幾種: 1. round-robin 2. random(隨機種子可以是隨機數或者哈西鍵) 3. fallback(就找第一臺,第一掛了找第二,第二掛了找第三) 4. hash(保證了會話粘性) 同時varnish應該對後端server叢集有個健康狀態探測機制,和keepalived一樣能剔除有問題的後端server。 我們來看兩個完整的例子: vcl 4.0; import directors; backend bkd_srv1 { //注意:定義的backend後面都要被引用,否則報錯(vcc_err_unref引數控制)   .host="www.web1.com";   .port="80";   .probe={   .url="/"; //探測粒度為url,還可以細化到req報文的請求方法和頭部   .timeout=1s; //超市時間為1s   .interval=4s; //每4s探測一次   .window=5; //最近的5次探測,有3次ok就認為健康   .threshold 3;   } } backend bkd_srv2 {   .host="www.web2.com";   .port="80";   .probe={   .url="/"; //探測粒度為url,還可以細化到req報文的請求方法和頭部   .timeout=1s; //超市時間為1s   .interval=4s; //每4s探測一次   .window=5; //最近的5次探測,有3次ok就認為健康   .threshold=3;   } } sub vcl_init {   new rr_dir = directors.round_robin();   rr_dir.add_backend(bkd_srv1);   rr_dir.add_backend(bkd_srv2);   new ran_dir = directors.random();   ran_dir.add_backend(bkd_srv1,10);   ran_dir.add_backend(bkd_srv2,5); } sub vcl_recv {   set req.backend_hint=rr_dir.backend(); //上面定義了兩種排程,這裡使用round-robin   //set req.backend_hint=ran_dir.backend(req.http.cookie); //這裡的cookie也只是做隨機種子而已 } ====== vcl 4.0; import directors; backend bkd_srv1 {    //注意:定義的backend後面都要被引用,否則報錯(vcc_err_unref引數控制)     .host="www.web1.com";    //得保證varnish可以解析該FQDN     .port="80";     .probe={         .url="/";  //探測粒度為url,還可以細化到req報文的請求方法和頭部         .timeout=1s;     //超市時間為1s         .interval=4s;    //每4s探測一次         .window=5;      //最近的5次探測,有3次ok就認為健康         .threshold 3;     } } backend bkd_srv2 {     .host="www.web2.com";     .port="80";     .probe={         .url="/";  //探測粒度為url,還可以細化到req報文的請求方法和頭部         .timeout=1s;     //超市時間為1s         .interval=4s;    //每4s探測一次         .window=5;      //最近的5次探測,有3次ok就認為健康         .threshold=3;     } } sub vcl_init {         new img_srvs = directors.random(); //定義叢集,排程演算法為隨機         img_srvs.add_backend(bkd_srv1,5);  //定義伺服器叢集成員,此處僅一臺,權重為5         new app_srvs = directors.hash();  //定義叢集,排程演算法為哈西         app_srvs.add_backend(appsrv1,5);  //定義伺服器叢集成員,此處僅一臺,權重為5 } sub vcl_recv {         if (req.url ~ "(?i)\.(jpg|jpeg|png|gif)$" {             set req.backend_hint = imgsrvs.backend();  //若請求的是圖片,快取不命中則被髮往圖片叢集,叢集內輪詢         } else {             set req.backend_hint = appsrvs.backend(req.http.cookie);  //其他請求法王另一個叢集,可以根據cookie做會話粘性         } }
----- 快取資源的新鮮度 beresp.ttl標明再過多久資源物件就不新鮮了,物件不新鮮之後還會有個grace時間,某些場景下varnish可以使用處於grace狀態的物件響應客戶端。 sub vcl_hit {   if (obj.ttl >= 0s) {       return (deliver);   }   elsif (std.healthy(req.backend_hint)) {       return (fetch);   }   else {       if (obj.ttl+obj.grace>0s) {           return (deliver);       }       else {           return (synth(404,"IHAVENOTHING"));       }   } } 作用:如果資源新鮮,則響應給客戶端。如果不新鮮,看看能否去後端server拿,能拿到就快取併發送給客戶端。如果拿不到就看看是否過了grace時間,如果美國就返回之前快取的物件,如果過了,就合成錯誤頁面。 預設的obj.grace時間是10s,obj.ttl倒計時完之後,obj.grace就開始倒計時。這個定時器的值有三種方式設定: 1.來自後端server的Cache-Control中的stale-while-revalidate 2.在vcl程式碼中設定beresp.grace變數的值 3.varnishadm中改變default值:param.set default_grace 20s //改為20s
這篇文章中沒有涉及日誌查詢如何使用,有很多知識點還可以更細化,留給下一篇文章吧。 另附上兩張狀態引擎處理報文的流程圖,這兩張圖務必要非常熟悉。