Linux Varnish
Web Cache:通常的大型網站架構都會使用緩存,緩存通常位於前段代理與後端服務器之間,當用戶發出請求之後,會首先經過代理查找緩存,若是緩存中有相關的數據就直接反回給客戶端,若是沒有就繼續向後端真實提供數據的原始服務器請求相關數據,而後再返回給客戶端;你可能會有疑問,這樣引入緩存難道不會增長網絡延遲嗎?固然會,可是引入緩存的效益是能夠徹底抵消引入緩存帶來的延遲的,由於若是不使用緩存,客戶端的全部請求都會被髮送到後端原始服務器,這樣一來對後端原始服務器的壓力就會急劇上升,尤爲是訪問量巨大的大型網站,更是扛不住的,還有一種緣由就是緩存能夠直接緩存某些用戶請求的動態內容的結果,無需服務器再執行一遍,這樣也會節省不少時間,因此這種狀況下引入緩存是利大於弊的;
緩存之因此存在的緣由:
之因此有緩存這一說法,是由於程序(指令和數據)具備局部性;其局部性包括時間局部性和空間局部性;
時間局部性:對於同一程序,剛剛被訪問到的數據,在接下來有很大可能還會再次被訪問到;
空間局部性:一個數據被訪問到,與之較近的數據也可能被訪問到;因此纔有熱區數據這一說法;
正是由於程序的此種特性,因此緩存纔有存在的意義;
緩存的存儲形式:
緩存在內存中通常是以key-value的形式存在的,其中key通常爲訪問路徑,其能夠是URL或者IP地址等,而且key通常都會被hash處理之後再進行存儲,由於被hash之後的數據查找起來方便、速度快(O(1));value通常爲訪問路徑所對應的資源;
緩存命中率:
若是在緩存中有用戶須要的數據咱們稱爲緩存命中;
命中率:hit/(hit+miss)
LRU:當緩存空間被耗盡時,若是有新的緩存進來,緩存服務器就會根據LRU(最近最少使用)算法將一些緩存清理掉,用來存儲新的緩存數據;而且緩存也是有生命週期的(好比緩存過時),會按期進行清理;
緩存數據類型:可緩存的和不可緩存的;
可緩存的:好比網站的圖標,各類圖片、文本等
不可緩存的:帶有用戶私有信息的,好比cookie信息等;
緩存處理的步驟:
接收請求→解析請求(提取請求中的URL及各類HTTP首部)→查詢緩存→新鮮度檢測(緩存是否過時)→建立響應報文→返回響應→記錄日誌
新鮮度檢測機制:
過時日期:
HTTP/1.0經過http response中的expires首部指定資源過時時間(絕對時間|相對時間);
HTTP/1.1經過http response中的Cache-Control:max-age來指定資源過時時間;
有效性再驗證(revalidate):
在返回緩存前先到服務器驗證一下,詢問服務器本身的某個緩存資源是否過時,可否繼續使用,服務器會進行檢測:
1.若是服務器中的原始內容沒有改變,則僅返回響應首部(不附帶body部分),響應碼爲304(not modified)
2.若是服務器中的原始內容發生改變,則正常響應,響應碼爲200;
3.若是響應內容不存在了,則響應404;此時緩存中的對象(object)也應該刪除;
條件式請求首部:
If-Modified-Since:基於時間戳驗證,自從某個時間點以後資源是否發生改變;
If-Unmodified-Since:基於時間戳驗證,自動某個時間點以後資源是否沒有發生改變;
一般使用在變化週期慢的資源中;
If-Match:基於Etag驗證資源是否匹配;
If-None-Match:基於Etag驗證資源是否不匹配;
一般使用在變化週期快的資源中;
Varnish (v4):相對於squid來講比較輕量級;
Varnish是一個緩存HTTP數據的反向代理。它接收來自客戶端的請求並嘗試從緩存中回答它們。若是Varnish沒法使用緩存應答來自客戶端的請求,它會將請求轉發給後端,獲取後端服務器的響應,而後將響應數據存儲在緩存中再將其響應給客戶端。
當咱們使用緩存時通常在前端的負載均衡調度器上建議使用基於URI的負載均衡調度算法,使對同一資源的請求始終發往同一緩存服務器;
Varnish Arch:
css
Varnish程序運行起來之後會啓動一個管理(Management)進程和一個或多個子進程(Child)組成,其中一個子進程能夠生成多個線程,而後經過線程對請求完成響應等操做;其中管理進程負責編譯配置文件並從配置文件中讀取配置參數、管理並監控子進程、完成varnish的初始化、提供CLI管理接口等;子進程負責加載管理進程編譯好的配置文件中的參數,而後提供相應的緩存服務;子進程還負責接收命令行參數、存儲緩存、hash數據、記錄日誌、統計數據、清理過時緩存、接收鏈接請求、處理用戶請求、與後端服務器交互等;
Note:Varnish的配置文件是使用VCL編寫的,當Varnish程序讀取配置文件是並非像ningx這種軟件同樣,直接讀取配置文件中的參數來設置各類功能的,而是首先將編寫好的配置文件經過VCL編譯器調用C編譯器編譯成二進制代碼,而後才能被Varnish加載使用的;
Varnish的日誌功能:
Varnish日誌使用的是Shared Memory Log(共享內存日誌),其默認大小通常爲90MB;而且分爲兩部分:一部分爲計數器,另外一部分爲請求相關的日誌數據;
VCL:Varnish Configuration Language
Varnish的配置文件就是使用這種語言編寫的,其爲基於域的簡單編程語言;
Varnish的緩存都是存放在內存中的,因此對內存的操做效率就極爲關注,一般狀況先通常的軟件都是使用malloc()和free()函數來分配和釋放內存,可是varnish使用的並非這個,而是另一種更高效的組件jemalloc來管理內存,能夠實現併發malloc;
Varnish存儲緩存的方式:能夠經過varnishd 的」-s」選項來進程設置;
file:將全部緩存放在單個文件中,varnish進程內部會對這些存儲在一塊兒的各個緩存做區分,存儲在磁盤中,因此磁盤IO性能可能成爲瓶頸,可是重啓會失效;
malloc:基於內存做緩存,性能高,大小有限,重啓失效;
persistent:基於文件的持久存儲;(暫時是實驗開發中的功能);
配置Varnish的三種方法:
1.varnishd應用程序的命令行參數參數;
用於設置監聽的套接字、使用的存儲類型等;
經過」-p」選項指明各類參數;可在運行時實時配置;
Note:能夠經過查看/usr/lib/systemd/system/varnish.service的內容,進行了解;
2.VCL:配置緩存系統的緩存機制;
經過vcl配置文件進行配置,先編譯後應用,依賴於C編譯器;
Varnish配置:
配置文件:
/etc/varnish/default.vcl
varnish使用的vcl編寫的配置文件,設置緩存機制;
/etc/varnish/varnish.params
varnish啓動時使用的參數,其中定義的變量會被/usr/lib/systemd/system/varnish.service調用;也能夠在命令行中使用varnishd命令手動指定啓動時加載的參數及文件;
監聽端口:
服務端口默認監聽在6081號端口上;
管理端口默認監聽在6082號端口上;
實例:
三臺主機,一臺centos7(ip:192.168.80.139)運行varnish v4,兩臺centos6(clone1,ip:192.168.80.13一、clone2)做爲後端服務器運行httpd;
Centos7:
vim /etc/varnish/default.vcl
backend default {
.host = "192.168.80.131"; 指定後端服務器地址,也可使用主機名;
.port = "80"; 指定後端服務器WEB服務監聽的接口;
}
Centos6:clone1
service httpd start
for i in {1..10} ; do echo "<h1>page $i on clone1</h1>" > /var/www/html/test$i.html ; done
Centos6:clone2
service httpd start
for i in {1..10} ; do echo "<h1>page $i on clone2</h1>" > /var/www/html/test$i.html ; done
Centos7:
systemctl start varnish.service
瀏覽器鍵入:http://192.168.80.139:6081/test1.html
管理工具介紹:varnishadm
-S:指定祕鑰文件
-T:指定被管理主機的鏈接的地址和端口
例子: varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 →便可進入管理接口;
使用help獲取幫助信息;
varnish> help
help [<command>] 獲取幫助
ping [<timestamp>] ping測試
auth <response>
quit 退出CLIL界面
banner
status 列出子進程的狀態信息
start 啓動子進程
stop 中止子進程
vcl.load <configname> <filename> 加載.vcl配置文件
configname:自定義配置文件名
filename:未編譯的.vcl文件
例子:varnish> vcl.load test1 default.vcl
vcl.inline <configname> <quoted_VCLstring>
vcl.use <configname> 指定欲使用.vcl配置文件
vcl.discard <configname> 刪除編譯後的指定配置文件
vcl.list 列出可用的.vcl配置文件
param.show [-l] [<param>] 列出支持的參數
param.set <param> <value> 設置參數
panic.show
panic.clear
storage.list 顯示使用的存儲機制
vcl.show [-v] <configname> 顯示.vcl文件編譯以前的內容
backend.list [<backend_expression>] 列出後端服務器列表
backend.set_health <backend_expression> <state>
ban <field> <operator> <arg> [&& <field> <oper> <arg>]... 手動清理緩存中的緩存對象
ban.list
日誌工具:
varnishlog
varnishncsa
其餘工具:
varnishtop:日誌條目排序
varnishstat:緩存統計信息
Varnish的VCL配置文件:
當請求進入Varnish代理中時,Varnish服務會對其進行各類各樣的檢查,好比是否容許其訪問、其訪問的資源類型(動|靜)、是否含有cookie、請求頭部中的方法爲什麼、怎麼將請求轉發至後端、服務器端響應的資源是否進行緩存等都是須要考慮的;這些配置都是要在.vcl文件中設定的;爲了解決上面的多種問題,Varnish設計了一種相似netfilter的鉤子函數的機制,在Varnish中稱之爲狀態引擎;
Varnish(v3)接收請求後的流程:請求首先會被vcl_recv()這個狀態引擎接收,而後判斷這個請求是否爲一個可緩存對象(好比PUT和POST方法就不可緩存),若是不是就將請求傳到vcl_fetch()這個狀態引擎中,到後端服務器取相應資源,而後再通過vcl_delive()r這個狀態引擎構建響應報文,響應客戶端;若是是可緩存對象,首先會交給vcl_hash()進行URL的hash計算,而後將hash後的結果與緩存中的key進行比較,若是有匹配的key則爲命中(hit),而後通過vcl_hit()進行緩存處理,最後通過vcl_deliver()構建響應報文,響應給客戶端,若是沒有匹配成功則爲未命中(miss),則會經過vcl_miss()作未命中處理,而後經過vcl_fetch()去後端服務器取資源,最後經過vcl_deliver()構建響應報文,響應給客戶端;
Note:從上面能夠看出來,各狀態引擎之間有相關性;前一個狀態引擎(engine)若是能夠有多種下游的狀態引擎,則上游的狀態引擎須要使用return()指明要轉移至的下游狀態引擎;
處理上面提到的那幾種狀態引擎以外還有一些經常使用的:
vcl_pass():若是請求頭部的方法爲PUT、POST或者帶有cookie或者須要驗證則就會使用此狀態引擎;
vcl_error():直接由Varnish在前端構成錯誤頁面信息,告訴客戶端有錯誤發生;
vcl_pipe():當遇到本身沒法理解的後端響應時,本身什麼也不作,直接將請求傳遞給後端服務器;
Varnish服務接收請求後的流程圖(v4):
html
圖片來自varnish-book;
vcl_recv():req.
vcl_hash():req.
vcl_hit():req.、obj.
vcl_miss():req.、bereq.
vcl_backend_fetch():req.、bereq.、beresp.
vcl_deliver():resp.
VCL語法:
1.使用//、#、/*foo*/做爲註釋;
2.每一個狀態引擎均可以當作是varnish的子例程,能夠經過」sub」關鍵字進行定義;
3.不支持循環;支持不少內置的狀態變量;
4.使用return(action)來選定其下游狀態引擎;
5.使用域做爲配置段,好比 sub req_recv { };
6.能夠自定義設置;
7.支持操做符:=、==、~、!、&&、||;
8.使用if做條件判斷,能夠嵌套;
if (CONDTION) {
} else {
}
9.經過」set」來設定變量值;
set name=value
10.經過」unset」來撤銷設定的變量值;
unset name
11.VCL變量
client
client.ip:客戶端的IP地址;
server
server.ip:接收客戶端鏈接的IP地址;
server.hostname:服務器的主機名;
req
req.http.*:調用request報文中http協議的指定的HEADER首部;
req.http.X-Forwarded-For
req.method | req.request:指定請求方法;
req.url:請求的URL
req.proto:客戶端使用的HTTP協議版本,一般爲「HTTP / 1.1」或「HTTP / 2.0」;
req.ttl:查找高速緩存返回響應的最大時間限制;
resp
resp.proto:用於響應的HTTP協議版本;
resp.status:用於響應的HTTP狀態碼;
resp.http.*:響應報文中的HTTP頭部信息;
bereq
bereq.method:請求方法
bereq.url:請求的URL,從req.url複製而來
bereq.http.*:由Varnish發日後端主機的請求報文中的http協議的指定HEADER首部;
bereq.backend:後端主機;
bereq.backend:指明使用的後端主機;
beresp
beresp.status:後端主機響應的狀態碼信息;
beresp.reason:後端主機返回的HTTP狀態消息(緣由短語);
beresp.http.*:調用後端主機返回的http報文中指定的HEADER首部;
brresp.ttl:後端服務器響應的內容的餘下的生存時長;
obj
obj.proto:存儲在對象中的HTTP協議版本;
obj.status:存儲在對象中的HTTP狀態碼;
obj.reason:存儲在對象中的HTTP緣由短語;
obj.http.*:存儲在對象中HTTP標頭;
obj.hits:從緩存中命中次數;
obj.ttl:存儲在對象中的ttl值;
storage
storage.<name>.free_space:查看緩存空間空閒的大小;
storage.<name>.used_space:查看緩存空間已使用的大小;前端
Type: BYTES
變量詳解:https://varnish-cache.org/docs/6.2/reference/vcl.html#local-server-remote-and-client
支持虛擬主機:
sub vcl_recv {
if (req.http.host == www.guowei.com) {
set bereq.http.host = 「www.GW.com」;
}
}
強制對某資源的請求不檢查緩存:
sub vcl_recv {
if (req.url ~ 「\.jpg$」 || req.url ~ 「(?i)\.png$」) {
return(pass);
}
}
只要是.jpg或者.png結尾的請求都不查找緩存,而且不區分大小寫「(?i)」;
對特定類型的資源取消其私有的cookie標識:
sub vcl_backend_response {
if (beresp.http.cache-control !~ 「s-maxage」) {
if (bereq.url ~ 「(?i)\.jpg$」) {
set beresp.ttl = 600s;
unset beresp.http.Set.-Cookie;
}
if (bereq.url ~ 「(?i).\css$」) {
set beresp.ttl = 300s;
unset beresp.http.Set-Cookie;
}
}
實現負載均衡:
須要在配置文件中添加:import directors;
web
backend name { }:設置後端服務器屬性;
算法
例子:
vcl 4.0;
import directors;
# Default backend definition. Set this to point to your content server.
backend clone1 {
.host = "192.168.80.131";
.port = "80";
}
backend clone2 {
.host = "192.168.80.134";
.port = "80";
}
sub vcl_init {
new web = directors.round_robin();
web.add_backend(clone1);
web.add_backend(clone2);
}
sub vcl_recv {
if (req.url ~ "/test3.html$") {
return(pass);
} else {
set req.backend_hint = web.backend();
}
}
後端主機的健康狀態檢測方式:
express
.url:斷定後端主機健康與否要請求的url;
.expected_response:指望的響應狀態碼,默認爲200;
例子:
backend clone1 {
.host = "192.168.80.131";
.port = "80";
.probe = {
.url = "/test1.html";
}
}
backend clone2 {
.host = "192.168.80.134";
.port = "80";
.probe = {
.url = "/test1.html";
}
}
動靜分離:
backend clone1 {
.host = "192.168.80.131";
.port = "80";
.probe = {
.url = "/test1.html";
}
}
backend clone2 {
.host = "192.168.80.134";
.port = "80";
.probe = {
.url = "/test1.html";
}
}
sub vcl_recv {
if (req.url ~ "/test4.html$") {
set req.backend_hint = clone1;
} else {
set req.backend_hint = clone2;
}
}編程
注:根據馬哥視頻作的學習筆記,若有錯誤,歡迎指正;侵刪;vim