簡介 css
Varnish 是一款高性能且開源的反向代理服務器和 HTTP 加速器,其採用全新的軟件體系機構,和如今的硬件體系緊密配合,與傳統的 squid 相比,varnish 具備性能更高、速度更快、管理更加方便等諸多優勢; html
目前最新版本是4.0.0,而3.x的版本也是能夠生產環境下使用的穩定版本,但yum源中的2.x版本過於陳舊,不建議使用; 前端
Varnish與Squid的對比 web
相同點 正則表達式
都是一個反向代理服務器; 後端
都是開源軟件; 緩存
Varnish的優點 安全
Varnish的穩定性很高,二者在完成相同負荷的工做時,Squid服務器發生故障的概率要高於Varnish,由於使用Squid要常常重啓; 服務器
Varnish訪問速度更快,由於採用了「Visual Page Cache」技術,全部緩存數據都直接從內存讀取,而squid是從硬盤讀取,於是Varnish在訪問速度方面會更快; cookie
Varnish能夠支持更多的併發鏈接,由於Varnish的TCP鏈接釋放要比Squid快,於是在高併發鏈接狀況下能夠支持更多TCP鏈接;
Varnish能夠經過管理端口,使用正則表達式批量的清除部分緩存,而Squid是作不到的;
squid屬因而單進程使用單核CPU,但Varnish是經過fork形式打開多進程來作處理,因此能夠合理的使用全部核來處理相應的請求;
Varnish的劣勢
varnish進程一旦Hang、Crash或者重啓,緩存數據都會從內存中徹底釋放,此時全部請求都會發送到後端服務器,在高併發狀況下,會給後端服務器形成很大壓力;
在varnish使用中若是單個url的請求經過HA/F5等負載均衡,則每次請求落在不一樣的varnish服務器中,形成請求都會被穿透到後端;並且一樣的請求在多臺服務器上緩存,也會形成varnish的緩存的資源浪費,形成性能降低;
Varnish劣勢的解決方案
針 對劣勢一:在訪問量很大的狀況下推薦使用varnish的內存緩存方式啓動,並且後面須要跟多臺squid服務器。主要爲了防止前面的varnish服 務、服務器被重啓的狀況下,大量請求穿透varnish,這樣squid能夠就擔當第二層CACHE,並且也彌補了varnish緩存在內存中重啓都會釋 放的問題;
針對劣勢二:能夠在負載均衡上作url哈希,讓單個url請求固定請求到一臺varnish服務器上;
對比Varnish 3.x的主要改進
徹底支持流對象;
可後臺獲取失效的對象,即Client/backend分離;
新的vanishlog查詢語言,容許對請求進行自動分組;
複雜的請求時間戳和字節計數;
安全方面的提高;
涉及VCL語法的改變點
vcl配置文件需明確指定版本:即在vcl文件的第一行寫上 vcl 4.0;
vcl_fetch函數被vcl_backend_response代替,且req.*再也不適用vcl_backend_response;
後端源服務器組director成爲varnish模塊,需import directors後再在vcl_init子例程中定義;
自定義的子例程(即一個sub)不能以vcl_開頭,調用使用call sub_name;
error()函數被synth()替代;
return(lookup)被return(hash)替代;
使用beresp.uncacheable建立hit_for_pss對象;
變量req.backend.healty被std.healthy(req.backend)替代;
變量req.backend被req.backend_hint替代;
關鍵字remove被unset替代;
詳見: https://www.varnish-cache.org/docs/4.0/whats-new/index.html#whats-new-index
架構及文件緩存的工做流程
Varnish 分爲 master 進程和 child 進程;
Master 進程讀入存儲配置文件,調用合適的存儲類型,而後建立 / 讀入相應大小的緩存文件,接着 master 初始化管理該存儲空間的結構體,而後 fork 並監控 child 進程;
Child 進程在主線程的初始化的過程當中,將前面打開的存儲文件整個 mmap 到內存中,此時建立並初始化空閒結構體,掛到存儲管理結構體,以待分配;
對外管理接口分爲3種,分別是命令行接口、Telnet接口和Web接口;
同時在運行過程當中修改的配置,能夠由VCL編譯器編譯成C語言,並組織成共享對象(Shared Object)交由Child進程加載使用;
Child 進程分配若干線程進行工做,主要包括一些管理線程和不少 worker 線程,可分爲:
Accept線程:接受請求,將請求掛在overflow隊列上;
Work線程:有多個,負責從overflow隊列上摘除請求,對請求進行處理,直到完成,而後處理下一個請求;
Epoll線程:一個請求處理稱爲一個session,在session週期內,處理完請求後,會交給Epoll處理,監聽是否還有事件發生;
Expire線程:對於緩存的object,根據過時時間,組織成二叉堆,該線程週期檢查該堆的根,處理過時的文件,對過時的數據進行刪除或重取操做;
HTTP請求基本處理流程
Varnish 處理 HTTP 請求的過程以下
Receive 狀態(vcl_recv):也就是請求處理的入口狀態,根據 VCL 規則判斷該請求應該 pass(vcl_pass)或是 pipe(vcl_pipe),仍是進入 lookup(本地查詢);
Lookup 狀態:進入該狀態後,會在 hash 表中查找數據,若找到,則進入 hit(vcl_hit)狀態,不然進入 miss(vcl_miss)狀態;
Pass(vcl_pass)狀態:在此狀態下,會直接進入後端請求,即進入 fetch(vcl_fetch)狀態;
Fetch(vcl_fetch)狀態:在 fetch 狀態下,對請求進行後端獲取,發送請求,得到數據,並根據設置進行本地存儲;
Deliver(vcl_deliver)狀態:將獲取到的數據發給客戶端,而後完成本次請求;
注:Varnish4中在vcl_fetch部分略有出入,已獨立爲vcl_backend_fetch和vcl_backend_response2個函數;
內置函數(也叫子例程)
vcl_recv:用於接收和處理請求;當請求到達併成功接收後被調用,經過判斷請求的數據來決定如何處理請求;
vcl_pipe:此函數在進入pipe模式時被調用,用於將請求直接傳遞至後端主機,並將後端響應原樣返回客戶端;
vcl_pass:此函數在進入pass模式時被調用,用於將請求直接傳遞至後端主機,但後端主機的響應並不緩存直接返回客戶端;
vcl_hit:在執行 lookup 指令後,在緩存中找到請求的內容後將自動調用該函數;
vcl_miss:在執行 lookup 指令後,在緩存中沒有找到請求的內容時自動調用該方法,此函數可用於判斷是否須要從後端服務器獲取內容;
vcl_hash:在vcl_recv調用後爲請求建立一個hash值時,調用此函數;此hash值將做爲varnish中搜索緩存對象的key;
vcl_purge:pruge操做執行後調用此函數,可用於構建一個響應;
vcl_deliver:將在緩存中找到請求的內容發送給客戶端前調用此方法;
vcl_backend_fetch:向後端主機發送請求前,調用此函數,可修改發日後端的請求;
vcl_backend_response:得到後端主機的響應後,可調用此函數;
vcl_backend_error:當從後端主機獲取源文件失敗時,調用此函數;
vcl_init:VCL加載時調用此函數,常常用於初始化varnish模塊(VMODs)
vcl_fini:當全部請求都離開當前VCL,且當前VCL被棄用時,調用此函數,常常用於清理varnish模塊;
VCL中內置公共變量
變量(也叫object)適用範圍
注:某些地方略有出入,詳細可參考官方文檔;
變量類型詳解
req:The request object,請求到達時可用的變量
bereq:The backend request object,向後端主機請求時可用的變量
beresp:The backend response object,從後端主機獲取內容時可用的變量
resp:The HTTP response object,對客戶端響應時可用的變量
obj:存儲在內存中時對象屬性相關的可用的變量
具體變量詳見: https://www.varnish-cache.org/docs/4.0/reference/vcl.html#reference-vcl
優雅模式(Garce mode)
Varnish中的請求合併
當幾個客戶端請求同一個頁面的時候,varnish只發送一個請求到後端服務器,而後讓其餘幾個請求掛起並等待返回結果;得到結果後,其它請求再複製後端的結果發送給客戶端;
但若是同時有數以千計的請求,那麼這個等待隊列將變得龐大,這將致使2類潛在問題:
驚羣問題(thundering herd problem),即忽然釋放大量的線程去複製後端返回的結果,將致使負載急速上升;
沒有用戶喜歡等待;
故爲了解決這類問題,能夠配置varnish在緩存對象因超時失效後再保留一段時間,以給那些等待的請求返回過去的文件內容(stale content),配置案例以下:
sub vcl_recv { if (! req.backend.healthy) { set req.grace = 5m; } else { set req.grace = 15s; } } sub vcl_fetch { set beresp.grace = 30m; } # 以上配置表示varnish將會將失效的緩存對象再多保留30分鐘,此值等於最大的req.grace值便可; # 而根據後端主機的健康情況,varnish可向前端請求分別提供5分鐘內或15秒內的過時內容
安裝配置
# 安裝包下載地址:http://repo.varnish-cache.org/redhat/varnish-4.0/el6/ yum localinstall --nogpgcheck varnish-4.0.0-1.el6.x86_64.rpm varnish-libs-4.0.0-1.el6.x86_64.rpm varnish-docs-4.0.0-1.el6.x86_64.rpm vi /etc/sysconfig/varnish # 編輯配置文件,修改以下項 VARNISH_STORAGE_SIZE=100M # 此值根據自身狀況調整,測試環境可調低此值 VARNISH_STORAGE="malloc,${VARNISH_STORAGE_SIZE}" # Varnish 4中默認使用malloc(即內存)做爲緩存對象存儲方式; service varnish start # 啓動varnish,默認外部請求的監聽端口6081,管理端口6082,後端主機127.0.0.1:80 =========== varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 # 登陸管理命令行 varnish> vcl.list # 列出全部的配置 varnish> vcl.load test1 test.vcl # 加載編譯新配置,test1是配置名,test.vcl是配置文件 varnish> vcl.use test1 # 使用配置,需指定配置名,當前使用的配置以最後一次vcl.use爲準 varnish> vcl.show test1 # 顯示配置內容,需指定配置名
實例解析
# # This is an example VCL file for Varnish. # # It does not do anything by default, delegating control to the # builtin VCL. The builtin VCL is called when there is no explicit # return statement. # # See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/ # and http://varnish-cache.org/trac/wiki/VCLExamples for more examples. # Marker to tell the VCL compiler that this VCL has been adapted to the # new 4.0 format. vcl 4.0; import directors; probe backend_healthcheck { # 建立健康監測 .url = /health.html; .window = 5; .threshold = 2; .interval = 3s; } backend web1 { # 建立後端主機 .host = "static1.lnmmp.com"; .port = "80"; .probe = backend_healthcheck; } backend web2 { .host = "static2.lnmmp.com"; .port = "80"; .probe = backend_healthcheck; } backend img1 { .host = "img1.lnmmp.com"; .port = "80"; .probe = backend_healthcheck; } backend img2 { .host = "img2.lnmmp.com"; .port = "80"; .probe = backend_healthcheck; } vcl_init { # 建立後端主機組,即directors new web_cluster = directors.random(); web_cluster.add_backend(web1); web_cluster.add_backend(web2); new img_cluster = directors.random(); img_cluster.add_backend(img1); img_cluster.add_backend(img2); } acl purgers { # 定義可訪問來源IP "127.0.0.1"; "192.168.0.0"/24; } sub vcl_recv { if (req.request == "GET" && req.http.cookie) { # 帶cookie首部的GET請求也緩存 return(hash); } if (req.url ~ "test.html") { # test.html文件禁止緩存 return(pass); } if (req.request == "PURGE") { # PURGE請求的處理 if (!client.ip ~ purgers) { return(synth(405,"Method not allowed")); } return(hash); } if (req.http.X-Forward-For) { # 爲發日後端主機的請求添加X-Forward-For首部 set req.http.X-Forward-For = req.http.X-Forward-For + "," + client.ip; } else { set req.http.X-Forward-For = client.ip; } if (req.http.host ~ "(?i)^(www.)?lnmmp.com$") { # 根據不一樣的訪問域名,分發至不一樣的後端主機組 set req.http.host = "www.lnmmp.com"; set req.backend_hint = web_cluster.backend(); } elsif (req.http.host ~ "(?i)^images.lnmmp.com$") { set req.backend_hint = img_cluster.backend(); } return(hash); } sub vcl_hit { # PURGE請求的處理 if (req.request == "PURGE") { purge; return(synth(200,"Purged")); } } sub vcl_miss { # PURGE請求的處理 if (req.request == "PURGE") { purge; return(synth(404,"Not in cache")); } } sub vcl_pass { # PURGE請求的處理 if (req.request == "PURGE") { return(synth(502,"PURGE on a passed object")); } } sub vcl_backend_response { # 自定義緩存文件的緩存時長,即TTL值 if (req.url ~ "\.(jpg|jpeg|gif|png)$") { set beresp.ttl = 7200s; } if (req.url ~ "\.(html|css|js)$") { set beresp.ttl = 1200s; } if (beresp.http.Set-Cookie) { # 定義帶Set-Cookie首部的後端響應不緩存,直接返回給客戶端 return(deliver); } } sub vcl_deliver { if (obj.hits > 0) { # 爲響應添加X-Cache首部,顯示緩存是否命中 set resp.http.X-Cache = "HIT from " + server.ip; } else { set resp.http.X-Cache = "MISS"; } }