Varnish緩存服務及應用
一、Varnish的基本介紹
Varnish與一般服務器軟件類似,就是一個web緩存代理服務器,分為master(management)進程和child(worker,主要做cache的工作)進程(也叫Cache進程)。master進程讀入命令,進行一些初始化,然後fork並監控child進程。child進程分配若幹線程進行工作,主要包括一些管理線程和很多woker線程。
Management進程主要實現應用新的配置、編譯VCL、監控varnish、初始化varnish以及提供一個命令行接口等。 Management進程會每隔幾秒鐘探測一下Child進程以判斷其是否正常運行,如果在指定的時長內未得到Child進程的回應,Management將會重啟此Child進程。
Child進程包含多種類型的線程,常見的如下:
Acceptor線程:接收新的連接請求並響應;
Worker線程:child進程會為每個會話啟動一個worker線程,因此,在高並發的場景中可能會出現數百個worker線程甚至更多;
Expiry線程:從緩存中清理過期內容;
Varnish依賴“工作區(workspace)”以降低線程在申請或修改內存時出現競爭的可能性。在varnish內部有多種不同的工作區,其中最關鍵的當屬用於管理會話數據的session工作區。
二、Varnish的特性
1、Varnish的穩定性很高,兩者在完成相同負荷的工作時,Squid服務器發生故障的幾率要高於Varnish,因為使用Squid要經常重啟;
2、Varnish訪問速度更快,因為采用了“Page Cache”技術,所有緩存數據都直接從內存讀取(映射),而squid是從硬盤讀取,因而Varnish在訪問速度方面會更快;
3、Varnish可以支持更多的並發連接,因為Varnish的TCP連接釋放要比Squid快,因而在高並發連接情況下可以支持更多TCP連接;
4、Varnish可以通過管理端口,使用正則表達式批量的清除部分緩存,而Squid是做不到的;
5、squid屬於是單進程使用單核CPU,但Varnish是通過fork形式打開多進程來做處理,所以可以合理的使用所有核來處理相應的請求。
補充知識:Varnish和Squid的不同
Squid是從硬盤讀取緩存的數據,而Varnish是把數據存放在內存中,直接從讀取內存,避免了頻繁在內存、磁盤中交換文件,所以Varnish要相對更高效,但也有缺點,內存中的緩存在服務器重啟後會丟失。
varnish和squid在中小規模的應用上,varnish足夠輕量級,足夠好用,但是在巨大的並發請求來說,單個varnish所能夠承載的並發訪問量大概在5000個連接請求左右,超出5000個可能就就得不穩定了;而在這裏squid就能表現出良好的性能了,因此在大規模的企業級應用中仍然是 以squid居多,而在中小規模的自己公司的反向代理緩存中varnish居多。
三、VCL的介紹
VCL,Varnish Configuration Language 是varnish配置緩存策略的工具,它是一種基於“域”(可想象與iptables的幾個鏈,也就是類似鉤子函數)的簡單編程語言,它支持有限的算術運算和邏輯運算操作、允許使用正則表達式進行字符串匹配、允許用戶使用set自定義變量、支持if判斷語句,也有內置的函數和變量等。
使用VCL編寫的緩存策略通常保存至.vcl文件中,其需要編譯成二進制的格式後才能由varnish調用。事實上,整個緩存策略就是由幾個特定的子例程如vcl_recv、vcl_hash等組成,它們分別在不同的位置(或時間)執行,如果沒有事先為某個位置自定義子例程,varnish將會執行默認的定義。
VCL的工作方式是基於狀態引擎(state engine)來實現的。
vcl內部有很多state engine,類似於iptables上的鉤子函數,分別作用於不同的邏輯位置,常用的有:
vcl_recv: 當請求報文進來時,會使用這裏的規則去判斷,比如該請求是不是能緩存的類型,是否允許它訪問本地的varnish服務等;
vcl_hash:在此處對請求報文中的特定屬性做hash計算,得出用於比對key的特征碼;
vcl_hit : 當緩存命中之後,我們要做什麽操作;
vcl_miss : 緩存miss之後,我們要做什麽操作;
vcl_backend_fetch: 與後端主機通信時,我們要做什麽操作;
vcl_deliver :響應數據給客戶前,我們要做什麽操作,相當於iptables的POSTROUTING鏈;
vcl_pass : 如果我們希望某些報文不要查詢緩存的話,可以直接送到vcl_pass中進行處理;vcl_pass中的報文會直接送往後端主機;
vcl_pipe : 比較特殊的engine,會直接把上遊來的請求報文送往後端服務器;
vcl_error : 當拒絕用戶訪問時,可以直接通過這個engine手動合成一個信息頁面,告訴用戶拒絕訪問原因之類的信息。使用這個engine的好處是,當我們預先判定用戶的某些請求一定會被拒絕時,我們可以直接在varnish層就把用戶的請求打回去;
vcl_purge :當管理員要手動修剪緩存條目時,我們要執行什麽規則;
這些engine之間之前彼此是有關聯性的,也有先後的邏輯順序,和iptables的 PREROUTING -- > INPUT 這種邏輯很類似。比如vcl_recv基本上是所有engine的上遊,它需要根據制定好的規則,來判斷每個報文的下遊engine是哪一個。
state engine的常見數據流向:
vcl_recv --> lookup --> vcl_hash --> cached --> vcl_hit--> vcl_deliver
數據報文進來之後先檢查,如果可緩存,則送進緩存中匹配,如果命中了,則直接響應給客戶。
vcl_recv --> lookup --> vcl_hash --> cached --> vcl_miss --> vcl_backend_fetch --> vcl_deliver
數據報文進來之後先檢查,如果可緩存,則送進緩存中匹配,如果沒命中緩存,varnish會替用戶去後端應用服務器請求對應的頁面,先緩存下來,然後響應給客戶。
vcl_recv --> vcl_pipe --> backendserver
送往vcl_pipe的報文不會做任何處理直接送到後端服務器。
vcl_recv --> vcl_pass --> vcl_backend_fetch --> vcl_deliver
通常用於不能緩存的內容,不檢查緩存直接送往vcl_pass,經由pass送到後端服務器去響應
四、Varnish的工作原理及工作流程
上圖說明:
vcl_recv的結果如果可以查詢緩存並可以識別,那就要到vcl_hash這步了,如果無法識別那就通過pipe(管道)送給vcl_pipe,如果能識別,但不是一個可緩存的對象,那就通過pass送到vcl_pass去,vcl_hash之後就可查看緩存中有沒有了,有這個請求的對象就表示命中 (vcl_hit),如果沒有那就表示未命中(vcl_miss),如果命中的就可以直接通過deliver直接送給vcl_deliver響應了,如果 未命中就通過fetch交給vcl_fatch去後端服務器上去取數據,取回數據之後如果數據可以緩存就緩存(cache),本地緩存完之後再構建響應, 如果不可以緩存就不做緩存交給vcl_deliver響應了;而如果命中了交給vcl_pass,交給pass之後就要到Fetch objet from backend後端服務器上去取數據了,這是因為這個命中的對象可能是過期或者是要做單獨立額外的處理的;這就是vcl的狀態引擎過程。
五、實例
實驗:實現基於Keepalived+Haproxy+Varnish+LNMP企業級架構
一、環境準備:
兩臺haproxy(一臺master,一臺backup,VIP:172.17.253.115)(對varnish實現負載均衡)
一臺varnish(IP分別為:192.168.159.139)
兩臺後端服務器(已實現lnmp)(IP分別為:192.168.159.120 192.168.159.121)
二、安裝步驟:
1、在用作haproxy負載均衡的機器上安裝keepalived和haproxy
yum install keepalived
yum install haproxy
2、在用作varnish的服務器上安裝varnish
yum install varnish
三、修改配置文件及啟動服務
1、對主haproxy操作:主要代碼如下
①vim /etc/keepalived/keepalived.conf 實現高可用
! Configuration File for keepalived
global_defs {
notification_email {
root@localhost #收件人
}
notification_email_from [email protected]
smtp_server 127.0.0.1 #發件的服務器
smtp_connect_timeout 30
router_id LVS_DEVEL2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 14
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 111111
}
virtual_ipaddress {
172.17.253.115
}
啟動服務:systemctl start keepalived
②vim /etc/haproxy/haproxy.cfg 實現負載均衡
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
stats socket /var/lib/haproxy/stats
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
listen stats
mode http #基於http協議
bind 0.0.0.0:1080 #監聽1080端口
stats enable #開啟統計報告服務
stats hide-version #隱藏統計報告版本信息
stats uri /haproxyadmin #統計報告訪問url
stats realm Haproxy\ Statistics #頁面登陸信息
stats auth admin:admin #驗證賬號信息
stats admin if TRUE #驗證模式
frontend http-in
bind *:80
default_backend cache
backend cache
balance roundrobin #負載均衡算法
server cache1 192.168.159.139:6081(varnish對應端口) check
啟動服務:systemctl start haproxy (打開了80和1080端口)
2、對從haproxy操作:
①vim /etc/keepalived/keepalived.conf
配置基本同上面主的,只需要修改下面兩行,然後啟動服務
state BACKUP
priority 90
②vim /etc/haproxy/haproxy.cfg
配置完全同上面主的,配置完成後啟動服務
3、兩臺varnish上進行同樣的操作:
①vim /etc/varnish/default.vcl
vcl 4.0; #版本
import directors; #導入模塊
probe backend_healthcheck { #定義健康狀態檢測
.url = "/index.html"; #檢測的頁面
.window = 5; #窗口
.threshold = 1; #門檻,1表示至少有一個正常工作
.interval = 3s; #檢測頻度
.timeout = 1s; #超時時長
}
backend web1 { #定義後端服務器
.host = "192.168.159.120";
.port = "80";
.probe = backend_healthcheck; #健康狀態檢測
}
backend web2 { #定義後端服務器
.host = "192.168.159.121";
.port = "80";
.probe = backend_healthcheck; #健康狀態檢測
}
sub vcl_init { #初始化
new web_cluster = directors.round_robin(); #定義後端服務器組,使用輪詢算法
web_cluster.add_backend(web1); #引用上面定義的服務器
web_cluster.add_backend(web2);
}
sub vcl_recv { #定義入口函數
if(req.url ~ "index.php"){ #不緩存index.php頁面,直接跳到pass
return (pass);
}
if(req.method == "GET"){ #請求方法為GET的就緩存
return (hash);
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "PURGE" &&
req.method != "DELETE"){
return (pipe); #如果不屬於以上列出的方法,那麽就通過管道直接傳遞到後端服務器
}
return (hash);
}
sub vcl_hash{ #定義hash
hash_data(req.url); #對請求的url進行hash處理
}
sub vcl_backend_response { #自定義緩存文件時長,即TTL值
if(bereq.url ~ "\.(jpg|jpeg|gif|png)$"){
set beresp.ttl = 30d; #緩存圖片30天
}
if(bereq.url ~ "\.(html|css|js)$"){
set beresp.ttl = 7d; #緩存靜態頁面7天
}
return (deliver);
}
sub vcl_deliver { #為響應添加首部,顯示緩存是否命中
if(obj.hits > 0){
set resp.http.X-Cache = "HIT from " + server.ip; #命中
}
else{
set resp.http.X-Cache = "MISS"; #沒命中
}
unset resp.http.Via; #取消顯示varnish版本號
}
②vim /etc/varnish/varnish.params
VARNISH_STORAGE="file,/var/lib/varnish/bin,1G" #以文件形式緩存,大小為1G,存在/var/lib/varnish/bin文件中
VARNISH_LISTEN_PORT=6081 #監聽端口為6081
啟動服務:systemctl start varnish (打開了6081端口)
查看頁面是否生效
六、Varnish命令行工具
Varnish常見工具使用
1>、varnishstat – Varnish Cache statistics 各種計數器
-1 批次顯示,只顯示1次
-1 -f FILED_NAME
-l:可用於-f選項指定的字段名稱列表;
# varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss
# varnishstat -l -f MAIN -f MEMPOOL
2>、varnishtop – Varnish log entry ranking 將日誌文件中相關數據逆序排序
-1 Instead of a continously updated display, print the statistics once and exit. -i taglist,可以同時使用多個-i選項,也可以一個選項跟上多個標簽;篩選
-I <[taglist:]regex>
-x taglist:排除列表
-X <[taglist:]regex>
varnishtop -i RespStatus 查看響應碼
3>、varnishlog – Display Varnish logs 查看實時日誌
4>、 varnishncsa – Display Varnish logs in Apache / NCSA combined log format 標準日誌格式
七、Varnish的後端存儲
varnish的緩存對象在每次服務重啟時都會被清空並重新建立,所以這些服務器都是不應該隨便去重啟的,varnish為了把數據更持久化的存儲,引入了更多的存儲機制,所以varnish支持多種不同的後端存儲;
varnish支持多種不同類型的後端存儲,這可以在varnishd啟動時使用-s選項指定。後端存儲的類型包括:
(1)file:使用特定的文件存儲全部的緩存數據,並通過操作系統的mmap()系統調用將整個緩存文件映射至內存區域(如果條件允許);
(2)malloc:使用malloc()庫調用在varnish啟動時向操作系統申請指定大小的內存空間以存儲緩存對象;
(3)persistent(experimental):與file的功能相同,但可以持久存儲數據(即重啟varnish數據時不會被清除);仍處於測試期;
varnish無法追蹤某緩存對象是否存入了緩存文件,從而也就無從得知磁盤上的緩存文件是否可用,因此,file存儲方法在varnish停止或重啟 時會清除數據。而persistent方法的出現對此有了一個彌補,但persistent仍處於測試階段,例如目前尚無法有效處理要緩存對象總體大小超 出緩存空間的情況,所以,其僅適用於有著巨大緩存空間的場景。
八、Varnish檢測後端主機的健康狀態
Varnish可以檢測後端主機的健康狀態,在判定後端主機失效時能自動將其從可用後端主機列表中移除,而一旦其重新變得可用還可以自動將其設定為可用。為了避免誤判,Varnish在探測後端主機的健康狀態發生轉變時(比如某次探測時某後端主機突然成為不可用狀態),通常需要連續執行幾次探測均為新 狀態才將其標記為轉換後的狀態。
每個後端服務器當前探測的健康狀態探測方法通過.probe進行設定,其結果可由req.backend.healthy變量獲取,也可通過varnishlog中的Backend_health查看或varnishadm的debug.health查看。
.probe中的探測指令常用的有:
(1) .url:探測後端主機健康狀態時請求的URL,默認為“/”;
(2) .request: 探測後端主機健康狀態時所請求內容的詳細格式,定義後,它會替換.url指定的探測方式;比如:
.request =
"GET /.healthtest.html HTTP/1.1"
"Host: www.magedu.com"
"Connection: close";
(3) .window:設定在判定後端主機健康狀態時基於最近多少次的探測進行,默認是8;
(4) .threshold:在.window中指定的次數中,至少有多少次是成功的才判定後端主機正健康運行;默認是3;
(5) .initial:Varnish啟動時對後端主機至少需要多少次的成功探測,默認同.threshold;
(6) .expected_response:期望後端主機響應的狀態碼,默認為200;
(7) .interval:探測請求的發送周期,默認為5秒;
(8) .timeout:每次探測請求的過期時長,默認為2秒;
例如:
backend server1 {
.host = “172.16.42.3”;
.port = “80”;
.probe = {
.url= “/.healthcheck.html” #得先創建這個測試頁面;
.timeout= 1s;
.interval= 2s;
.window=5;
.threshold=5;
}
Varnish緩存服務及應用