緩存的基礎(chǔ)知識
1、程序本身具有局部性
-
時(shí)間局部性
- 過去訪問到的數(shù)據(jù),也有可能被兩次訪問
-
空間局部性
- 一個(gè)數(shù)據(jù)被訪問到時(shí),離它最近的文件可能馬上也會被訪問
2、命中率
-
文檔命中率
- 從文檔個(gè)數(shù)進(jìn)行衡量
-
字節(jié)命中率
- 從內(nèi)容大小進(jìn)行衡量
3、緩存系統(tǒng)的特性
-
緩存對象
- 有生命周期,且是定期清理的
-
緩存空間耗盡
- 使用LRU(最近最少使用算法)或者M(jìn)RU算法進(jìn)行緩存項(xiàng)清理
-
不可緩存項(xiàng)
- 用戶私有數(shù)據(jù)
4、緩存系統(tǒng)一般處理步驟
-
接收請求
-
解析請求
- 提取請求的URL及各種首部
-
查詢緩存
-
新鮮度檢測
-
創(chuàng)建響應(yīng)報(bào)文
-
發(fā)送響應(yīng)報(bào)文
-
記錄日志
5、新鮮度檢測機(jī)制
-
過期日期
-
HTTP/1.0: expires(其是一個(gè)絕對時(shí)間) -
HTTP/1.1: Cache-Control: max-age=600(其是一個(gè)相對時(shí)間)
-
-
產(chǎn)效性再驗(yàn)證(revalidate)
- 如果原始內(nèi)容未改變,則僅響應(yīng)首部信息,響應(yīng)碼為304(not modified)
- 如果原始內(nèi)容發(fā)生了改,則正常響應(yīng),響應(yīng)碼為200
- 3)如果原始內(nèi)容消失,則響應(yīng)404,此時(shí)緩存中的cache object也應(yīng)該被刪除
-
條件式請求方式
-
If-Modified-Since: 基于請求內(nèi)容的時(shí)間戳作驗(yàn)證 If-unmodified-SinceIf-Match-
If-None-Match: 結(jié)合Etag對文件做MD5校驗(yàn)
-
6、HTTP緩存相關(guān)首部
age: 一個(gè)緩存對象從產(chǎn)生到此刻為止,經(jīng)過的多少時(shí)間-
Cache-Control請求首部-
no-cache: 不要從緩存中返回內(nèi)容 -
max-age: 相對過期時(shí)間,是以秒為單位 -
max-stale: 可以接受的對象,但是過期時(shí)間必須小于max-stale值 -
min-fresh: 接受其新鮮生命期大于其當(dāng)前age跟min-fresh值之各的緩存對象
-
-
Cache-Control響應(yīng)首部-
no-cache: 可以緩存,但要跟web服務(wù)器再驗(yàn)證 -
no-store: 不允許緩存 -
public: 可以用cache內(nèi)容回應(yīng)任何用戶 -
private: 只能用緩存內(nèi)容回應(yīng)先前請求該內(nèi)容的那個(gè)用戶 -
max-age: 本響應(yīng)包含的對象的過期時(shí)間 -
s-maxage: 公共緩存的最大生命周期 -
must-revlidate: 每次響應(yīng)給客戶端時(shí),必須做有效性驗(yàn)證
-
緩存時(shí)需要考慮到的特殊首部
Authorization:跟授權(quán)相關(guān)的首部
cookie:用戶識別相關(guān)的首部
Vary:accept-encoding:所能接受的字符編碼格式
以上三種首部未特性情況下是不予緩存
通常與緩存相關(guān)的方法:
1、GET
2、HEAD
7、常見的緩存服務(wù)開源解決方案
-
varnish: 專用于web服務(wù)的緩存 -
squid: 類似nginx,apache,但比varnish穩(wěn)定
Varnish
varnish對比squid的優(yōu)點(diǎn)
- 1、varnish的穩(wěn)定性很高,兩者在完成相同負(fù)荷的工作時(shí),squid服務(wù)器發(fā)生故障的幾率要高于varnish,因?yàn)閟quid要經(jīng)常重啟
- 2、varnish訪問速度更快,其采用了"Visual Page Cache"技術(shù),所有緩存數(shù)據(jù)都直接從內(nèi)存中讀取,而squid是從硬盤讀取,因而varnish在訪問速度方面會更快
- 3、varnish可以支持更多的并發(fā)連接,因?yàn)関arnish的TCP連接釋放要比squid快,因而在高并發(fā)連接情況下可以支持更多TCP連接
- 4、varnish可以通過管理端口,使用正則表達(dá)式批量的清除部分緩存,而squid是做不到的。
- 5、squid屬于單進(jìn)程使用單核CPU,但Varnish是通過fork形式打開多進(jìn)程來做處理,所以是合理的使用所有核來處理相應(yīng)的請求
varnish對比squid的缺點(diǎn)
- 1、varnish進(jìn)程一旦Hang、Crash或者重啟,緩存數(shù)據(jù)都會從內(nèi)存中完全釋放,此時(shí)所有請求都會發(fā)送到后端服務(wù)器,在高并發(fā)情況下,會給后端服務(wù)器造成很大的壓力
- 2、在varnish使用中,如果單個(gè)vrl的請求通過HA/F5,每次請求不同的varnish服務(wù)器時(shí),被請求的varnish服務(wù)器都會被穿透到后端,而同樣的請求會在多臺服務(wù)器上緩存 ,也會造成varnish的緩存資源浪費(fèi),也會造成性能下降
varnish的工作進(jìn)程特性
-
varnish工作進(jìn)程示意圖

*
Management(主進(jìn)程)* 實(shí)現(xiàn)應(yīng)用新的配置
* 編譯VCL
* 監(jiān)控Varnish的子進(jìn)程(其management每隔幾秒進(jìn)行子進(jìn)程探測,如較長時(shí)間沒有回應(yīng)探測它將重啟一個(gè)子進(jìn)程)
* 初始化varnish
* 提供命令行接口
*
Child/Cache* accept : 接收新的連接請求,交由worker線程處理
* worker : 用于處理并響應(yīng)用戶請求
* expiry : 管理過期緩存,從緩存中清理過期的Cache
*
Vcl compiler* 把配置文件編譯成VCL格式
*
C compiler* C編譯器,vcl compiler調(diào)用c compiler
*
日志
shared memory log,共享內(nèi)存日志大小默認(rèn)一般為90M+,分為兩部分組成,前一部分為計(jì)數(shù)器,后一部分請求響應(yīng)的相關(guān)數(shù)據(jù),日志保存在一個(gè)共享的內(nèi)存空間,只能保存最近最新的日志,需要使用工具,把日志不斷的導(dǎo)出,以實(shí)現(xiàn)長期保存
* `varnishlog` : 其以守護(hù)進(jìn)程方式運(yùn)行,需要將其重啟才會把日志導(dǎo)入到本地磁盤,類似于httpd日志的comm格式
* `varnishncsa` : 其與varnishlog類似,但日志的格式與httpd的combind格式類似
-
varnish的進(jìn)程工作特性
- varnish啟動或有2個(gè)進(jìn)程master(management)進(jìn)程和child(worker)進(jìn)程,master讀入存儲配置命令,進(jìn)行初始化,然后fork并監(jiān)控child,child則分配線程進(jìn)行cache工作,child還會做管理線程生成很多的worker線程
- child線程主線程初始化過程中,將存儲大文件整個(gè)加載到內(nèi)存中,如果該文件超出系統(tǒng)的虛擬內(nèi)存,則會減少原來配置MMAP大小,然后繼續(xù)加載,這時(shí)候創(chuàng)建并初始化空間存儲結(jié)構(gòu)體,放在存儲管理的struct中,等待分配
- 接著varnish某個(gè)負(fù)責(zé)接受http連接的線程開始等待用戶請求,如果有新http連接,但這個(gè)線程只負(fù)責(zé)接收,然后喚醒等待線程池中的work線程,進(jìn)行請求處理
- work線程讀入uri后,將會查找已有的object,命中直接返回,沒有命中則會從后端服務(wù)器中取出來,放到緩存中,如果緩存已滿,會根據(jù)LRU算法釋放舊的Object,對于釋放緩存,有一個(gè)超時(shí)線程檢測緩存中所有object的生命周期,如果緩存過期(ttl),則刪除,釋放相應(yīng)的存儲內(nèi)存
varnish使用單進(jìn)程多線程模型,其worker stats類似于一個(gè)線程池,所有的資源將整合在一個(gè)工作區(qū)中,以降低線程在申請或修改內(nèi)存時(shí),出現(xiàn)的競爭的可能性,當(dāng)多個(gè)線程同時(shí)訪問同個(gè)資源時(shí),工作區(qū)對資源以施加鎖保證用戶的請求在資源爭用時(shí),后來的線程處于等待狀態(tài),以協(xié)調(diào)線程的工作。
-
varnish存儲緩存機(jī)制
-
malloc基于內(nèi)存存儲,在內(nèi)存中存儲各緩存對象,時(shí)間久了會產(chǎn)生緩存碎片,如果分配的內(nèi)存太大,會降低效率。varnish可能會激活大內(nèi)存空間分配機(jī)制(在Centos6以后),這樣也會降低緩存的查詢效率
-
file所有緩存對象緩存在單個(gè)文件中,重啟后將會失效,不支持持久機(jī)制,建議使用SSD存放緩存數(shù)據(jù),一般用于大文件緩存,如圖片等
-
persistent基于文件的持久存儲,varnish重啟了,緩存還有效,其目前為實(shí)驗(yàn)性項(xiàng)目,生產(chǎn)環(huán)境中不能使用
-
-
varnish內(nèi)存分配回收機(jī)制
-
分配- malloc()函數(shù)
- jemalloc()函數(shù) : 其是malloc的并發(fā)實(shí)現(xiàn)
-
回收- free()函數(shù)
-
-
VCL編程語法
VCL:varnish configuration language,又被稱之為DSL(域)編譯語言,其是參照C和perl語言的風(fēng)格編寫,基本格式如下:
-
sub NAME {
....;
-
}
* 不支持循環(huán)
* 受狀態(tài)引擎的變量,變量的可調(diào)用位置與state engine有密切相關(guān)性
* 支持終止語句,使用ruturn()返回一個(gè)action,其沒有返回值
* 可自定義變量
* //,#,/* */: 用于注釋,會被編譯器忽略
* “域”專用,只能一個(gè)域有效
* 操作符: `=,==,~,!,&&,||`
* 條件判斷語句的寫法:
```
單分支:
if (condition) {
....;
} else {
....;
}
多分支:
if (condition) {
...;
} elseif {
...;
} else {
...;
}
```
* 變量賦值:`set name = value`
* 撤消變量的值:`unset name`
安裝及配置Varnish
Centos 6
-
安裝
-
yum install varnish: 默認(rèn)安裝為varnish2的版本
-
-
主進(jìn)程配置文件
/etc/sysconfig/varnish-
/etc/varnish/default.vcl: VCL引擎配置文件
-
服務(wù)管理腳本
-
/etc/rc.d/init.d/varnish: varnish服務(wù)主進(jìn)程管理 -
/etc/rc.d/init.d/varnishlog: varnishlog服務(wù)管理 -
/etc/rc.d/init.d/varnishncsa: varnishncsa服務(wù)管理
-
Centos 7
-
安裝
-
yum install varnish: 默認(rèn)安裝為varnish 4的版本
-
-
主進(jìn)程配置文件
-
/etc/varnish/varnish.params: varnish服務(wù)主進(jìn)程管理 -
/etc/varnish/default.vcl: VCL引擎配置文件
-
-
服務(wù)管理腳本
/usr/lib/systemd/system/varnish.service/usr/lib/systemd/system/varnishlog.service/usr/lib/systemd/system/varnishncsa.service
varnish主配置文件參數(shù)說明
-
RELOAD_VCL=1: 是否啟動加載VCL配置文件 -
VARNISH_VCL_CONF=/etc/varnish/default.vcl: varnish的VCL配置文件路徑 -
VARNISH_LISTEN_PORT=6081: 默認(rèn)監(jiān)聽端口 -
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1: 遠(yuǎn)程管理監(jiān)聽地址 -
VARNISH_ADMIN_LISTEN_PORT=6082: 遠(yuǎn)程管理監(jiān)聽端口 -
VARNISH_SECRET_FILE=/etc/varnish/secret: varnish默認(rèn)加載的密鑰文件,其為遠(yuǎn)程連接varnish的共享密鑰文件 -
VARNISH_MIN_THREADS=50: varnish最少啟動worker線程 -
VARNISH_MAX_THREADS=1000: varnish最大啟動worker線程數(shù)(據(jù)說超出5000就不穩(wěn)定了) -
VARNISH_THREAD_TIMEOUT=120: 空閑線程的超時(shí)時(shí)間,超時(shí)后被父進(jìn)程銷毀 -
VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin: varnish的緩存存儲位置文件,其是二進(jìn)制格式 -
VARNISH_STORAGE_SIZE=1G: 緩存大小 -
VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}”: 基于文件存儲緩存的設(shè)置方式 -
VARNISH_STORAGE_SHM=64M: 基于內(nèi)存的存儲緩存,指定緩存的大小,不可與文件的方式共存 -
VARNISH_STORAGE=“malloc,${VARNISH_STORAGE_SHM}”: varnish基于內(nèi)存存儲緩存的設(shè)置方式
注意:varnish配置文件修改不應(yīng)重啟服務(wù),而是手動加載配置文件
Varnish管理工具
varnishd命令
其可以在/etc/sysconfig/varnish的配置文件中完成
-
Options
-
-f: vcl的配置文件 -
-a address[:port]:服務(wù)監(jiān)聽的IP地址及端口 -
-s[name=]type[,options]: varnish緩存的存儲機(jī)制- options
- malloc[,size]
- file[,path[,size[,granularity]]] (力度)
- persistent,path,size
- options
-
-T address[:port]: 指定管理接口,默認(rèn)6082 -
-p param=value: 指定參數(shù) -
-r param: 指定只讀參數(shù)
-
2、varnishadm命令
其是通過連接varnish服務(wù)端,可以實(shí)現(xiàn)varnish服務(wù)的管理操作,實(shí)現(xiàn)動態(tài)加載VCL的配置文件
-
Options:
-
-S scret_file: 密鑰文件,跟服務(wù)啟動的一樣,在varnish目錄下存放 -
-T IP:PORT: 連接服務(wù)端的端口,默認(rèn)為6082
-
-
varnish命令行命令
-
vcl.list: 查看vcl配置文件的列表 -
vcl.load NAME default.vcl: 加載vcl當(dāng)前的配置文件為NAME -
vcl.use NAME: 使用加載的配置文件 -
vcl.show NAME: 查看加載的配置文件詳細(xì)配置信息 -
vcl.discard: 刪除加載的配置文件 -
backend.list: 查看后端服務(wù)器列表及狀態(tài) -
param.show -l: 查看varnish主進(jìn)程的運(yùn)行選項(xiàng)參數(shù) -
param.set <param> <value>: 在運(yùn)行時(shí)設(shè)定主進(jìn)程的運(yùn)行參數(shù) -
ping: 判斷后端服務(wù)器的狀態(tài) -
status: 查看進(jìn)行的運(yùn)行狀態(tài) -
panic.show: 如果某個(gè)子進(jìn)程曾經(jīng)panic過,可以使用這個(gè)命令查看(panic本為恐慌,代表服務(wù)曾經(jīng)崩潰過) -
storage.list: 查看緩存存儲機(jī)制 -
ban <field> <operator> <arg> [&& <field> <oper> <arg> ]….: 手動清除緩存 -
ban.list: 列出清除緩存的規(guī)則列表,由上一個(gè)參數(shù)定義
-
3、Varnishtop命令
內(nèi)存日志區(qū)域查看工具
-
Options
-
-I REGEXP: 僅顯示被模式匹配到的條目,過濾右邊的信息條目 -
-X: 僅顯示不被模式匹配到的條目,過濾右邊的信息條目 -
-i: 過濾左邊字段 -
-x: 指定的不顯示,不指定的才顯示 -
-C: 忽略字符大小寫 -
-d: 顯示已有的日志
varnishtop -i RxHeader varnishtop -I ^User-Agent varnishtop -I ^User-Agent -1 : 只顯示一屏即退出 -
4、varnishstat命令
varnish的運(yùn)行統(tǒng)計(jì)數(shù)據(jù)
-
Options
-
-f filed,filed,....: 指明只顯示哪些參數(shù) -
-l: 列出所有字段 -
-x: 以XML格式輸出 -
-j: 以json格式輸出
-
5、varnishlog命令
啟動以comm格式記錄日志到磁盤
6、varnishncsa命令
啟動以combind格式記錄日志到磁盤
VCL Engine
vcl engine是varnish通過VCL配置語言定義的緩存策略,state engine之間有相關(guān)性,上級engine通過return指明下級engine,常用的引擎(varnish version 3),如下:
-
vcl_recv- 由return(lookup)定義到vcl_hash引擎
-
vcl_hash- 其是對緩存項(xiàng)進(jìn)行hash計(jì)算
-
vcl_fetch- 其是向后端服務(wù)器取文件的
-
vcl_hit- 其下一個(gè)工作引擎可能是deliver,也有可能是pass(為pass的情況下:命中后需要把緩存強(qiáng)制刪除)
vcl_missvcl_deliver-
vcl_pipe- 無法識別的方法,交由pipe管道引擎處理
-
vcl_pass- 可以理解的方法,但不緩存,就經(jīng)由pass
vcl_error
Vcl engine 完整的工作流程示意圖(1)

Vcl engine 完整的工作流程示意圖(2)

Vcl engine常見工作流程
1、查詢緩存未命中的工作流

2、查詢緩存命中的工作流

3、未識別的HTTP方法工作流

4、不予緩存的工作流

5、完整的工作流

-
restart- 當(dāng)命中后,把url進(jìn)行了重寫操作,就需要從vcl_recv重新開始,即為重啟,這類操作被稱之為restart,但varnish有一個(gè)內(nèi)申機(jī)制,即重啟了10次,仍然還在重啟,此次請求將會被丟棄
-
error- 類似于404類的請求錯(cuò)誤
6、狀態(tài)引擎說明
- 1、首先由vcl_init裝載vcl引擎
- 2、再由vcl_recv將請求接入,分析是否服務(wù)于此請求,并指定如何服務(wù),可能交由下一個(gè)(pipe,pass,hash)處理
- 3、vcl engine可用的return函數(shù)
-
return(lookup): 送給vcl_hash引擎處理 -
return(pass): 交給vcl_pass引擎處理 -
return(pipe): 交由vcl_pipe引擎處理 -
error CODE: 返回錯(cuò)誤和相應(yīng)的CODE給客戶端 -
return(deliver): 交由vcl_deliver直接投遞 return(hit_for_pass)-
return(restart): 重啟請求
-
VCl引擎中常用變量
-
1、在任何引擎中均可使用
-
now: 獲取當(dāng)前系統(tǒng)當(dāng)前時(shí)間 -
.host: 后端主機(jī)或主機(jī)名 -
.port: 后端主機(jī)的端口或服務(wù)名
-
-
2、用于處理一個(gè)請求階段
可用于recv,hash,pipe,pass引擎中。
* `client.ip` : 客戶端IP地址
* `server.hostname` : 服務(wù)器的主機(jī)名(緩存服務(wù)器)
* `server.ip` : varnish服務(wù)器的IP
* `server.port` : varnish服務(wù)器的端口
* `req.request` : 客戶端的請求方法
* `req.url` : 客戶端請求的URL
* `req.proto` : http協(xié)議版本
* `req.backend` : 用于服務(wù)此次請求的后端主機(jī)
* `req.backend.healthy` : 后端主機(jī)的健康狀態(tài)
* `req.http.HEADER` : 引用請求報(bào)文中指定的首部,哪req.http.host
* `req.hash_always_miss`
* `req.hash_ignore_busy`
* `req.can_gzip` : 客戶端是否能夠接受GZIP壓縮格式的響應(yīng)內(nèi)容
* `req.restarts` : 此請求被重啟的次數(shù)
-
3、vanish向backend主機(jī)發(fā)起請求前可用的變量
-
bereq.request: 請求方法 bereq.urlbereq.protobereq.http.HEADER-
bereq.connect_timeout: 等待與后端建立連接的超時(shí)時(shí)長
-
-
4、當(dāng)后端服務(wù)器響應(yīng)varnish,但未放置緩存之前
-
beresp.do_stream: 表示流式響應(yīng)
流式響應(yīng):當(dāng)后端backend主機(jī)響應(yīng)一個(gè)10M大小的文件,10M的文件是由多個(gè)數(shù)據(jù)報(bào)文組成,當(dāng)varnish接收到一個(gè)報(bào)文時(shí),就直接將報(bào)文發(fā)給客戶端,也不是等待數(shù)據(jù)報(bào)文接收完整后再發(fā)出,這就被稱之為流式響應(yīng)-
beresp.do_gzip: 從后端服務(wù)器收到的響應(yīng)報(bào)文,要不要壓縮以后存儲下來 -
beresp.do_gunzip: 從后端服務(wù)器收到的響應(yīng)報(bào)文,如果壓縮了,要不要解壓縮后再存下來 beresp.http.HEADERberesp.proto-
beresp.status: 響應(yīng)狀態(tài)碼 -
beresp.response: 響應(yīng)時(shí)的原因短語 -
beresp.ttl: 響應(yīng)對象的剩余生存時(shí)長,單位為second -
beresp.backend.name: 此響應(yīng)報(bào)文來源的backend主機(jī)名稱 beresp.backend.ipberesp.backend.port-
beresp.storage: 緩存后端
-
-
5、緩存對象存入cache之后可用的變量,大多數(shù)為只讀
-
obj.response: 服務(wù)端所返回的原因短語 -
obj.proto: 響應(yīng)時(shí)使用的協(xié)議 obj.status-
obj.ttl: 指明當(dāng)前的對象緩存的還有多少時(shí)長 -
obj.hits: 這個(gè)緩存對象已經(jīng)命中多少次(大約值) -
obj.http.HEADER: 后端服務(wù)器的響應(yīng)首部
-
-
6、決定對請求的健做hash計(jì)算時(shí)可用的變量
-
req.hash: 把什么內(nèi)容做hash鍵,做查詢的健
-
-
7、在為客戶端準(zhǔn)備響應(yīng)報(bào)文時(shí)可用的變量
-
resp.proto: 指定使用什么協(xié)議來響應(yīng) resp.statusresp.responseresp.http.HEADER
-
vcl定義后端服務(wù)器主機(jī)
定義的后端主機(jī)需要在recv中調(diào)用,必須會出錯(cuò)
-
1、定義格式:
backend NAME { .host= #后端backend server IP地址 .port= #后端backend server 端口 } -
2、后端服務(wù)器需要vcl_recv中調(diào)用,示例如下
示例1: sub vcl_recv { ... if(req.url ~ "test.html") { set req.backend = NAME; } else { set req.backend = NAME1; } 示例2: sub vcl_recv { ... set req.backend = NAME; }
定義后端服務(wù)器集群
varnish中可以使用director指令將一個(gè)或多個(gè)近似的后端主機(jī)定義成一個(gè)邏輯組,并可以指定其調(diào)度方法(也叫挑選方法)來輪流將請求發(fā)送至后端backend主機(jī)上,不同的director可以使用同一個(gè)后端主機(jī),而某director也可以使用“匿名”后端主機(jī)(在director中直接定義),每個(gè)director都必須有其專用名,且在定義后必須在vcl中進(jìn)行調(diào)用,VCL中任何可以指定后端主機(jī)的位置均可按需將其替換為調(diào)用某已定義的director
-
1、定義方法
backend web1 { .host = "www.zhenping.me"; .port = "80"; } director webservers random { .retries = 5; { .backend = web1; .weight = 2; } { .backend = { .host = "www2.zhenping.me"; .port = "80"; } .weight = 3; } } 以上為兩種定義方法示例 -
2、調(diào)用方法
示例: sub vcl_recv { .... set req.backend = webservers; } -
3、backend調(diào)度方法
-
round-robin- 其是對資源對象的輪詢調(diào)度方法(當(dāng)訪問index1.html到后端server1,當(dāng)訪問index2.html到后端server2),其沒有參數(shù)
-
random- 其是對后端backend server的隨機(jī)調(diào)度方法,也是建議使用的方法,其支持以下參數(shù)
-
.weight = #: 權(quán)重 -
.retires = #: 來設(shè)定查找一個(gè)健康后端主機(jī)時(shí)的嘗試次數(shù)
-
varnish2.1.0之后,random挑選方法又多了兩種變化形式client和hash,client類型的director使用client.identity作為挑選因子,這意味著client.identity相同的請求都將被發(fā)送至同一后端主機(jī),clinet.itdentity默認(rèn)為client.ip,但也可以在VCL中將其修改為所需要的標(biāo)識符,類似的,hash類型的director使用hash數(shù)據(jù)作為挑選因子,這意味著對同一個(gè)URL的請求將被發(fā)往同一個(gè)后端主機(jī),其常用于多級緩存的場景中,然后,無論是client還是hash,當(dāng)其傾向于使用后端主機(jī)不可用時(shí)將會重新挑選新的后端主機(jī) - 其是對后端backend server的隨機(jī)調(diào)度方法,也是建議使用的方法,其支持以下參數(shù)
-
fallback- 用于定義備用服務(wù)器,其更多的是冗余的作用,以下示例
director b3 fallback { { .backend = web1; } { .backend = web2; } { .backend = web3; } } 注意: 只有web1不可用才會使用到web2,web2和web1同時(shí)不可用時(shí),才會使用到web3,如果web2不可用,但web1可用,它也會使用web1
-
varnish檢測后端主機(jī)的健康狀態(tài)
varnish可以檢測后端主機(jī)的健康狀態(tài),在判定后端主機(jī)失效時(shí)能自動將其從可用后端主機(jī)列表中移除,而一旦其重新變得可用還可以自動將其設(shè)定為可用,為了避免誤判,varnish在探測后端主機(jī)的健康狀態(tài)發(fā)生轉(zhuǎn)變時(shí)(比如某次檢測時(shí)某后端主機(jī)突然成為不可用狀態(tài)),通常需要連續(xù)執(zhí)行幾次探測均為新狀態(tài)才將其標(biāo)記為轉(zhuǎn)換后的狀態(tài)
每個(gè)后端服務(wù)器當(dāng)前探測的健康狀態(tài)探測方法通過.probe進(jìn)行設(shè)定,其結(jié)果可由req.backend.healthy變量獲取,也可通過varnishlog中的backend_health查看或varnishadm的debug.health查看
-
.probe中探測常用指令-
url: 探測后端主機(jī)健康狀態(tài)時(shí)請求的URL,默認(rèn)為/ -
.request: 探測后端主機(jī)健康狀態(tài)時(shí)所請求內(nèi)容的詳細(xì)格式,定義后,它會替換.rul指定的探測方法,如下:
.request = "GET /.healthtest.html HTTP/1.1" "Host:www.zhenping.me" "Connection: close" #探測時(shí)關(guān)閉長連接-
.window: 設(shè)定在判定后端主機(jī)健康狀態(tài)時(shí)基于最近多少次的探測進(jìn)行,默認(rèn)是8次 -
.threshold: 在window中指定的次數(shù)中,至少有多少次是成功的才判定后端主機(jī)是正常健康運(yùn)行,默認(rèn)是3次 -
.initial: varnish啟動時(shí)對后端主機(jī)至少需要多少次的成功探測,轉(zhuǎn)儲同.threshold -
.expected_response: 期望后端主機(jī)的響應(yīng)狀態(tài)碼,默認(rèn)是200 -
interval: 探測請求的發(fā)送周期,默認(rèn)是5秒 -
.timeout: 每次探測請求的過期時(shí)長,默認(rèn)為2秒
-
```
示例1:
backend web1 {
.host = "www.zhenping.me";
.probe = {
.url = "/.healthtest.html";
.interval = 1s;
.window = 5;
.threshold = 2;
}
}
示例2:
可以將probe的機(jī)制定義為一個(gè)代碼塊,在backend中引用
probe PRO_NAME {
....;
}
backend NAME {
...;
.probe = PRO_NAME;
}
```
移除單個(gè)緩存對象
purge用于清理緩存中的某特定對象及其變種(variants),因此,在有著明確要修剪的緩存對象時(shí)可以使用此種方式。HTTP協(xié)議的PURGE方法可以實(shí)現(xiàn)purge功能,不過,其僅能用于vcl_hit和vcl_miss中,它會釋放內(nèi)存工作并移除指定緩存對象的所有Vary:-變種,并等待下一個(gè)針對此內(nèi)容的客戶端請求到達(dá)時(shí)刷新此內(nèi)容。另外,其一般要與return(restart)一起使用。下面是個(gè)在VCL中配置的示例
acl purgers {
"127.0.0.1";
"192.168.0.0"/24;
}
sub vcl_recv {
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") { #需要添加PURGE方法,以不被送到PIPE引擎處理
/* Non-RFC2616 or CONNECT which is weird. */
return (pipe);
}
if (req.request != "GET" && req.request != "HEAD" && req.request != "PURGE") { #也需要添加PURGE方法不被送到PASS引擎,以確保PURGE方法可以到達(dá)HASH引擎
/* We only deal with GET and HEAD by default */
return (pass);
}
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";
}
}
客戶端在發(fā)起HTTP請求時(shí),只需要為所請求的URL使用PURGE方法即可,其命令使用方式如下:
# curl -I -X PURGE http://varniship/path/to/someurl
使用示例1
#drop any cookies sent to wordpress
sub vcl_recv {
if(!(req.url ~ “wp-(login|admin)”)) {
unset req.http.cookie;
}
}
使用示例2
sub vcl_recv {
if (req.http.host ~ “(?i)^(www.)?zhenping.me$”) {
set req.http.host = “www.zhenping.me”;
set req.backend = www;
} elseif (req.http.host ~ “(?i)^images.zhenping.me$”) {
set req.backend = images;
} else {
error 404 “Unknown virtual host”;
}
}
使用示例3
sub vcl_recv {
if (req.http.User-Agent ~ "iPad" || req.http.User-Agent ~ "iPhone" || req.http.User-Agent ~ "Android") {
set req.http.X-Device = "mobile";
} else {
set req.http.X-Device = "Desktop";
}
}
使用示例4(測試是否命中緩存)
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
return (deliver);
}
使用示例5(隱藏后端服務(wù)軟件版本)
sub vcl_deliver {
if (resp.http.Server) {
unset resp.http.Server; #出于安全考慮,需要將后端所使用的軟件名稱和版本隱藏起來
}
return (deliver);
}
生產(chǎn)環(huán)境實(shí)例
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);
}