緩存的基礎知識
1、程序本身具有局部性
-
時間局部性
- 過去訪問到的數據,也有可能被兩次訪問
-
空間局部性
- 一個數據被訪問到時,離它最近的文件可能馬上也會被訪問
2、命中率
-
文檔命中率
- 從文檔個數進行衡量
-
字節命中率
- 從內容大小進行衡量
3、緩存系統的特性
-
緩存對象
- 有生命周期,且是定期清理的
-
緩存空間耗盡
- 使用LRU(最近最少使用算法)或者MRU算法進行緩存項清理
-
不可緩存項
- 用戶私有數據
4、緩存系統一般處理步驟
-
接收請求
-
解析請求
- 提取請求的URL及各種首部
-
查詢緩存
-
新鮮度檢測
-
創建響應報文
-
發送響應報文
-
記錄日志
5、新鮮度檢測機制
-
過期日期
-
HTTP/1.0
: expires(其是一個絕對時間) -
HTTP/1.1
: Cache-Control: max-age=600(其是一個相對時間)
-
-
產效性再驗證(revalidate)
- 如果原始內容未改變,則僅響應首部信息,響應碼為304(not modified)
- 如果原始內容發生了改,則正常響應,響應碼為200
- 3)如果原始內容消失,則響應404,此時緩存中的cache object也應該被刪除
-
條件式請求方式
-
If-Modified-Since
: 基于請求內容的時間戳作驗證 If-unmodified-Since
If-Match
-
If-None-Match
: 結合Etag對文件做MD5校驗
-
6、HTTP緩存相關首部
age
: 一個緩存對象從產生到此刻為止,經過的多少時間-
Cache-Control請求首部
-
no-cache
: 不要從緩存中返回內容 -
max-age
: 相對過期時間,是以秒為單位 -
max-stale
: 可以接受的對象,但是過期時間必須小于max-stale值 -
min-fresh
: 接受其新鮮生命期大于其當前age跟min-fresh值之各的緩存對象
-
-
Cache-Control響應首部
-
no-cache
: 可以緩存,但要跟web服務器再驗證 -
no-store
: 不允許緩存 -
public
: 可以用cache內容回應任何用戶 -
private
: 只能用緩存內容回應先前請求該內容的那個用戶 -
max-age
: 本響應包含的對象的過期時間 -
s-maxage
: 公共緩存的最大生命周期 -
must-revlidate
: 每次響應給客戶端時,必須做有效性驗證
-
緩存時需要考慮到的特殊首部
Authorization:跟授權相關的首部
cookie:用戶識別相關的首部
Vary:accept-encoding:所能接受的字符編碼格式
以上三種首部未特性情況下是不予緩存
通常與緩存相關的方法:
1、GET
2、HEAD
7、常見的緩存服務開源解決方案
-
varnish
: 專用于web服務的緩存 -
squid
: 類似nginx,apache,但比varnish穩定
Varnish
varnish對比squid的優點
- 1、varnish的穩定性很高,兩者在完成相同負荷的工作時,squid服務器發生故障的幾率要高于varnish,因為squid要經常重啟
- 2、varnish訪問速度更快,其采用了"Visual Page Cache"技術,所有緩存數據都直接從內存中讀取,而squid是從硬盤讀取,因而varnish在訪問速度方面會更快
- 3、varnish可以支持更多的并發連接,因為varnish的TCP連接釋放要比squid快,因而在高并發連接情況下可以支持更多TCP連接
- 4、varnish可以通過管理端口,使用正則表達式批量的清除部分緩存,而squid是做不到的。
- 5、squid屬于單進程使用單核CPU,但Varnish是通過fork形式打開多進程來做處理,所以是合理的使用所有核來處理相應的請求
varnish對比squid的缺點
- 1、varnish進程一旦Hang、Crash或者重啟,緩存數據都會從內存中完全釋放,此時所有請求都會發送到后端服務器,在高并發情況下,會給后端服務器造成很大的壓力
- 2、在varnish使用中,如果單個vrl的請求通過HA/F5,每次請求不同的varnish服務器時,被請求的varnish服務器都會被穿透到后端,而同樣的請求會在多臺服務器上緩存 ,也會造成varnish的緩存資源浪費,也會造成性能下降
varnish的工作進程特性
-
varnish工作進程示意圖
*
Management(主進程)
* 實現應用新的配置
* 編譯VCL
* 監控Varnish的子進程(其management每隔幾秒進行子進程探測,如較長時間沒有回應探測它將重啟一個子進程)
* 初始化varnish
* 提供命令行接口
*
Child/Cache
* accept : 接收新的連接請求,交由worker線程處理
* worker : 用于處理并響應用戶請求
* expiry : 管理過期緩存,從緩存中清理過期的Cache
*
Vcl compiler
* 把配置文件編譯成VCL格式
*
C compiler
* C編譯器,vcl compiler調用c compiler
*
日志
shared memory log,共享內存日志大小默認一般為90M+,分為兩部分組成,前一部分為計數器,后一部分請求響應的相關數據,日志保存在一個共享的內存空間,只能保存最近最新的日志,需要使用工具,把日志不斷的導出,以實現長期保存
* `varnishlog` : 其以守護進程方式運行,需要將其重啟才會把日志導入到本地磁盤,類似于httpd日志的comm格式
* `varnishncsa` : 其與varnishlog類似,但日志的格式與httpd的combind格式類似
-
varnish的進程工作特性
- varnish啟動或有2個進程master(management)進程和child(worker)進程,master讀入存儲配置命令,進行初始化,然后fork并監控child,child則分配線程進行cache工作,child還會做管理線程生成很多的worker線程
- child線程主線程初始化過程中,將存儲大文件整個加載到內存中,如果該文件超出系統的虛擬內存,則會減少原來配置MMAP大小,然后繼續加載,這時候創建并初始化空間存儲結構體,放在存儲管理的struct中,等待分配
- 接著varnish某個負責接受http連接的線程開始等待用戶請求,如果有新http連接,但這個線程只負責接收,然后喚醒等待線程池中的work線程,進行請求處理
- work線程讀入uri后,將會查找已有的object,命中直接返回,沒有命中則會從后端服務器中取出來,放到緩存中,如果緩存已滿,會根據LRU算法釋放舊的Object,對于釋放緩存,有一個超時線程檢測緩存中所有object的生命周期,如果緩存過期(ttl),則刪除,釋放相應的存儲內存
varnish使用單進程多線程模型,其worker stats類似于一個線程池,所有的資源將整合在一個工作區中,以降低線程在申請或修改內存時,出現的競爭的可能性,當多個線程同時訪問同個資源時,工作區對資源以施加鎖保證用戶的請求在資源爭用時,后來的線程處于等待狀態,以協調線程的工作。
-
varnish存儲緩存機制
-
malloc
基于內存存儲,在內存中存儲各緩存對象,時間久了會產生緩存碎片,如果分配的內存太大,會降低效率。varnish可能會激活大內存空間分配機制(在Centos6以后),這樣也會降低緩存的查詢效率
-
file
所有緩存對象緩存在單個文件中,重啟后將會失效,不支持持久機制,建議使用SSD存放緩存數據,一般用于大文件緩存,如圖片等
-
persistent
基于文件的持久存儲,varnish重啟了,緩存還有效,其目前為實驗性項目,生產環境中不能使用
-
-
varnish內存分配回收機制
-
分配
- malloc()函數
- jemalloc()函數 : 其是malloc的并發實現
-
回收
- free()函數
-
-
VCL編程語法
VCL:varnish configuration language,又被稱之為DSL(域)編譯語言,其是參照C和perl語言的風格編寫,基本格式如下:
-
sub NAME {
....;
-
}
* 不支持循環
* 受狀態引擎的變量,變量的可調用位置與state engine有密切相關性
* 支持終止語句,使用ruturn()返回一個action,其沒有返回值
* 可自定義變量
* //,#,/* */: 用于注釋,會被編譯器忽略
* “域”專用,只能一個域有效
* 操作符: `=,==,~,!,&&,||`
* 條件判斷語句的寫法:
```
單分支:
if (condition) {
....;
} else {
....;
}
多分支:
if (condition) {
...;
} elseif {
...;
} else {
...;
}
```
* 變量賦值:`set name = value`
* 撤消變量的值:`unset name`
安裝及配置Varnish
Centos 6
-
安裝
-
yum install varnish
: 默認安裝為varnish2的版本
-
-
主進程配置文件
/etc/sysconfig/varnish
-
/etc/varnish/default.vcl
: VCL引擎配置文件
-
服務管理腳本
-
/etc/rc.d/init.d/varnish
: varnish服務主進程管理 -
/etc/rc.d/init.d/varnishlog
: varnishlog服務管理 -
/etc/rc.d/init.d/varnishncsa
: varnishncsa服務管理
-
Centos 7
-
安裝
-
yum install varnish
: 默認安裝為varnish 4的版本
-
-
主進程配置文件
-
/etc/varnish/varnish.params
: varnish服務主進程管理 -
/etc/varnish/default.vcl
: VCL引擎配置文件
-
-
服務管理腳本
/usr/lib/systemd/system/varnish.service
/usr/lib/systemd/system/varnishlog.service
/usr/lib/systemd/system/varnishncsa.service
varnish主配置文件參數說明
-
RELOAD_VCL=1
: 是否啟動加載VCL配置文件 -
VARNISH_VCL_CONF=/etc/varnish/default.vcl
: varnish的VCL配置文件路徑 -
VARNISH_LISTEN_PORT=6081
: 默認監聽端口 -
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
: 遠程管理監聽地址 -
VARNISH_ADMIN_LISTEN_PORT=6082
: 遠程管理監聽端口 -
VARNISH_SECRET_FILE=/etc/varnish/secret
: varnish默認加載的密鑰文件,其為遠程連接varnish的共享密鑰文件 -
VARNISH_MIN_THREADS=50
: varnish最少啟動worker線程 -
VARNISH_MAX_THREADS=1000
: varnish最大啟動worker線程數(據說超出5000就不穩定了) -
VARNISH_THREAD_TIMEOUT=120
: 空閑線程的超時時間,超時后被父進程銷毀 -
VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin
: varnish的緩存存儲位置文件,其是二進制格式 -
VARNISH_STORAGE_SIZE=1G
: 緩存大小 -
VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}”
: 基于文件存儲緩存的設置方式 -
VARNISH_STORAGE_SHM=64M
: 基于內存的存儲緩存,指定緩存的大小,不可與文件的方式共存 -
VARNISH_STORAGE=“malloc,${VARNISH_STORAGE_SHM}”
: varnish基于內存存儲緩存的設置方式
注意:varnish配置文件修改不應重啟服務,而是手動加載配置文件
Varnish管理工具
varnishd命令
其可以在/etc/sysconfig/varnish的配置文件中完成
-
Options
-
-f
: vcl的配置文件 -
-a address[:port]
:服務監聽的IP地址及端口 -
-s[name=]type[,options]
: varnish緩存的存儲機制- options
- malloc[,size]
- file[,path[,size[,granularity]]] (力度)
- persistent,path,size
- options
-
-T address[:port]
: 指定管理接口,默認6082 -
-p param=value
: 指定參數 -
-r param
: 指定只讀參數
-
2、varnishadm命令
其是通過連接varnish服務端,可以實現varnish服務的管理操作,實現動態加載VCL的配置文件
-
Options:
-
-S scret_file
: 密鑰文件,跟服務啟動的一樣,在varnish目錄下存放 -
-T IP:PORT
: 連接服務端的端口,默認為6082
-
-
varnish命令行命令
-
vcl.list
: 查看vcl配置文件的列表 -
vcl.load NAME default.vcl
: 加載vcl當前的配置文件為NAME -
vcl.use NAME
: 使用加載的配置文件 -
vcl.show NAME
: 查看加載的配置文件詳細配置信息 -
vcl.discard
: 刪除加載的配置文件 -
backend.list
: 查看后端服務器列表及狀態 -
param.show -l
: 查看varnish主進程的運行選項參數 -
param.set <param> <value>
: 在運行時設定主進程的運行參數 -
ping
: 判斷后端服務器的狀態 -
status
: 查看進行的運行狀態 -
panic.show
: 如果某個子進程曾經panic過,可以使用這個命令查看(panic本為恐慌,代表服務曾經崩潰過) -
storage.list
: 查看緩存存儲機制 -
ban <field> <operator> <arg> [&& <field> <oper> <arg> ]….
: 手動清除緩存 -
ban.list
: 列出清除緩存的規則列表,由上一個參數定義
-
3、Varnishtop命令
內存日志區域查看工具
-
Options
-
-I REGEXP
: 僅顯示被模式匹配到的條目,過濾右邊的信息條目 -
-X
: 僅顯示不被模式匹配到的條目,過濾右邊的信息條目 -
-i
: 過濾左邊字段 -
-x
: 指定的不顯示,不指定的才顯示 -
-C
: 忽略字符大小寫 -
-d
: 顯示已有的日志
varnishtop -i RxHeader varnishtop -I ^User-Agent varnishtop -I ^User-Agent -1 : 只顯示一屏即退出
-
4、varnishstat命令
varnish的運行統計數據
-
Options
-
-f filed,filed,....
: 指明只顯示哪些參數 -
-l
: 列出所有字段 -
-x
: 以XML格式輸出 -
-j
: 以json格式輸出
-
5、varnishlog命令
啟動以comm格式記錄日志到磁盤
6、varnishncsa命令
啟動以combind格式記錄日志到磁盤
VCL Engine
vcl engine是varnish通過VCL配置語言定義的緩存策略,state engine之間有相關性,上級engine通過return指明下級engine,常用的引擎(varnish version 3),如下:
-
vcl_recv
- 由return(lookup)定義到vcl_hash引擎
-
vcl_hash
- 其是對緩存項進行hash計算
-
vcl_fetch
- 其是向后端服務器取文件的
-
vcl_hit
- 其下一個工作引擎可能是deliver,也有可能是pass(為pass的情況下:命中后需要把緩存強制刪除)
vcl_miss
vcl_deliver
-
vcl_pipe
- 無法識別的方法,交由pipe管道引擎處理
-
vcl_pass
- 可以理解的方法,但不緩存,就經由pass
vcl_error
Vcl engine 完整的工作流程示意圖(1)
Vcl engine 完整的工作流程示意圖(2)
Vcl engine常見工作流程
1、查詢緩存未命中的工作流
2、查詢緩存命中的工作流
3、未識別的HTTP方法工作流
4、不予緩存的工作流
5、完整的工作流
-
restart
- 當命中后,把url進行了重寫操作,就需要從vcl_recv重新開始,即為重啟,這類操作被稱之為restart,但varnish有一個內申機制,即重啟了10次,仍然還在重啟,此次請求將會被丟棄
-
error
- 類似于404類的請求錯誤
6、狀態引擎說明
- 1、首先由vcl_init裝載vcl引擎
- 2、再由vcl_recv將請求接入,分析是否服務于此請求,并指定如何服務,可能交由下一個(pipe,pass,hash)處理
- 3、vcl engine可用的return函數
-
return(lookup)
: 送給vcl_hash引擎處理 -
return(pass)
: 交給vcl_pass引擎處理 -
return(pipe)
: 交由vcl_pipe引擎處理 -
error CODE
: 返回錯誤和相應的CODE給客戶端 -
return(deliver)
: 交由vcl_deliver直接投遞 return(hit_for_pass)
-
return(restart)
: 重啟請求
-
VCl引擎中常用變量
-
1、在任何引擎中均可使用
-
now
: 獲取當前系統當前時間 -
.host
: 后端主機或主機名 -
.port
: 后端主機的端口或服務名
-
-
2、用于處理一個請求階段
可用于recv,hash,pipe,pass引擎中。
* `client.ip` : 客戶端IP地址
* `server.hostname` : 服務器的主機名(緩存服務器)
* `server.ip` : varnish服務器的IP
* `server.port` : varnish服務器的端口
* `req.request` : 客戶端的請求方法
* `req.url` : 客戶端請求的URL
* `req.proto` : http協議版本
* `req.backend` : 用于服務此次請求的后端主機
* `req.backend.healthy` : 后端主機的健康狀態
* `req.http.HEADER` : 引用請求報文中指定的首部,哪req.http.host
* `req.hash_always_miss`
* `req.hash_ignore_busy`
* `req.can_gzip` : 客戶端是否能夠接受GZIP壓縮格式的響應內容
* `req.restarts` : 此請求被重啟的次數
-
3、vanish向backend主機發起請求前可用的變量
-
bereq.request
: 請求方法 bereq.url
bereq.proto
bereq.http.HEADER
-
bereq.connect_timeout
: 等待與后端建立連接的超時時長
-
-
4、當后端服務器響應varnish,但未放置緩存之前
-
beresp.do_stream
: 表示流式響應
流式響應:當后端backend主機響應一個10M大小的文件,10M的文件是由多個數據報文組成,當varnish接收到一個報文時,就直接將報文發給客戶端,也不是等待數據報文接收完整后再發出,這就被稱之為流式響應
-
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
beresp.backend.port
-
beresp.storage
: 緩存后端
-
-
5、緩存對象存入cache之后可用的變量,大多數為只讀
-
obj.response
: 服務端所返回的原因短語 -
obj.proto
: 響應時使用的協議 obj.status
-
obj.ttl
: 指明當前的對象緩存的還有多少時長 -
obj.hits
: 這個緩存對象已經命中多少次(大約值) -
obj.http.HEADER
: 后端服務器的響應首部
-
-
6、決定對請求的健做hash計算時可用的變量
-
req.hash
: 把什么內容做hash鍵,做查詢的健
-
-
7、在為客戶端準備響應報文時可用的變量
-
resp.proto
: 指定使用什么協議來響應 resp.status
resp.response
resp.http.HEADER
-
vcl定義后端服務器主機
定義的后端主機需要在recv中調用,必須會出錯
-
1、定義格式:
backend NAME { .host= #后端backend server IP地址 .port= #后端backend server 端口 }
-
2、后端服務器需要vcl_recv中調用,示例如下
示例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; }
定義后端服務器集群
varnish中可以使用director
指令將一個或多個近似的后端主機定義成一個邏輯組,并可以指定其調度方法(也叫挑選方法)來輪流將請求發送至后端backend主機上,不同的director可以使用同一個后端主機,而某director也可以使用“匿名”后端主機(在director中直接定義),每個director都必須有其專用名,且在定義后必須在vcl中進行調用,VCL中任何可以指定后端主機的位置均可按需將其替換為調用某已定義的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、調用方法
示例: sub vcl_recv { .... set req.backend = webservers; }
-
3、backend調度方法
-
round-robin
- 其是對資源對象的輪詢調度方法(當訪問index1.html到后端server1,當訪問index2.html到后端server2),其沒有參數
-
random
- 其是對后端backend server的隨機調度方法,也是建議使用的方法,其支持以下參數
-
.weight = #
: 權重 -
.retires = #
: 來設定查找一個健康后端主機時的嘗試次數
-
varnish2.1.0之后,random挑選方法又多了兩種變化形式client和hash,client類型的director使用client.identity作為挑選因子,這意味著client.identity相同的請求都將被發送至同一后端主機,clinet.itdentity默認為client.ip,但也可以在VCL中將其修改為所需要的標識符,類似的,hash類型的director使用hash數據作為挑選因子,這意味著對同一個URL的請求將被發往同一個后端主機,其常用于多級緩存的場景中,然后,無論是client還是hash,當其傾向于使用后端主機不可用時將會重新挑選新的后端主機
- 其是對后端backend server的隨機調度方法,也是建議使用的方法,其支持以下參數
-
fallback
- 用于定義備用服務器,其更多的是冗余的作用,以下示例
director b3 fallback { { .backend = web1; } { .backend = web2; } { .backend = web3; } } 注意: 只有web1不可用才會使用到web2,web2和web1同時不可用時,才會使用到web3,如果web2不可用,但web1可用,它也會使用web1
-
varnish檢測后端主機的健康狀態
varnish可以檢測后端主機的健康狀態,在判定后端主機失效時能自動將其從可用后端主機列表中移除,而一旦其重新變得可用還可以自動將其設定為可用,為了避免誤判,varnish在探測后端主機的健康狀態發生轉變時(比如某次檢測時某后端主機突然成為不可用狀態),通常需要連續執行幾次探測均為新狀態才將其標記為轉換后的狀態
每個后端服務器當前探測的健康狀態探測方法通過.probe進行設定,其結果可由req.backend.healthy變量獲取,也可通過varnishlog中的backend_health查看或varnishadm的debug.health查看
-
.probe
中探測常用指令-
url
: 探測后端主機健康狀態時請求的URL,默認為/ -
.request
: 探測后端主機健康狀態時所請求內容的詳細格式,定義后,它會替換.rul指定的探測方法,如下:
.request = "GET /.healthtest.html HTTP/1.1" "Host:www.zhenping.me" "Connection: close" #探測時關閉長連接
-
.window
: 設定在判定后端主機健康狀態時基于最近多少次的探測進行,默認是8次 -
.threshold
: 在window中指定的次數中,至少有多少次是成功的才判定后端主機是正常健康運行,默認是3次 -
.initial
: varnish啟動時對后端主機至少需要多少次的成功探測,轉儲同.threshold
-
.expected_response
: 期望后端主機的響應狀態碼,默認是200 -
interval
: 探測請求的發送周期,默認是5秒 -
.timeout
: 每次探測請求的過期時長,默認為2秒
-
```
示例1:
backend web1 {
.host = "www.zhenping.me";
.probe = {
.url = "/.healthtest.html";
.interval = 1s;
.window = 5;
.threshold = 2;
}
}
示例2:
可以將probe的機制定義為一個代碼塊,在backend中引用
probe PRO_NAME {
....;
}
backend NAME {
...;
.probe = PRO_NAME;
}
```
移除單個緩存對象
purge用于清理緩存中的某特定對象及其變種(variants),因此,在有著明確要修剪的緩存對象時可以使用此種方式。HTTP協議的PURGE方法可以實現purge功能,不過,其僅能用于vcl_hit和vcl_miss中,它會釋放內存工作并移除指定緩存對象的所有Vary:-變種,并等待下一個針對此內容的客戶端請求到達時刷新此內容。另外,其一般要與return(restart)一起使用。下面是個在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方法可以到達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";
}
}
客戶端在發起HTTP請求時,只需要為所請求的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(隱藏后端服務軟件版本)
sub vcl_deliver {
if (resp.http.Server) {
unset resp.http.Server; #出于安全考慮,需要將后端所使用的軟件名稱和版本隱藏起來
}
return (deliver);
}
生產環境實例
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);
}