一、varnish的簡介
Varnish 是一款高性能且開源的反向代理服務器和 HTTP 加速器,其採用全新的軟件體系機構,和現在的硬件體系緊密配合,與傳統的squid 相比,varnish 具有性能更高、速度更快、管理更加方便等諸多優點,很多大型的網站都開始嘗試使用 varnish 來替換 squid,這些都促進varnish 迅速發展起來。
1、varnish的架構
varnish主要運行兩個進程:Management進程和Child進程(也叫Cache進程)。它們的工作原理大致如下圖:
Management進程:
Management進程主要實現應用新的配置、編譯VCL、監控varnish、初始化varnish以及提供一個命令行接口等。Management進程會每隔幾秒鐘探測一下Child進程以判斷其是否正常運行,如果在指定的時長內未得到Child進程的迴應,Management將會重啓此Child進程。
Child進程:
Child進程包含多種類型的線程,常見的如:Acceptor線程:接收新的連接請求並響應;Worker線程:child進程會爲每個會話啓動一個worker線程,因此,在高併發的場景中可能會出現數百個worker線程甚至更多;Expiry線程:從緩存中清理過期內容;
Varnish依賴“工作區(workspace)”以降低線程在申請或修改內存時出現競爭的可能性。在varnish內部有多種不同的工作區,其中最關鍵的當屬用於管理會話數據的session工作區。
日誌:
爲了與系統的其它部分進行交互,Child進程使用了可以通過文件系統接口進行訪問的共享內存日誌(shared memory log),因此,如果某線程需要記錄信息,其僅需要持有一個鎖,而後向共享內存中的某內存區域寫入數據,再釋放持有的鎖即可。而爲了減少競爭,每個worker線程都使用了日誌數據緩存。
共享內存日誌大小一般爲90M,其分爲兩部分,前一部分爲計數器,後半部分爲客戶端請求的數據。varnish提供了多個不同的工具如varnishlog、varnishncsa或varnishstat等來分析共享內存日誌中的信息並能夠以指定的方式進行顯示。
二、varnish的後端存儲
varnish支持多種不同類型的後端存儲,這可以在varnishd啓動時使用-s選項指定。後端存儲的類型包括:
(1)file: 使用特定的文件存儲全部的緩存數據,並通過操作系統的mmap()系統調用將整個緩存文件映射至內存區域(如果條件允許);
(2)malloc: 使用malloc()庫調用在varnish啓動時向操作系統申請指定大小的內存空間以存儲緩存對象,類似於C語言中的malloc動態申請函數;
file和malloc存儲方法類似,重啓緩存服務時,先前緩存的數據不復存在。
三、varnish的狀態引擎(state engine)
說道varnish的狀態引擎,不得不說vcl(Varnish Configuration Language:varnish配置緩存策略的工具)。它是基於域的一種簡單的編程語言,支持算數運算、允許使用正則表達式、支持if語句等。使用vcl語言編寫的緩存策略通常保存於.vcl文件中,其需要編譯成二進制的格式後才能由varnish調用。
VCL用於讓管理員定義緩存策略,而定義好的策略將由varnish的management進程分析、轉換成C代碼、編譯成二進制程序並連接至child進程。varnish內部有幾個所謂的狀態(state),在這些狀態上可以附加通過VCL定義的策略以完成相應的緩存處理機制,因此VCL也經常被稱作“域專用”語言或狀態引擎,“域專用”指的是有些數據僅出現於特定的狀態中。
具體的狀態的是通過定義內置函數來實現的,具體過程如下圖:
vcl各狀態引擎的功用:
vcl_recv:是在Varnish完成對請求報文的解碼爲基本數據結構後第一個要執行的子例程
vcl_fetch:根據服務器端的響應作出緩存決策
vcl_pipe:用於將請求直接發往後端主機;
vcl_hash:自定義hash生成時的數據來源
vcl_pass:用於將請求直接傳遞至後端主機;
vcl_hit:從緩存中查找到緩存對象時要執行的操作;
vcl_miss:從緩存中款查找到緩存對象時要執行的操作;
vcl_deliver:將用戶請求的內容響應給客戶端時用到的方法;
vcl_error:在varnish端合成錯誤響應而時;
四、HTTP協議與varnish
緩存相關的HTTP首部:
HTTP協議提供了多個首部用以實現頁面緩存及緩存失效的相關功能,這其中最常用的有:
(1)Expires:用於指定某web對象的過期日期/時間,通常爲GMT格式;一般不應該將此設定的未來過
長的時間,一年的長度對大多場景來說足矣;其常用於爲純靜態內容如JavaScripts樣式表或圖片
指定緩存週期;
(2)Cache-Control:用於定義所有的緩存機制都必須遵循的緩存指示,這些指示是一些特定的指令,
包括public、private、no-cache(表示可以存儲,但在重新驗正其有效性之前不能用於響應客戶端
請求)、no-store、max-age、s-maxage以及must-revalidate等;Cache-Control中設定的時間會覆
蓋Expires中指定的時間;
(3)Etag:響應首部,用於在響應報文中爲某web資源定義版本標識符;
(4)Last-Mofified:響應首部,用於迴應客戶端關於Last-Modified-Since或If-None-Match首部的請
求,以通知客戶端其請求的web對象最近的修改時間;
(5)If-Modified-Since:條件式請求首部,如果在此首部指定的時間後其請求的web內容發生了更改,
則服務器響應更改後的內容,否則,則響應304(not modified);
(6)If-None-Match:條件式請求首部;web服務器爲某web內容定義了Etag首部,客戶端請求時能獲取
並保存這個首部的值(即標籤);而後在後續的請求中會通過If-None-Match首部附加其認可的標籤列
表並讓服務器端檢驗其原始內容是否有可以與此列表中的某標籤匹配的標籤;如果有,則響應304,
否則,則返回原始內容;
(7)Vary:響應首部,原始服務器根據請求來源的不同響應的可能會有所不同的首部,最常用的是
Vary: Accept-Encoding,用於通知緩存機制其內容看起來可能不同於用戶請求時
Accept-Encoding-header首部標識的編碼格式;
(8)Age:緩存服務器可以發送的一個額外的響應首部,用於指定響應的有效期限;瀏覽器通常根據此
首部決定內容的緩存時長;如果響應報文首部還使用了max-age指令,那麼緩存的有效時長爲
“max-age減去Age”的結果;
五、安裝配置varnish
# rpm包下載地址:https://repo.varnish-cache.org/
rpm -ivh varnish-3.0.5-1.el6.x86_64.rpm varnish-libs-3.0.5-1.el6.x86_64.rpm \
varnish-docs-3.0.5-1.el6.x86_64.rpm
修改varnish的相關參數:
## /etc/sysconfig/varnish 的有效參數如下:
NFILES=131072
MEMLOCK=82000
NPROCS="unlimited"
RELOAD_VCL=1
VARNISH_VCL_CONF=/etc/varnish/default.vcl
VARNISH_LISTEN_PORT=80 # 一般修改varnish的監聽端口
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
VARNISH_ADMIN_LISTEN_PORT=6082
VARNISH_SECRET_FILE=/etc/varnish/secret
VARNISH_MIN_THREADS=50
VARNISH_MAX_THREADS=1000
VARNISH_THREAD_TIMEOUT=120
VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin
VARNISH_STORAGE_SIZE=1G
VARNISH_STORAGE_MEM_SIZE=128M # 使用malloc存儲方法時的內存大小
VARNISH_STORAGE="malloc,${VARNISH_STORAGE_MEM_SIZE}" # 配置後端存儲方法爲malloc
VARNISH_TTL=120
DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \
-f ${VARNISH_VCL_CONF} \
-T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \
-t ${VARNISH_TTL} \
-w ${VARNISH_MIN_THREADS},${VARNISH_MAX_THREADS},${VARNISH_THREAD_TIMEOUT} \
-u varnish -g varnish \
-S ${VARNISH_SECRET_FILE} \
-s ${VARNISH_STORAGE}"
配置vcl的相關參數:
# 修改 /etc/varnish/default.vcl 中以下內容:
backend default {
.host = "172.16.10.11";
.port = "80";
}
結果展示:
此時我們訪問:http://172.16.10.77時,會發現第一次響應速度比較慢,隨後的響應是很快的。
此時,可以通過varnishstat查看緩存命中情況。
也可以自行定義緩存策略:
Varnish內置變量:
請求到達時可用的內置變量:
req.url
req.request
req.http.HEADER
req.restarts: 請求被重啓的次數;
server.ip
server.port
server.hostname
client.ip
req.backend
向後後端主機請求時可用的內置變量
bereq.url
bereq.request
bereq.http.HEADER
bereq.connect_timeout
bereq.proto
從後端主機獲取到響應的object時可用的內置變量
beresp.status
beresp.response
beresp.http.HEADER
beresp.ttl
beresp.backend.name
beresp.backend.ip
beresp.backend.port
緩存對象進入緩存時可用的內置變量(只能用於vcl_hit或vcl_error,且大多爲只讀)
obj.status
obj.response
obj.ttl
obj.hits
obj.http.HEADER
響應給客戶端時可用的內置變量
resp.proto
resp.status
resp.response
resp.http.HEADER
自定義web.vcl,內容如下:
[root@varnish ~]# cat /etc/varnish/web.vcl
# 定義後端主機,並提供健康狀態檢測
backend web1 {
.host = "172.16.10.11";
.probe = {
.url="/index.html";
.interval=2s;
.window=8;
.threshold=2;
}
}
backend web2 {
.host = "172.16.10.16";
.probe = {
.url="/index.html";
.interval=2s;
.window=8;
.threshold=2;
}
}
director websrv round-robin {
{ .backend = web1; }
{ .backend = web2; }
}
# 定義一個acl 目的是進行緩存裁剪
acl purgers {
"127.0.0.1";
"172.16.0.0"/16;
}
sub vcl_recv {
set req.backend = websrv;
if (req.restarts == 0) {
if (req.http.x-forwarded-for) {
set req.http.X-Forwarded-For =
req.http.X-Forwarded-For + ", " + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}
# 定義那些內容不緩存的
if (req.url ~ "^/test.html$") {
return(pass);
}
if (req.request == "PURGE") {
if (!client.ip ~ purgers) {
error 405 "Method not allowed";
}
return (lookup);
}
if (req.request != "GET" &&
req.request != "HEAD" &&
req.request != "PUT" &&
req.request != "POST" &&
req.request != "TRACE" &&
req.request != "OPTIONS" &&
req.request != "DELETE") {
/* Non-RFC2616 or CONNECT which is weird. */
return (pipe);
}
if (req.request != "GET" && req.request != "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 (lookup);
}
sub vcl_pipe {
return (pipe);
}
# sub vcl_hash {
# hash_data(req.url);
# if (req.http.host) {
# hash_data(req.http.host);
# } else {
# hash_data(server.ip);
# }
# return (hash);
# }
sub vcl_hit {
if (req.request == "PURGE") {
purge;
error 200 "Purged";
}
return (deliver);
}
sub vcl_miss {
if (req.request == "PURGE") {
purge;
error 404 "Not in cache";
}
return (fetch);
}
sub vcl_pass {
if (req.request == "PURGE") {
error 502 "PURGE on a passed object";
}
return (pass);
}
#
# sub vcl_fetch {
# if (beresp.ttl <= 0s ||
# beresp.http.Set-Cookie ||
# beresp.http.Vary == "*") {
# /*
# * Mark as "Hit-For-Pass" for the next 2 minutes
# */
# set beresp.ttl = 120 s;
# return (hit_for_pass);
# }
# return (deliver);
# }
sub vcl_deliver {
set resp.http.X-Age = resp.http.Age;
unset resp.http.Age;
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT Via" + " " + server.hostname;
} else {
set resp.http.X-Cache = "MISS Via" + " " + server.hostname;
}
return (deliver);
}
# sub vcl_deliver {
#
#
# return (deliver);
# }
#
# sub vcl_error {
# set obj.http.Content-Type = "text/html; charset=utf-8";
# set obj.http.Retry-After = "5";
# synthetic {"
# <?xml version="1.0" encoding="utf-8"?>
# <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
# "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
# <html>
# <head>
# <title>"} + obj.status + " " + obj.response + {"</title>
# </head>
# <body>
# <h1>Error "} + obj.status + " " + obj.response + {"</h1>
#"} + obj.response + {"# Guru Meditation:
#XID: "} + req.xid + {"# <hr>
#Varnish cache server# </body>
# </html>
# "};
# return (deliver);
# }
#
sub vcl_init {
return (ok);
}
sub vcl_fini {
return (ok);
}
可以使用varnishadm命令手動編譯vcl文件:
補充:
vcl自動編譯的方法,編輯 /etc/sysconfig/varnish 文件中的:
VARNISH_VCL_CONF=/etc/varnish/web.vcl