一、Varnish簡介
Varnish 的作者Poul-Henning Kamp是FreeBSD的內核開發者之一,他認爲現在的計算機比起1975年已經複雜許多。在1975年時,儲存媒介只有兩種:內存與硬盤。但現在計算機系統的內存除了主存外,還包括了CPU內的L1、L2,甚至有L3快取。硬盤上也有自己的快取裝置,因此Squid Cache自行處理物件替換的架構不可能得知這些情況而做到最佳化,但操作系統可以得知這些情況,所以這部份的工作應該交給操作系統處理,這就是 Varnish cache設計架構。
varnish項目是2006年發佈的第一個版本0.9.距今已經八年多了,此文檔之前也提過varnish還不穩定,那是2007年時候編寫的,經過varnish開發團隊和網友們的辛苦耕耘,現在的varnish已經很健壯。很多門戶網站已經部署了varnish,並且反應都很好,甚至反應比squid還穩定,且效率更高,資源佔用更少。相信在反向代理,web加速方面,varnish已經有足夠能力代替squid。
二、Web緩存部分首部介紹
程序能夠緩存是因爲程序具有局部性,時間局部性和空間局部性。緩存一般存儲爲鍵值,key爲訪問路徑的url經過hash計算後的結果,value爲服務器內容,一般緩存存儲的是熱點數據,切緩存中存儲的數據不會大於後端web服務器的程序數據大小。緩存的對象是有生命週期的,當緩存空間耗盡的時候會根據LRU算法(最近最少使用)或其他算法對緩存空間進行清理。緩存中一般只能緩存非客戶私有數據,客戶私有數據和帶cookie的數據可以進行緩存,但是存在風險,所以一般不對客戶私有數據進行緩存。
緩存處理的步驟:接收請求 --> 解析請求 (提取請求的URL及各種首部)--> 查詢緩存 --> 新鮮度檢測 --> 創建響應報文 --> 發送響應 --> 記錄日誌(此圖不一定準確,只是爲了好理解)
新鮮度檢測機制:
過期日期:
HTTP/1.0 Expires(Expires:Thu, 04 Jun 2015 23:38:18 GMT)
HTTP/1.1 Cache-Control: max-age(Cache-Control:max-age=600)
條件式請求首部:
If-Modified-Since:基於請求內容的時間戳作驗正;
If-None-Match:基於被請求變量的實體值ETag;ETag是一個可以與Web資源關聯的記號;
有效性再驗正:revalidate
如果原始內容未改變,則僅響應首部(不附帶body部分),響應碼304 (Not Modified)
如果原始內容發生改變,則正常響應,響應碼200;
如果原始內容消失,則響應404,此時緩存中的cache object也應該被刪除;
緩存控制機制(Cache-Control):
請求:
no-cache:不要給我緩存的內容,要從Web服務器現取的內容
max-age:只接受Age值小於max-age的值,並且沒有過期的緩存對象
(Age:當代理服務器用自己緩存的實體去響應請求時,用該頭部表明該實體從產生到現在經過多長時候)
max-statle:可以接受過期的緩存對象,但是過期時間必須小於此值
min-fresh:接收其新鮮生命期大於當前Age和min-fresh值之和的緩存對象
響應:
public:可以用Cache內容迴應任何用戶
private:只能緩存內容迴應先前請求該內容的那個用戶
no-cache:可以緩存,但是只有在跟Web服務器驗證了其有效後,才能返回客戶端
no-store:不允許緩存
三、Varnish的結構和工作流程
①、Management進程
Management進程主要實現應用新的配置、編譯VCL、監控varnish、初始化varnish以及提供一個命令行接口等。Management進程會每隔幾秒鐘探測一下Child進程以判斷其是否正常運行,如果在指定的時長內未得到Child進程的迴應,Management將會重啓此Child進程。
②、Child/Cache 進程
Commad line 線程 : 管理接口
Storage/hashing 線程 :完成hash並進行緩存存儲
Log/stats 線程:查看記錄日誌並統計各種狀態
Accept 線程:接收新的連接請求並響應;
Backend Communication 線程:管理後端主機線程,當緩存中沒有緩存需要有此進程交由後端服務器處理
Worker 線程:child進程會爲每個會話啓動一個worker線程,因此,在高併發的場景中可能會出現數百個worker線程甚至更多;
Object Expiry 線程:從緩存中清理過期內容;
③、Varnish日誌
爲了與系統的其它部分進行交互,Child進程使用了可以通過文件系統接口進行訪問的共享內存日誌(shared memory log),因此,如果某線程需要記錄信息,其僅需要持有一個鎖,而後向共享內存中的某內存區域寫入數據,再釋放持有的鎖即可。而爲了減少競爭,每個worker線程都使用了日誌數據緩存。共享內存日誌大小一般爲90M,其分爲兩部分,前一部分爲計數器,後半部分爲客戶端請求的數據。varnish提供了多個不同的工具如varnishlog、varnishncsa或varnishstat等來分析共享內存日誌中的信息並能夠以指定的方式進行顯示。
④、VCL(Varnish Configuation Language)簡介
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命令完成。
⑤、Varnish 的後端存儲
Varnish支持多種不同類型的後端存儲,這可以在varnishd啓動時使用-s選項指定。後端存儲的類型包括:
file:使用特定的文件存儲全部的緩存數據,並通過操作系統的mmap()系統調用將整個緩存文件映射至內存區域(如果條件允許);
malloc:使用malloc()庫調用在varnish啓動時向操作系統申請指定大小的內存空間以存儲緩存對象;
persistent(experimental):與file的功能相同,但可以持久存儲數據(即重啓varnish數據時不會被清除);仍處於測試期;
Varnish無法追蹤某緩存對象是否存入了緩存文件,從而也就無從得知磁盤上的緩存文件是否可用,因此,file存儲方法在varnish停止或重啓時會清除數據。而persistent方法的出現對此有了一個彌補,但persistent仍處於測試階段,例如目前尚無法有效處理要緩存對象總體大小超出緩存空間的情況,所以,其僅適用於有着巨大緩存空間的場景。
一個Management可以啓動多個child/cache,每個child/cache子進程內部生成多個worker threads響應用戶請求。Varnish是單進程多線程模式的,每個線程處理一個用戶請求,但是不是所有的線程都用於用戶請求,部分線程工作於Backend Communication、Log/stats等
三、Varnish的安裝和配置
這裏使用Centos 7通過epel源安裝Varnish
[root@C7node1 /]# yum install varnish jemalloc.x86_64 0:3.6.0-1.el7 #jemalloc 一個性能非常卓越的內存分配器,由於C提供的內存分配效率過低,所以這裏使用了更高性能的內存管理器 #用戶malloc內存緩存對象使用 [root@C7node1 /]# rpm -ql varnish /etc/logrotate.d/varnish #日誌回滾 /etc/varnish/default.vcl #varnish的配置文件 /etc/varnish/varnish.params #varnish的參數,用於配置varnish作爲緩存的工作屬性 /usr/lib/systemd/system/varnishlog.service #從共享內存中抽取第二段數據日誌 /usr/lib/systemd/system/varnishncsa.service #從共享內存中抽取第二段數據日誌
配置varnish的三種應用:
①、varnishd應用程序的命令行參數(/etc/varnish/varnish.params)
Varnishd常用選項
-p param=value # set parameter。配置參數
-r param[,param...] # make parameter read-only。設定只讀參數列表
-f file # VCL script,讀取VCL配置文件
-a address:port # HTTP listen address and port。指定http地址端口
-d # debug。運行於調試模式
-s [name=]kind[,options] # Backend storage specification。指定存儲類型
# -s malloc[,<size>]
# -s file,<dir_or_file>,<size>,<granularity>
# -s persist{experimental}
-T address:port # Telnet listen address and port。指定管理接口
-S secret-file # Secret file for CLI authentication。指定管理密鑰文件
-t # Default TTL。前端服務器varnish連接各後端服務器時超時時間
②、varnishd Child/cahce:實時參數(child的進程數,worker的線程數)
varnishadm
param.set
③、vcl:配置緩存系統的緩存機制;
通過vcl配置文件進行配置;先編譯,後應用;依賴於c編譯器;
四、Varnish工具的使用
①、varnishadm
[root@C7node1 varnish]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 help [<command>] #查詢幫助 ping [<timestamp>] #測試ping通信 auth <response> quit #退出varnishadm status #查看Child的狀態 start #開啓Child stop #停止Child vcl.load <configname> <filename> #編譯加載指定vcl vcl.inline <configname> <quoted_VCLstring> vcl.use <configname> #使用指定vcl vcl.discard <configname> #刪除指定vcl vcl.list #列出所有vcl vcl.show <configname> #查看編譯好的vcl的配置文件 param.show [-l] [<param>] #查看運行參數 param.set <param> <value> #設置運行參數 panic.show #查看Child恐慌掛掉的信息 panic.clear #清除信息 storage.list #查看使用的storage列表 backend.list [<backend_expression>] #查看backed後端服務器 backend.set_health <backend_expression> <state> #調整backed狀態 ban <field> <operator> <arg> [&& <field> <oper> <arg>]... #清理緩存中的緩存對象 ban.list #列出定義的ban規則
②、varnishlog
③、varnishncsa
④、varnishtop
⑤、varnishstat
四、VCL配置文件及其相關參數
①、VCL的狀態引擎
state engine:各引擎之間存一定程度上的相關性;前一個engine如果可以有多種下游engine,則上游engine需要用return指明要轉移的下游engine;
vcl_recv:接收客戶請求
vcl_hash:當域名做泛解析的時候,對同一類的url做規範化
vcl_hit:命中緩存
vcl_miss:沒有命中緩存
vcl_fetch:去後端服務器請求數據
vcl_deliver:投遞給客戶端
vcl_pipe:
vcl_pass:
vcl_error:
vcl_backend_fetch(V4新添加的)
vcl_backend_response(V4新添加的)
vcl_backend_error(V4新添加的)
vcl_purge(V4新添加的)
vcl_synth(V4新添加的)
②、VCL語法
//、#或/* comment */ 用於註釋;會被編譯器忽略
sub $name 定義函數 ,sub vcl_recv { }
不支持循環,有衆多內置變量 ,變量的可調用位置與state engine有密切相關性
使用終止語句,return(action),沒有返回值
域專用
操作符:=(賦值)、==(等值比較)、~(模式匹配)、!(取反)、&&(邏輯與)、||(邏輯或)
條件判斷語句:if(comment){ }else{ }
變量賦值和撤銷變量:set name=value,unset name
③、varnish中的內置變量:
client
client.ip:客戶端IP地址
server
server.ip:varnish ip地址
server.hostname:varnish主機名
req
req.http.HEADERS:客戶端發往varnish的請求報文的指定首部
req.method:請求方法
req.proto:請求協議版本
req.ttl:請求ttl
req.url:請求url
resp
resp.http.HEADERS:varnish發往客戶端的響應報文的指定首部
resp.proto:響應協議版本
resp.reason:響應的原因短語
resp.status:響應的狀態碼
bereq
bereq.http.HEADERS: 由varnish發往backend server的請求報文的指定首部;
bereq.request:請求方法;
bereq.url:請求的url
bereq.proto:請求的協議版本
bereq.backend:指明要調用的後端主機;
beresp
beresp.proto:又backend server響應的協議版本
beresp.status:後端服務器的響應的狀態碼
beresp.reason:原因短語;
beresp.backend.ip:後端服務器的IP地址
beresp.backend.name:後端服務器的主機名
beresp.http.HEADER: 從backend server響應的報文的首部;
beresp.ttl:後端服務器響應的內容的餘下的生存時長;
obj
obj.ttl: 對象的ttl值;
obj.hits:此對象從緩存中命中的次數;
storage
官方文檔:https://www.varnish-cache.org/docs/4.0/reference/vcl.html#varnish-configuration-language
④、varnish中的內置方法:
return():終止vcl引擎
hash_data(input):進行hash計算並放回對應的hash值
regsub(str, regex, sub):正則表達式查找替換,之查找替換一次
regsuball(str, regex, sub):正則表達式查找替換,替換所有
ban(expression):緩存對象清理
四、實驗總結
c7node1.wlw.com 192.168.0.56 Varnish服務器
C6node1.wlw.com 192.168.0.66 backend server
C6node2.wlw.com 192.168.0.76 backend server
定義在vcl_deliver中,向響應給客戶端的報文添加一個自定義首部X-Cache
[root@C7node1 varnish]# vim /etc/varnish/wlw.vcl 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_deliver { if(obj.hits > 0) { set resp.http.X-Cache = "HIT"; }else{ set resp.http.X-Cache = "Miss"; } } [root@C7node1 varnish]# varnishadm vcl.load wlw wlw.vcl 200 VCL compiled. vcl.list 200 active 0 boot available 0 wlw vcl.use wlw 200 VCL 'wlw' now active [root@wlw ~]# curl -I http://192.168.0.56 X-Cache: Miss [root@wlw ~]# curl -I http://192.168.0.56 X-Cache: HIT
讓Varnish支持虛擬主機
sub vcl_recv { if (req.http.host == "www.wlw.com") { return (pipe); } } sub vcl_pipe{ set bereq.http.host = "www.wlw.com"; }
強制對某資源的請求,不檢查緩存
sub vcl_recv { if (req.url ~ "^/test1.html$") { return(pass); } } [root@C6node1 dz]# curl -I http://192.168.0.56/test1.html HTTP/1.1 200 OK X-Cache: Miss [root@C6node1 dz]# curl -I http://192.168.0.56/test1.html HTTP/1.1 200 OK X-Cache: Miss #可以看到多次請求都沒有命中緩存 sub vcl_recv { if (req.url ~ "(?i)^/login" || req.url ~ "(?i)^/admin") { return(pass); } } #對/login或者/admin的目錄都不查詢緩存,(?i) 表示對正則匹配不區分大小寫
對特定類型的資源(公共可用)取消其私有的cookie標識,並強行設定其可以varnish緩存的時長
sub vcl_backend_response { if (beresp.http.cache-control !~ "s-maxage") { if (bereq.url ~ "(?i)\.jpg$") { set beresp.ttl = 3600s; unset beresp.http.Set-Cookie; } if (bereq.url ~ "(?i)\.css$") { set beresp.ttl = 600s; unset beresp.http.Set-Cookie; } } } #veresp.ttl爲設置在varnish上的緩存時長
定義backend server和後端主機的健康狀態檢測,並實現資源分別分配
backend websrv1 { .host = "192.168.0.66"; .port = "80"; .probe = { .url = "/test5.html"; } } backend websrv2 { .host = "192.168.0.76"; .port = "80"; .probe = { .url = "/test5.html"; } } sub vcl_recv { if (req.url ~ "(?i)\.(jpg|png|gif)$") { set req.backend_hint = websrv1; } else { set req.backend_hint = websrv2; } } #定義兩臺後端主機
測試查看結果
backend.list Backend name Refs Admin Probe websrv1(192.168.0.66,,80) 1 probe Healthy 8/8 websrv2(192.168.0.76,,80) 1 probe Sick 0/8 backend.list 200 Backend name Refs Admin Probe websrv1(192.168.0.66,,80) 1 probe Healthy 8/8 websrv2(192.168.0.76,,80) 1 probe Healthy 6/8 #這裏因爲192.168.0.76忘記關閉防火牆導致請求test5.html檢測失敗,關閉防火牆後檢測通過並恢復
backend server的定義:
backend name {
.attribute = "value";
}
.host: BE主機的IP;
.port:BE主機監聽的PORT;
.probe: 對BE做健康狀態檢測;
.max_connections:並連接最大數量;
後端主機的健康狀態檢測方式:
probe name {
.attribute = "value";
}
.url: 判定BE健康與否要請求的url;
.request:手動定義請求報文的內容
.expected_response:期望響應狀態碼;默認爲200;
.timeout:健康狀態檢測的超時時間,默認2s
.interval:每隔多長時間檢測一次,默認5s
.window:最近檢測的窗口,默認爲8
.threshold:默認爲3,只要在window定義的次數中有threshold次通過即爲正常
.initial:當Varnish主機剛啓動的時候檢測後端主機多少次成功即爲正常,默認爲1
基於Varnish的負載均衡
backend websrv1 { .host = "192.168.0.66"; .port = "80"; .probe = { .url = "/test5.html"; } } backend websrv2 { .host = "192.168.0.76"; .port = "80"; .probe = { .url = "/test5.html"; } } import directors; sub vcl_init { new mycluster = directors.round_robin(); mycluster.add_backend(websrv1); mycluster.add_backend(websrv2); } sub vcl_recv { if (req.url ~ "(?i)\.(jpg|png|gif)$") { set req.backend_hint = websrv1; } else { set req.backend_hint = websrv2; } set req.backend_hint = mycluster.backend(); } [root@C6node1 dz]# curl http://192.168.0.56/test1.html Page 1 Web2 [root@C6node1 dz]# curl http://192.168.0.56/test2.html Page 2 Web1 [root@C6node1 dz]# curl http://192.168.0.56/test3.html Page 3 Web2 [root@C6node1 dz]# curl http://192.168.0.56/test4.html Page 4 Web1 #基於資源的負載均衡,負載均衡算法:fallback, random, round_robin, hash