簡介:
Varnish 是一款高性能且開源的反向代理服務(wù)器和 HTTP 加速器饶碘,其采用全新的軟件體系機(jī)構(gòu)目尖,和現(xiàn)在的硬件體系緊密配合,與傳統(tǒng)的 squid 相比扎运,varnish 具有性能更高瑟曲、速度更快饮戳、管理更加方便等諸多優(yōu)點(diǎn);
Varnish與Squid的對比
相同點(diǎn)
都是一個(gè)反向代理服務(wù)器洞拨;
都是開源軟件扯罐;
Varnish的優(yōu)勢:
Varnish的穩(wěn)定性很高,兩者在完成相同負(fù)荷的工作時(shí)烦衣,Squid服務(wù)器發(fā)生故障的幾率要高于Varnish歹河,因?yàn)槭褂肧quid要經(jīng)常重啟;
Varnish訪問速度更快花吟,因?yàn)椴捎昧恕癡isual Page Cache”技術(shù)秸歧,所有緩存數(shù)據(jù)都直接從內(nèi)存讀取,而squid是從硬盤讀取示辈,因而Varnish在訪問速度方面會更快寥茫;
Varnish可以支持更多的并發(fā)連接,因?yàn)閂arnish的TCP連接釋放要比Squid快矾麻,因而在高并發(fā)連接情況下可以支持更多TCP連接纱耻;
Varnish可以通過管理端口,使用正則表達(dá)式批量的清除部分緩存险耀,而Squid是做不到的弄喘;
squid屬于是單進(jìn)程使用單核CPU,但Varnish是通過fork形式打開多進(jìn)程來做處理甩牺,所以可以合理的使用所有核來處理相應(yīng)的請求蘑志;
Varnish的劣勢
varnish進(jìn)程一旦Hang、Crash或者重啟贬派,緩存數(shù)據(jù)都會從內(nèi)存中完全釋放急但,此時(shí)所有請求都會發(fā)送到后端服務(wù)器,在高并發(fā)情況下搞乏,會給后端服務(wù)器造成很大壓力波桩;
在varnish使用中如果單個(gè)url的請求通過HA/F5等負(fù)載均衡,則每次請求落在不同的varnish服務(wù)器中请敦,造成請求都會被穿透到后端镐躲;而且同樣的請求在多臺服務(wù)器上緩存,也會造成varnish的緩存的資源浪費(fèi)侍筛,造成性能下降萤皂;
Varnish劣勢的解決方案
針對劣勢一:在訪問量很大的情況下推薦使用varnish的內(nèi)存緩存方式啟動,而且后面需要跟多臺squid服務(wù)器匣椰。主要為了防止前面的varnish服 務(wù)裆熙、服務(wù)器被重啟的情況下,大量請求穿透varnish,這樣squid可以就擔(dān)當(dāng)?shù)诙覥ACHE弛车,而且也彌補(bǔ)了varnish緩存在內(nèi)存中重啟都會釋 放的問題齐媒;
針對劣勢二:可以在負(fù)載均衡上做url哈希,讓單個(gè)url請求固定請求到一臺varnish服務(wù)器上;
Varnish 進(jìn)程的工作模式:
(1)Varnish 啟動或有2個(gè)進(jìn)程 master(management) 進(jìn)程和 child(worker) 進(jìn)程纷跛。master 讀入存儲配置命令,進(jìn)行初始化邀杏,然后 fork贫奠,監(jiān)控 child。child 則分配線程進(jìn)行 cache 工作望蜡,child 還會做管理線程和生成很多 worker 線程唤崭。
(2)child 進(jìn)程主線程初始化過程中,將存儲大文件整個(gè)加載到內(nèi)存中脖律,如果該文件超出系統(tǒng)的虛擬內(nèi)存谢肾,則會減少原來配置 mmap 大小,然后繼續(xù)加載小泉,這時(shí)候創(chuàng)建并初始化空閑存儲結(jié)構(gòu)體芦疏,放在存儲管理的 struct 中,等待分配微姊。
接著 Varnish 某個(gè)負(fù)責(zé)接受新 http 連接的線程開始等待用戶酸茴,如果有新的 http 連接,但是這個(gè)線程只負(fù)責(zé)接收兢交,然后喚醒等待線程池中的 work 線程薪捍,進(jìn)行請求處理。
worker 線程讀入 uri 后配喳,將會查找已有的 object酪穿,命中直接返回,沒有命中晴裹,則會從后端服務(wù)器中取出來被济,放到緩存中。如果緩存已滿息拜,會根據(jù) LRU 算法溉潭,釋放舊的 object。對于釋放緩存少欺,有一個(gè)超時(shí)線程會檢測緩存中所有 object 的生命周期喳瓣,如果緩存過期 (ttl),則刪除赞别,釋放相應(yīng)的存儲內(nèi)存畏陕。
Child進(jìn)程特點(diǎn):
Child 進(jìn)程分配若干線程進(jìn)行工作,主要包括一些管理線程和很多 worker 線程仿滔,可分為:
(1)Accept線程:接受請求惠毁,將請求掛在overflow隊(duì)列上犹芹;
(2)Work線程:有多個(gè),負(fù)責(zé)從overflow隊(duì)列上摘除請求鞠绰,對請求進(jìn)行處理腰埂,直到完成,然后處理下一個(gè)請求蜈膨;
(3)Epoll線程:一個(gè)請求處理稱為一個(gè)session屿笼,在session周期內(nèi),處理完請求后翁巍,會交給Epoll處理驴一,監(jiān)聽是否還有事件發(fā)生;
(5)Expire線程:對于緩存的object灶壶,根據(jù)過期時(shí)間肝断,組織成二叉堆,該線程周期檢查該堆的根驰凛,處理過期的文件胸懈,對過期的數(shù)據(jù)進(jìn)行刪除或重取操作;
Varnish 處理 HTTP 請求的過程如下
1.Receive 狀態(tài)(vcl_recv):也就是請求處理的入口狀態(tài)洒嗤,根據(jù) VCL 規(guī)則判斷該請求應(yīng)該 pass(vcl_pass)或是 pipe(vcl_pipe)箫荡,還是進(jìn)入 lookup(本地查詢);
2.Lookup 狀態(tài):進(jìn)入該狀態(tài)后渔隶,會在 hash 表中查找數(shù)據(jù)羔挡,若找到,則進(jìn)入 hit(vcl_hit)狀態(tài)间唉,否則進(jìn)入 miss(vcl_miss)狀態(tài)绞灼;
3.Pass(vcl_pass)狀態(tài):在此狀態(tài)下,會直接進(jìn)入后端請求呈野,即進(jìn)入 fetch(vcl_fetch)狀態(tài)低矮;
4.Fetch(vcl_fetch)狀態(tài):在 fetch 狀態(tài)下,對請求進(jìn)行后端獲取被冒,發(fā)送請求军掂,獲得數(shù)據(jù),并根據(jù)設(shè)置進(jìn)行本地存儲昨悼;
5.Deliver(vcl_deliver)狀態(tài):將獲取到的數(shù)據(jù)發(fā)給客戶端蝗锥,然后完成本次請求;
注:Varnish4中在vcl_fetch部分略有出入率触,已獨(dú)立為vcl_backend_fetch和vcl_backend_response2個(gè)函數(shù)终议;
內(nèi)置函數(shù)(也叫子例程)
- vcl_recv:用于接收和處理請求;當(dāng)請求到達(dá)并成功接收后被調(diào)用,通過判斷請求的數(shù)據(jù)來決定如何處理請求穴张;
- vcl_pipe:此函數(shù)在進(jìn)入pipe模式時(shí)被調(diào)用细燎,用于將請求直接傳遞至后端主機(jī),并將后端響應(yīng)原樣返回客戶端皂甘;
- vcl_pass:此函數(shù)在進(jìn)入pass模式時(shí)被調(diào)用玻驻,用于將請求直接傳遞至后端主機(jī),但后端主機(jī)的響應(yīng)并不緩存直接返回客戶端偿枕;
- vcl_hit:在執(zhí)行 lookup 指令后击狮,在緩存中找到請求的內(nèi)容后將自動調(diào)用該函數(shù);
- vcl_miss:在執(zhí)行 lookup 指令后益老,在緩存中沒有找到請求的內(nèi)容時(shí)自動調(diào)用該方法,此函數(shù)可用于判斷是否需要從后端服務(wù)器獲取內(nèi)容寸莫;
- vcl_hash:在vcl_recv調(diào)用后為請求創(chuàng)建一個(gè)hash值時(shí)捺萌,調(diào)用此函數(shù);此hash值將作為varnish中搜索緩存對象的key膘茎;
- vcl_purge:pruge操作執(zhí)行后調(diào)用此函數(shù)桃纯,可用于構(gòu)建一個(gè)響應(yīng);
- vcl_deliver:將在緩存中找到請求的內(nèi)容發(fā)送給客戶端前調(diào)用此方法披坏;
- vcl_backend_fetch:向后端主機(jī)發(fā)送請求前态坦,調(diào)用此函數(shù),可修改發(fā)往后端的請求棒拂;
- vcl_backend_response:獲得后端主機(jī)的響應(yīng)后伞梯,可調(diào)用此函數(shù);
- vcl_backend_error:當(dāng)從后端主機(jī)獲取源文件失敗時(shí)帚屉,調(diào)用此函數(shù)谜诫;
- vcl_init:VCL加載時(shí)調(diào)用此函數(shù),經(jīng)常用于初始化varnish模塊(VMODs)
- vcl_fini:當(dāng)所有請求都離開當(dāng)前VCL攻旦,且當(dāng)前VCL被棄用時(shí)喻旷,調(diào)用此函數(shù),經(jīng)常用于清理varnish模塊牢屋;
VCL中內(nèi)置公共變量
變量(也叫object)適用范圍
req:The request object且预,請求到達(dá)時(shí)可用的變量
bereq:The backend request object,向后端主機(jī)請求時(shí)可用的變量
beresp:The backend response object烙无,從后端主機(jī)獲取內(nèi)容時(shí)可用的變量
resp:The HTTP response object锋谐,對客戶端響應(yīng)時(shí)可用的變量
obj:存儲在內(nèi)存中時(shí)對象屬性相關(guān)的可用的變量
優(yōu)雅模式(Garce mode)
Varnish中的請求合并
當(dāng)幾個(gè)客戶端請求同一個(gè)頁面的時(shí)候,varnish只發(fā)送一個(gè)請求到后端服務(wù)器皱炉,然后讓其他幾個(gè)請求掛起并等待返回結(jié)果怀估;獲得結(jié)果后,其它請求再復(fù)制后端的結(jié)果發(fā)送給客戶端;
但如果同時(shí)有數(shù)以千計(jì)的請求多搀,那么這個(gè)等待隊(duì)列將變得龐大歧蕉,這將導(dǎo)致2類潛在問題:驚群問題(thundering herd problem)贼急,即突然釋放大量的線程去復(fù)制后端返回的結(jié)果珠洗,將導(dǎo)致負(fù)載急速上升;
-
沒有用戶喜歡等待
故為了解決這類問題咒锻,可以配置varnish在緩存對象因超時(shí)失效后再保留一段時(shí)間从藤,以給那些等待的請求返回過去的文件內(nèi)容(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值即可夷野;而根據(jù)后端主機(jī)的健康狀況懊蒸,varnish可向前端請求分別提供5分鐘內(nèi)或15秒內(nèi)的過期內(nèi)容
安裝配置
安裝包下載地址: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
vim /etc/sysconfig/varnish # 編輯配置文件,修改如下項(xiàng)
VARNISH_STORAGE_SIZE=100M # 此值根據(jù)自身情況調(diào)整悯搔,測試環(huán)境可調(diào)低此值
VARNISH_STORAGE="malloc,${VARNISH_STORAGE_SIZE}" # Varnish 4中默認(rèn)使用malloc(即內(nèi)存)作為緩存對象存儲方式骑丸;
systemctl start varnish # 啟動varnish,默認(rèn)外部請求的監(jiān)聽端口6081妒貌,管理端口6082通危,后端主機(jī)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 # 使用配置菊碟,需指定配置名,當(dāng)前使用的配置以最后一次vcl.use為準(zhǔn)
varnish> vcl.show test1 # 顯示配置內(nèi)容在刺,需指定配置名
實(shí)例解析
#
# 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 { # 創(chuàng)建健康監(jiān)測
.url = /health.html;
.window = 5;
.threshold = 2;
.interval = 3s;
}
backend web1 { # 創(chuàng)建后端主機(jī)
.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 { # 創(chuàng)建后端主機(jī)組逆害,即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) { # 為發(fā)往后端主機(jī)的請求添加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$") { # 根據(jù)不同的訪問域名,分發(fā)至不同的后端主機(jī)組
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 { # 自定義緩存文件的緩存時(shí)長增炭,即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首部的后端響應(yīng)不緩存忍燥,直接返回給客戶端
return(deliver);
}
}
sub vcl_deliver {
if (obj.hits > 0) { # 為響應(yīng)添加X-Cache首部,顯示緩存是否命中
set resp.http.X-Cache = "HIT from " + server.ip;
} else {
set resp.http.X-Cache = "MISS";
}
}