Varnish是一款高性能的開源HTTP加速器,挪威最大的在線報紙 Verdens Gang 使用3臺Varnish代替了原來的12臺Squid,性能比以前更好。作者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。
Varnish與一般服務器軟件類似,分爲master(management)進程和child(worker,主要做cache的工作)進程。master進程讀入命令,進行一些初始化,然後fork並監控child進程。child進程分配若干線程進行工作,主要包括一些管理線程和很多woker線程。
針對文件緩存部分,master讀入存儲配置(-s file[,path[,size[,granularity]]] ),調用合適的存儲類型,然後創建/讀入相應大小的緩存大文件。接着,master初始化管理該存儲空間的結構體。這些變量都是全局變量,在fork以後會被child進程所繼承(包括文件描述符)。
在child進程主線程初始化過程中,將前面打開的存儲大文件整個mmap到內存中(如果超出系統的虛擬內存,mmap失敗,進程會減少原來的配置mmap大小,然後繼續mmap),此時創建並初始化空閒存儲結構體,掛到存儲管理結構體,以待分配。
接着,真正的工作開始,Varnish的某個負責接受新HTTP連接的線程開始等待用戶,如果有新的HTTP連接過來,它總負責接收,然後叫醒某個等待中的線程,並把具體的處理過程交給它。Worker線程讀入HTTP請求的URI,查找已有的object,如果命中則直接返回並回複用戶。如果沒有命中,則需要將所請求的內容,從後端服務器中取過來,存到緩存中,然後再回復。
分配緩存的過程是這樣的:它根據所讀到object的大小,創建相應大小的緩存文件。爲了讀寫方便,程序會把每個object的大小變爲最接近其大小的內存頁面倍數。然後從現有的空閒存儲結構體中查找,找到最合適的大小的空閒存儲塊,分配給它。如果空閒塊沒有用完,就把多餘的內存另外組成一個空閒存儲塊,掛到管理結構體上。如果緩存已滿,就根據LRU機制,把最舊的object釋放掉。
釋放緩存的過程是這樣的:有一個超時線程,檢測緩存中所有object的生存期,如果超初設定的TTL(Time To Live)沒有被訪問,就刪除之,並且釋放相應的結構體及存儲內存。注意釋放時會檢查該存儲內存塊前面或後面的空閒內存塊,如果前面或後面的空閒內存和該釋放內存是連續的,就將它們合併成更大一塊內存。
整個文件緩存的管理,沒有考慮文件與內存的關係,實際上是將所有的object都考慮是在內存中,如果系統內存不足,系統會自動將其換到swap空間,而不需要varnish程序去控制。
Varnish特點與Squid的對比
Varnish特點:
基於內存緩存,重啓後數據將消失。
利用虛擬內存方式,I/O性能好。
支持設置0~60秒內的精確緩存時間。
VCL(全稱varnish config language,這是Varnish自己領域的特定語言)配置管理比較靈活。
32位機器上緩存文件大小爲最大2G。
具有強大的管理功能,例如top,stat,admin,list等。
狀態機設計巧妙,結構清晰。
利用二叉堆管理緩存文件,達到積極刪除目的。
Varnish與Squid的對比:
相同點:
都是一個反向代理服務器;
都是開源軟件;
Varnish相較於Squid的優點:
Varnish的穩定性很高,兩者在完成相同負荷的工作時,Squid服務器發生故障的機率要高於Varnish,因爲使用Squid要經常重啓;
Varnish訪問速度更快,Varnish採用了“Visual Page Cache”技術,所有緩存數據都直接從內存讀取,而Squid是從硬盤讀取,因此Varnish在訪問速度方面會更快;
Varnish可以支持更多的併發連接,因爲Varnish的TCP連接釋放要比Squid快,所以在高併發連接情況下可以支持更多TCP連接;
Varnish可以通過管理端口,使用正則表達式批量的清除部分緩存,而Squid是做不到的;
Squid屬於是單進程使用單核CPU,但Varnish是通過fork形式打開多進程來做處理,所以是合理的使用所有核來處理相應的請求;
Varnish相較於Squid的缺點:
Varnish在高併發狀態下CPU、I/O和內存等資源開銷都高於Squid;
Varnish進程一旦Hang(掛起)、Crash(崩潰)或者重啓,緩存數據都會從內存中完全釋放,此時所有請求都會發送到後端服務器,在高併發情況下,會給後端服務器造成很大壓力;
在Varnish使用中如果單個url的請求通過HA/F5(負載均衡)每次請求不同的varnish服務器中,被請求varnish服務器都會被穿透到後端,而且同樣的請求會在多臺服務器上緩存,也會造成Varnish的緩存的資源浪費,也會造成性能下降。
實驗環境
- 192.168.1.2 CentOS 7 Varnish
-
Web1 192.168.1.3 CentOS 7 Nginx+PHP
-
Web2 192.168.1.4 CentOS 7 Nginx+PHP
-
Web3 192.168.1.5 CentOS 7 Nginx+PHP
-
Varnish官網地址:http://varnish-cache.org/
-
Yum 安裝方式,https://packagecloud.io/varnishcache
-
curl -s https://packagecloud.io/install/repositories/varnishcache/varnish64/script.rpm.sh | sudo bash
-
yum -y install varnish
-
varnishd -V 查看版本信息
-
/etc/varnish #varnish工作目錄
/etc/varnish/default.vcl #默認配置各Child/Cache線程的緩存策略
/usr/bin/varnishadm #varnish內置的管理工具,端口默認爲 6082
/usr/bin/varnishhist #Shared Memory Log交互工具
/usr/bin/varnishlog
/usr/bin/varnishncsa
/usr/bin/varnishstat
/usr/bin/varnishtop
/usr/bin/varnishtest #內置測試工具程序
/usr/lib/systemd/system/varnish.service #varnish服務啓動腳本
/usr/lib/systemd/system/varnishncsa.service #日誌持久服務啓動腳本 -
/usr/sbin/varnishreload #VCL配置文件重載程序
- varnish 主配置文件 /etc/varnish/default.vcl varnish使用了一種叫VCL的語言去對其進行配置
- vcl_recv 函數: 接收和處理請求,當請求到達併成功接收後被調用,判斷請求的數據並決定如何處理請求;
pass 表示進入pass模式,將請求交給vcl_pass 函數
pipe 表示進入pipe模式,將請求交個vcl_pipe函數
error code[reason] 表示返回'code'給客戶端並放棄請求, 'code'是錯誤標示例如200 405等,'reason'是錯誤原因
- vcl_pipe函數:該函數在進入pipe模式時被調用,用於將請求直接傳遞給後端主機,在請求和返回內容沒有改變的情況下,將不變的內容返回給客戶端,直到這個鏈接關閉;該函數有這麼些返回值=>
error code[reason] 同vcl_recv
pipe
- vcl_pass函數: 該函數進入pass模式被調用,用於直接將請求發送給後端主機,後端主機響應後發送給客戶端,不進行任何緩存,每次都返回最新內容,該函數有幾個返回值=>
error code[reason] 同vcl_recv
pass
- lookup函數: 表示在緩存中查找到被請求的對象,並且根據查找的結果交給vcl_hit函數(命中)或vcl_miss(未命中)函數處理
-
vcl_hit函數:在執行lookup後,如果在緩存中找到對象,該函數將會被自動調用,該函數有以下幾個返回值 =>
deliver 表示找到內容發送給客戶端,把控制權交給vcl_deliver函數
error code[reason] 同vcl_recv
pass
- vcl_miss函數: 在執行lookup後,在緩存中沒有找到對象,該函數被調用,該函數可用於判斷是否從後端請求內容,該函數有以下幾個返回值 =>
fetch 表示從後端獲取內容,並把控制權交給vcl_fetch函數
error code[reason] 同vcl_recv
pass
- vcl_fetch函數:從後端主機更新緩存獲取內容後調用該函數,接着判斷獲取的內容是放入緩存還是直接給客戶端,該函數有以下幾個返回值=》
error code[reason] 同vcl_recv
pass
deliver
- vcl_deliver函數:將在緩存中找到的內容發送給客戶端調用的方法,該函數有以下幾個返回值=》
error code[reason] 同vcl_recv
deliver
- vcl_timeout函數:在緩存內容到期前調用該函數,該函數有以下幾個返回值=》
discard 表示中緩存中清除該內容
fetch
- vcl_discard函數:緩存內容到期後或者緩存空間不夠時自動被調用,該函數有以下幾個返回值=》
keep 表示在內容中保留該緩存
discard
- VCL處理流程可以用下面這張圖表示
Receive狀態:請求處理的入口狀態,根據VCL規則判斷該請求應該在Pass或Pipe,還是進入Lookup(到本地緩存中查詢)
Lookup狀態:進入此狀態後,會在hash表中查詢數據,如果找到則進入Hit狀態,否則進入Miss狀態
Pass狀態:在此狀態下會進入後端獲取內容,既進入Fetch狀態
Fetch狀態:從後端獲取請求,發送請求,獲得數據,並存儲到本地
Deliver狀態:將獲取的數據發送給客戶端,然後完成本次請求
- VCL裏面還有些變量,可以用在VCL函數中,用於邏輯處理
請求到達後可以使用的內置公用變量:
req.backend => 指定對應的後端主機
server.ip => 表示服務器端IP
client.ip => 表示客戶端IP
req.request => 指定請求的類型,如GET POST PUT DELETE
req.url => 知道請求的URL
req.proto => 表示客戶端發起請求的HTTP協議版本
req.http.header => 表示請求中的頭部信息
req.restarts => 表示請求重啓的次數,默認最大值爲4
- VCL向後端主機請求時使用的內置變量
beresp.request => 指定請求的類型,如GET POST
beresp.url => 指定請求的地址
beresp.proto => 指定請求的協議版本號
beresp.http.header => 對應請求的HTTP頭信息
beresp.ttl => 表示緩存的週期,單位秒
- 從cache或後端主機獲取內容後可以使用的內置變量
obj.status => 表示返回的狀態碼,如200 404 500 等
obj.cacheable => 表示返回的內容是否可以緩存
obj.valid => 表示是否是有效的HTTP應答
obj.response => 表示應答的狀態信息
obj.ttl => 表示返回內容的生存週期,單位秒
obj.lastuse => 表示上次請求到現在的間隔,單位秒
- 客戶端應答時可以使用的變量
resp.status => 表示返回給客戶端的狀態碼
resp.proto => 表示返回給客戶端的HTTP協議版本
resp.http.header => 表示返回給客戶端的頭信息
resp.response => 表示返回給客戶端的狀態信息
- 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.myweb.com"
"Connection: close";
(3) .window:設定在判定後端主機健康狀態時基於最近多少次的探測進行,默認是8;
(4) .threshold:在.window中指定的次數中,至少有多少次是成功的才判定後端主機正健康運行;默認是3;
(5) .initial:Varnish啓動時對後端主機至少需要多少次的成功探測,默認同.threshold;
(6) .expected_response:期望後端主機響應的狀態碼,默認爲200;
(7) .interval:探測請求的發送週期,默認爲5秒;
(8) .timeout:每次探測請求的過期時長,默認爲2秒;
-
設置後臺健康狀態檢查
-
vim /etc/varnish/health_check.vcl
-
probe chk {
.interval = 5s; # 每隔1秒鐘嘗試一次
.timeout = 3s; #每次探測請求的過期時長
.window = 10; # 最多嘗試10次,判斷採樣的樣本
.threshold = 8; # 如果採樣10次,如果有8次是錯誤的,就認爲是失敗的,上線也是一樣
.request =
"GET /.test.html HTTP/1.1" #測試根目錄下的 .test.html ,這個文件必須存在且能夠訪問
"Host: www.myweb.com"
"Connection: close"
"Accept-Encoding: foo/bar";
} -
後端服務器地址池配置
vim /etc/varnish/backends.vcl -
import directors;
include "health_check.vcl";backend web1 {
.host = "192.168.1.3";
.port = "80";
.probe = chk;
}backend web2 {
.host = "192.168.1.4";
.port = "80";
.probe = chk;
}backend web3 {
.host = "192.168.1.5";
.port = "80";
.probe = chk;
}
## 負載均衡池
sub vcl_init {
new web = directors.round_robin(); #以輪詢方式調度
web.add_backend(web1);
web.add_backend(web2);
web.add_backend(web3);
} -
緩存規則主配置
# vim /etc/varnish/default.vcl -
vcl 4.1;
import std;
include "backends.vcl";
acl purge_ip { # 定義acl訪問控制,只允許以下網段或主機執行purgers操作
"127.0.0.1";
"192.168.0.0"/16;
}
sub vcl_recv {
if (req.method == "PURGE"){ # 如果請求方法是PURGE,並且客戶端IP在上面定義的網段內的就允許執行PURGE操作,否則生成一個錯誤頁面返回給用戶
if (!client.ip ~ purge_ip) {
return(synth(405,"Purging not allowed for "+client.ip));
}
return(purge);
}
if (req.http.host ~ "^(www.)?myweb.com") {
set req.http.host = "www.myweb.com";
set req.backend_hint = web.backend();
}
}sub vcl_purge{
return (synth(200,"success"));
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT cache" + " " + server.ip;
}
else {
set resp.http.X-Cache = "Miss cache";
}
}sub vcl_pass {
if (req.method == "PURGE") {
return (synth(502, "PURGE on a passed object."));
}
} -
爲了方便測試,將 varnish 默認啓動端口 6081 更改爲 80
-
編輯 vim /usr/lib/systemd/system/varnish.service
-
[Unit]
Description=Varnish Cache, a high-performance HTTP accelerator
After=network-online.target[Service]
Type=forking
KillMode=process# Maximum number of open files (for ulimit -n)
LimitNOFILE=131072# Locked shared memory - should suffice to lock the shared memory log
# (varnishd -l argument)
# Default log size is 80MB vsl + 1M vsm + header -> 82MB
# unit is bytes
LimitMEMLOCK=85983232# Enable this to avoid "fork failed" on reload.
TasksMax=infinity# Maximum size of the corefile.
LimitCORE=infinity#ExecStart=/usr/sbin/varnishd -a :6081 -f /etc/varnish/default.vcl -s malloc,256m
ExecStart=/usr/sbin/varnishd -a :80 -f /etc/varnish/default.vcl -s malloc,256m #修改默認監聽端口爲 :80
ExecReload=/usr/sbin/varnishreload[Install]
WantedBy=multi-user.target -
systemctl daemon-reload
-
systemctl restart varnish
- 使用 varnishadm 進行後端健康檢查
- backend.list
200
Backend name Admin Probe Health Last change
boot.web1 probe 10/10 healthy Sat, 28 Mar 2020 08:03:18 GMT
boot.web2 probe 10/10 healthy Sat, 28 Mar 2020 08:01:58 GMT
boot.web3 probe 10/10 healthy Sat, 28 Mar 2020 08:01:58 GMT
boot.web probe 3/3 healthy Sat, 28 Mar 2020 08:03:18 GMT
- 編輯 /etc/hosts 添加 192.168.1.2 www.myweb.com
- curl www.myweb.com
- X-Cache: HIT cache192.168.1.2
- 清除緩存 curl -X PURGE www.myweb.com
- curl www.myweb.com
- 將其中一臺的 web 服務停掉
- systemctl stop nginx
- web2 的狀態爲 sick 不可用
- 重新啓動 web2 的nginx