一、WebCache
WebCache,web緩存,是一種緩存技術,用於臨時存儲(緩存)的網頁文件,如HTML頁面和圖像等靜態資源(此處不絕對,也可以緩存動態頁面,但是存儲到本地後也爲靜態文件),減少帶寬以及後端服務器的壓力,通常一個WebCache也是一個反向代理軟件,既可以通過緩存響應用戶的請求,當本地沒有緩存時,可以代理用戶請求至後端主機。(自己學習總結,由於昨天剛接觸varnish,所以有錯的地方還請擔待)
WebCache分爲正向和反向之分,一般正向WebCache不常用,本文以反向WebCache爲主。
WebCache的由來:
由於程序具有局部性,而局部性分爲:時間局部性和空間局部性
(1)時間局部性是指:在單位時間內,大部分用戶訪問的數據只是熱點數據(熱點數據指經常被訪問的數據)
(2)空間局部性是指:比如,某新聞網站突然出來一個重大新聞,此新聞會被被反覆訪問。
WebCache的新鮮度監測機制:數據都是可變的,所以緩存中的內容要做新鮮度檢測
過期日期:
由於網站是可變的,可能緩存定義的時間在未到達之前,數據就已經發生了改變,這在大部分電商站點上是經常發現的,這個時候我們就不得不對數據做新鮮度檢測,其方式分爲:
HTTP/1.0:Expires
例如:expires:Sat, 20 May 2017 07:49:55 GMT 在具體時間到達之前緩存服務器不會去後端服務器請求,但是會有一個問題,不同地區的時間可能不同
HTTP/1.1:Cache-Control:max-age
例如:Cache-Control: max-age=600 爲了解決HTTP/1.0中對於新鮮度控制的策略而生,通過相對時間來控制緩存使用期限
緩存有效性驗證機制:
如果原始內容未發生改變,則僅響應首部(不附帶body部分),響應碼304(Not Modified)
如果原始內容發生改變,則正常響應,響應碼200
如果原始內容消失,則響應404,此時緩存中的cache object應被刪除
條件式請求首部:
If-Modified-Since:基於請求內容的時間戳作驗正,如果後端服務器數據的時間戳未發生改變則繼續使用,反之亦然
If-None-Match:通過Etag來跟後端服務器進行匹配,如果數據的Etag未發生改變,既不匹配,則響應新數據,否則繼續使用當前數據
WebCache的緩存控制機制:
Cache-Control = "Cache-Control" ":" 1#cache-directive
cache-directive = cache-request-directive
| cache-response-directive
cache-request-directive = //請求報文中的緩存指令
"no-cache" //不要緩存的實體,要求現在從WEB服務器去取
| "no-store" (backup) //不要緩存,其中可能包括用戶的敏感信息
| "max-age" "=" delta-seconds //只接受 Age 值小於 max-age 值,並且沒有過期的對象
| "max-stale" [ "=" delta-seconds ] //可以接受過去的對象,但是過期時間必須小於 max-stale 值
| "min-fresh" "=" delta-seconds //接受其新鮮生命期大於其當前 Age 跟 min-fresh 值之和的緩存對象
| "only-if-cached" //只有當緩存中有副本時,客戶端纔會獲取一份副本
| cache-extension
cache-response-directive =
"public" //可以用 Cached 內容迴應任何用戶
| "private" [ "=" <"> 1#field-name <"> ] //只能用緩存內容迴應先前請求該內容的那個用戶
| "no-cache" [ "=" <"> 1#field-name <"> ] //可以緩存,但是只有在跟WEB服務器驗證了其有效後,才能返回給客戶端
| "no-store" //此內容不允許緩存到緩存服務器上,可能包含用戶的敏感信息
| "no-transform" //未改變
| "max-age" "=" delta-seconds //本響應包含的對象的過期時間
| "s-maxage" "=" delta-seconds //本響應包含的對象的過期時間
| cache-extension
常見WebCache軟件:
Name | Operating system | Forward mode | Reverse mode | License |
---|---|---|---|---|
Untangle | Linux | Yes | Yes | Proprietary |
ApplianSys CACHEbox | Linux | Yes | Yes | Proprietary |
aiScaler Dynamic Cache Control | Linux | Yes | Yes | Proprietary |
Nginx | Linux, BSD variants, OS X, Solaris, AIX, HP-UX, other *nix flavors | No | Yes | 2-clause BSD-like |
Varnish | Linux, Unix | No (possible with a VMOD) | Yes | BSD |
Traffic Server | Linux, Unix | Yes | Yes | Apache License 2.0 |
Squid | Linux, Unix, Windows | Yes | Yes | GNU General Public License |
Blue Coat ProxySG | SGOS | Yes | Yes | Proprietary |
WinGate | Windows | Yes | Yes | Proprietary / Free for 3 users |
Microsoft Forefront Threat Management Gateway | Windows | Yes | Yes | Proprietary |
Polipo | Windows, OS X, Linux, OpenWrt, FreeBSD | No | Yes | MIT License |
Apache HTTP Server | Windows, OS X, Linux, Unix, FreeBSD, Solaris, Novell NetWare, OS/2, TPF, OpenVMS and eComStation | No | Yes | Apache License 2.0 |
二、Varnish架構
(1)Managentment管理進程
CLI interface:命令行接口來,目前Web interface爲收費接口,而telnet純文本傳輸,所以只能使用ClI interface.
managentment主要用於編譯VCL並應用新配置、監控varnish、初始化varnish,並提供一個CLI。
(2)child/cache
child/cache線程有幾類:
Acceptor:接收新的連接請求;
Worker:用於處理並響應用戶請求;
Expiry:從緩存中清理過期cache object
(3)log
shared memory log,共享內容日誌方式存儲,一般其大小爲90MB,分爲兩部分:前一部分爲計數器、後一部分爲客戶請求相關的數據
Varnish支持的後端緩存存儲機制:
malloc[,size] 使用內存緩存機制
VARNISH_STORAGE="malloc,64M"
file[,path[,size[,granularity]]] 通過文件方式存儲
VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}"
persistent,path,size 前兩者在重啓後緩存都會消失,persistent可以永久保存緩存,但還爲開發階段
Varnish的 state engine:
vcl配置的緩存策略會在state engine中生效
上圖爲varnish中緩存控制的規則,每一個request進入vcl_recv後都會被髮往到各state engine上,不同的vcl規則會發往不同的state engine上,我們可以通過vcl規則來控制用戶請求,下面說一些常見的場景。
未命中緩存時:
直接與後端服務器建立管道:
三、Varnish安裝配置
環境介紹:
varnish_server | varnish3.0 | 172.18.4.70 | CentOS7 |
backend_server | httpd+php | 172.18.4.71 | CentOS7 |
zabbix官網的yum倉庫:https://repo.varnish-cache.org/
安裝:
#yum install varnish gcc -y
配置啓動環境
# vim /etc/sysconfig/varnish NFILES=131072 //可打開最大的文件數 MEMLOCK=82000 //鎖定的內存空間 RELOAD_VCL=1 //是否在重啓varnish服務時裝載vcl配置文件 VARNISH_VCL_CONF=/etc/varnish/default.vcl //vcl默認讀取配置文件路徑 VARNISH_LISTEN_PORT=80 //varnish 默認監聽端口 VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 //varnish管理監聽地址 VARNISH_ADMIN_LISTEN_PORT=6082 //varnish管理監聽端口 VARNISH_SECRET_FILE=/etc/varnish/secret //varnish 密鑰文件 VARNISH_MIN_THREADS=50 //最小線程數,varnish進程啓動時啓動多少個線程 VARNISH_MAX_THREADS=1000 //最大線程數,一般varnish的總線程數不超過5000(線程池數x最大線程數) VARNISH_THREAD_TIMEOUT=120 //線程超時時間 VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin //varnish緩存文件,varnish將緩存存儲爲單個文件 VARNISH_STORAGE_SIZE=64M //varnish存儲大小 VARNISH_STORAGE="malloc,${VARNISH_STORAGE_SIZE}" //varnish存儲訪方式,內存方式
啓動服務
# systemctl start varnish
四、vcl配置
(1)常見變量
1、在任何引擎中均可使用:
now, .host, .port
2、用於處理請求階段:
client.ip, server.hostname, server.ip, server.port
req.request:請求方法
req.url: 請求的URL
req.proto: HTTP協議版本
req.backend: 用於服務此次請求的後端主機;
req.backend.healthy: 後端主機健康狀態;
req.http.HEADER: 引用請求報文中指定的首部;
req.can_gzip:客戶端是否能夠接受gzip壓縮格式的響應內容;
req.restarts: 此請求被重啓的次數;
3、varnish向backend主機發起請求前可用的變量
bereq.request: 請求方法
bereq.url:請求url
bereq.proto:請求協議
bereq.http.HEADER:請求首部
bereq.connect_timeout: 等待與be建立連接的超時時長
4、backend主機的響應報文到達本主機(varnish)後,將其放置於cache中之前可用的變量
beresp.do_stream: 流式響應;
beresp.do_gzip:是否壓縮之後再存入緩存;
beresp.do_gunzip:是否解壓縮之後存入緩存
beresp.http.HEADER:報文首部;
beresp.proto: 協議
beresp.status:響應狀態碼
beresp.response:響應時的原因短語
beresp.ttl:響應對象剩餘的生存時長,單位爲second;
beresp.backend.name: 此響應報文來源backend名稱;
beresp.backend.ip:後端主機ip
beresp.backend.port:後端主機的端口
beresp.storage:
5、緩存對象存入cache之後可用的變量
obj.proto:協議
obj.status:狀態
obj.response:響應報文
obj.ttl:生存週期
obj.hits:命中
obj.http.HEADER:http首部
6、在決定對請求鍵做hash計算時可用的變量
req.hash:將請求交給hash
7、在爲客戶端準備響應報文時可用的變量
resp.proto:協議
resp.status:狀態
resp.response:響應
resp.http.HEADER:http首部
(3)各變量可用的狀態引擎
五、常用示例
爲了便於使用及理解,在介紹實例前,介紹一個varnish的命令行工具:varnishadm
在命令行下,直接敲varnishadm
# varnishadm 200 ----------------------------- Varnish Cache CLI 1.0 ----------------------------- Linux,2.6.32-573.el6.x86_64,x86_64,-sfile,-smalloc,-hcritbit varnish-3.0.6 revision 1899836 Type 'help' for command list. Type 'quit' to close CLI session. varnish>
會看到上面的一個界面,可以使用help命令來獲取幫助。
varnish> help 200 help [command] ping [timestamp] auth response quit banner status start stop vcl.load <configname> <filename> vcl.inline <configname> <quoted_VCLstring> vcl.use <configname> vcl.discard <configname> vcl.list vcl.show <configname> param.show [-l] [<param>] param.set <param> <value> panic.show panic.clear storage.list backend.list backend.set_health matcher state ban.url <regexp> ban <field> <operator> <arg> [&& <field> <oper> <arg>]... ban.list
常用的子命令有:
vcl.list:用於列出當前使用的配置及狀態
vcl.load:用於加載新配置
vcl.show:查看配置中的內容
ping:用來測試varnish是否正常
vcl.use:切換新配置
(1)實例一:添加http首部,讓客戶端可知緩存是否從服務器中得到
修改後端主機:
# vim /etc/varnish/defatult.vcl backend default { .host = "172.18.4.71"; .port = "80"; }
添加vcl語句:
# vim /etc/varnish/defatult.vcl sub vcl_deliver { if (obj.hits > 0) { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } } sub vcl_hit { return (deliver); }
通過命令行工具varnishadm重載配置
varnish> vcl.list 200 active 0 boot varnish> vcl.load test1 /etc/varnish/default.vcl 200 VCL compiled. varnish> vcl.use test1 200 varnish> vcl.list 200 available 0 boot active 0 test1
訪問並測試
(2)實例二,設置http首部,讓後端主機知道真實客戶端ip地址
修改配置文件
# vim /etc/varnish/default.vcl sub vcl_recv { 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; } }
重載配置
varnish> vcl.load test2 /etc/varnish/default.vcl 200 VCL compiled. varnish> vcl.use test2 200 varnish> vcl.list 200 available 0 boot active 0 test2
修改後端web服務器配置文件
# vim /etc/httpd/conf/httpd.conf LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined # systemctl reload httpd
訪問並查看httpd日誌
172.18.250.172 - - [23/May/2016:22:40:07 +0800] "GET / HTTP/1.1" 200 24 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
並不是varnish:172.18.4.70
(3)實例三:移除某個對象
修改配置文件
acl purgers { "127.0.0.1"; "172.18.0.0"/16; } sub vcl_recv { if (req.request == "PURGE") { if (!client.ip ~ purgers) { error 405 "Method not allowed"; } return (lookup); } } sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged"; } } sub vcl_miss { if (req.request == "PURGE") { purge; error 404 "Not in cache"; } } sub vcl_pass { if (req.request == "PURGE") { error 502 "PURGE on a passed object"; } }
重載配置
varnish> vcl.load test3 /etc/varnish/default.vcl 200 VCL compiled. varnish> vcl.use test3 200
訪問並測試
客戶端在發起HTTP請求時,只需要爲所請求的URL使用PURGE方法即可,其命令使用方式如下:
# curl -I -X PURGE http://varniship/path/to/someurl
啓用默認vcl_recv默認配置時使用的方式:
sub vcl_recv { 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.request == "PURGE" ) { if (!client.ip ~ purgers) { error 405 "Method not allowed."; } } if (req.request != "GET" && req.request != "HEAD" && req.request != "PUT" && req.request != "POST" && req.request != "TRACE" && req.request != "OPTIONS" && req.request != "DELETE" && req.request != "PURGE" ) { /* Non-RFC2616 or CONNECT which is weird. */ return (pipe); } if (req.request != "GET" && req.request != "HEAD" && req.request != "PURGE") { /* 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); }
(4)實例四:控制指定來源地址可訪問的資源
修改配置文件
# vim /etc/varnish/default.conf acl admingroup { "127.0.0.1"; "172.18.0.0"/16; } if (req.url ~ "login") { if (!client.ip ~ admingroup) { error 404 "no permission access"; } return (lookup); }
重載配置
爲了實現效果,這裏我修改配置文件,拒絕本機訪問
# vim /etc/varnish/default.conf acl admingroup { "127.0.0.1"; # "172.18.0.0"/16; //註釋此行 }
重載配置並測試
varnish> vcl.load test5 /etc/varnish/default.vcl 200 VCL compiled. varnish> vcl.use test5 200
(5)實例五:varnish多主機配置
添加配置
backend web1 { .host = "172.18.4.71"; .port = "80"; } director webservers random { .retries = 5; { .backend = web1; .weight = 2; } { .backend = { .host = "172.18.4.72"; .port = "80"; } .weight = 3; } } sub vcl_recv { set req.backend = webservers; return (lookup); }
多主機配置中,常用算法有兩種:random和round-robin
重載配置
varnish> vcl.load test6 /etc/varnish/default.vcl 200 VCL compiled. varnish> vcl.use test6 200
訪問測試
varnish> backend.list 200 Backend name Refs Admin Probe default(172.18.4.71,,80) 4 probe Healthy (no probe) web1(172.18.4.71,,80) 1 probe Healthy (no probe) webservers[1](172.18.4.72,,80) 1 probe Healthy (no probe)
由於本地是緩存服務器,所以測試效果不是很明顯,所以此次並沒有測試結果
(6)實例六:varnish健康狀態檢查
修改配置文件
backend server1 { .host = "server1.example.com"; .probe = { .url = "/"; .interval = 5s; .timeout = 1 s; .window = 5; .threshold = 3; } } backend server2 { .host = "server2.example.com"; .probe = { .url = "/"; .interval = 5s; .timeout = 1 s; .window = 5; .threshold = 3; } } sub vcl_recv { if (req.url ~ "\.php$") { set req.backend = web1; } else { set req.backend = web2; } return (pass); }
重載配置
varnish> vcl.load test8 default.vcl 200 VCL compiled. varnish> vcl.use test8 200
查看後端狀態
varnish> backend.list 200 Backend name Refs Admin Probe web1(172.18.4.71,,80) 1 probe Healthy 8/8 web2(172.18.4.72,,80) 1 probe Healthy 8/8
關閉其中一臺,並查看
# systemctl stop httpd varnish> backend.list 200 Backend name Refs Admin Probe web1(172.18.4.71,,80) 1 probe Sick 1/8 web2(172.18.4.72,,80) 1 probe Healthy 8/8 varnish> backend.list 200 Backend name Refs Admin Probe web1(172.18.4.71,,80) 1 probe Sick 0/8 web2(172.18.4.72,,80) 1 probe Healthy 8/8
(6)生產案例
backend shopweb { .host = "172.18.4.1"; .port = "80"; } acl purge { "localhost"; "127.0.0.1"; "10.1.0.0"/16; "192.168.0.0"/16; } sub vcl_hash { hash_data(req.url); return (hash); } sub vcl_recv { set req.backend = shopweb; # set req.grace = 4h; if (req.request == "PURGE") { if (!client.ip ~ purge) { error 405 "Not allowed."; } return(lookup); } if (req.request == "REPURGE") { if (!client.ip ~ purge) { error 405 "Not allowed."; } ban("req.http.host == " + req.http.host + " && req.url ~ " + req.url); error 200 "Ban OK"; } 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.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) { /* Not cacheable by default */ return (pass); } if ( req.url == "/Heartbeat.html" ) { return (pipe); } if ( req.url == "/" ) { return (pipe); } if ( req.url == "/index.jsp" ) { return (pipe); } if (req.http.Cookie ~ "dper=") { return (pass); } if (req.http.Cookie ~ "sqltrace=") { return (pass); } if (req.http.Cookie ~ "errortrace=") { return (pass); } # if ( req.request == "GET" && req.url ~ "req.url ~ "^/shop/[0-9]+$" ) { if ( req.url ~ "^/shop/[0-9]+$" || req.url ~ "^/shop/[0-9]?.*" ) { return (lookup); } if ( req.url ~ "^/shop/(\d{1,})/editmember" || req.url ~ "^/shop/(\d{1,})/map" || req.url ~ "^/shop/(\d+)/dish-([^/]+)" ) { return (lookup); } return (pass); # return (lookup); } sub vcl_pipe { return (pipe); } sub vcl_pass { return (pass); } sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged."; } return (deliver); } sub vcl_miss { if (req.request == "PURGE") { error 404 "Not in cache."; } # if (object needs ESI processing) { # unset bereq.http.accept-encoding; # } return (fetch); } sub vcl_fetch { set beresp.ttl = 3600s; set beresp.http.expires = beresp.ttl; #set beresp.grace = 4h; # if (object needs ESI processing) { # set beresp.do_esi = true; # set beresp.do_gzip = true; # } if ( req.url ~ "^/shop/[0-9]+$" || req.url ~ "^/shop/[0-9]?.*" ) { set beresp.ttl = 4h; } if ( req.url ~ "^/shop/(\d{1,})/editmember" || req.url ~ "^/shop/(\d{1,})/map" || req.url ~ "^/shop/(\d+)/dish-([^/]+)" ) { set beresp.ttl = 24h; } if (beresp.status != 200){ return (hit_for_pass); } return (deliver); } sub vcl_deliver { if (obj.hits > 0){ set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } set resp.http.X-Powered-By = "Cache on " + server.ip; set resp.http.X-Age = resp.http.Age; return (deliver); } sub vcl_error { set obj.http.Content-Type = "text/html; charset=utf-8"; set obj.http.Retry-After = "5"; synthetic {""} + obj.status + " " + obj.response + {""}; return (deliver); } sub vcl_init { return (ok); } sub vcl_fini { return (ok); }
對於varnish就寫到這裏了,感覺寫的不錯可以給點個贊。
作者:Ace
QQ1257465991
Linux運維攻城獅一隻
Q/A:如有問題請慷慨提出