一、序言
緩存伺服器是什麼?
緩存在計算機系統中是用于減少處理器通路記憶體所需平均時間的部件。而在網際網路系統中,緩存伺服器是能夠将使用者所需要的資源不需要交由後端伺服器去進行處理直接響應給使用者的伺服器,進而能夠讓使用者能夠更加快速的接收到資源,還能夠給後端的伺服器減輕負載。是以,緩存伺服器在網站架構中是非常重要的一環,有着“緩存為王”的說法。
因為程式的局部性,是以導緻緩存成為可能。這個局部性分為時間局部性與空間局部性
- 時間局部性:過去通路的程式在未來的時間可能會再次被通路到
- 空間局部性:一個資料被通路到了,那麼離它較近的資料也可能會再次被問到
在緩存中,命中率是有效的判斷緩機制是否合理的一個衡量标準。命中率又分為兩種:
- 文檔命中率:無論是圖檔還是文本檔案統稱為文檔。從文檔個數進行衡量
- 位元組命中率:文檔的内容很大的時候,緩存命中大量的位元組。從内容大小進行衡量
而在緩存伺服器中最著名的有兩個軟體:squid 和 varnish。squid 相當于 web 伺服器領域中的 Apache,varnish 則相當于 Nginx 。這裡我們主要說說 varnish 。
二、HTTP 協定中關于緩存的知識
緩存控制機制
1、Expires:基于時間控制的,表示一個内容被緩存之後的新鮮度的有效期限。這是一個固定的時間,但是可能伺服器端與用戶端的時間不一樣,導緻緩存提前失效
2、Cache-Control: 基于緩存控制,緩存控制有衆多選項
請求(cache-request-directive):
no-cache:不要緩存的實體,要求現在從WEB伺服器去取
max-age:隻接受 Age 值小于 max-age 值,并且沒有過期的對象,也就是說這個資源定義的 max-age 是三個小時,Age 是兩個小時,那麼這個時候用戶端能夠接受這個緩存的資源。但是如果 Age 變成了四個小時了,而這個時候 Age 要比 max-age 大,那麼用戶端就不會接受這個緩存。這個前提是緩存沒有失效
max-stale:可以接受過期的對象,但是過期時間必須小于 max-stale 值。也就是這個緩存對象已經過期了,但是與後端的伺服器聯系不上了,是以緩存伺服器将這個緩存響應給了用戶端
min-fresh:接受其新鮮生命期大于其目前 Age 跟 min-fresh 值之和的緩存對象。如果 Age 是三個小時,min-fresh 是一個小時,那麼能夠接受這個緩存為四個小時,隻要這個緩存不超過四個小時用戶端都接受
響應(cache-response-directive):
public: 可以用 Cached 中内容回應任何使用者;表示這是一個公共的緩存對象,誰都可以使用。 可存緩存于公共緩存中對象
private:隻能用緩存内容回應先前請求該内容的那個使用者,表示這是一個私有緩存,隻能響應給特定使用者。這也就表示使用者的私有資訊是可以被緩存的
no-cache:可以緩存,但是隻有在跟WEB伺服器驗證了其有效性後,才能傳回給用戶端
max-age:本響應包含的對象的過期時間
no-store:不允許緩存,請求響應都可以使用的
新鮮度檢測機制
對于HTTP/1.0與HTTP/1.1,有兩種不同的檢測機制
1、過期日期機制:
HTTP/1.0-Expires
HTTP/1.0 協定使用的是 Expires 緩存機制
這是伺服器指定的緩存有效期限,如果超過這個期限,則緩存失效
但是這有一個缺陷:如果用戶端的時間比伺服器端的時間要快,且快的時間超過了緩存時間,那麼對于用戶端來說,緩存永遠是過期的。
這是在浏覽器中的響應頭部檢視到的。表示不緩存
HTTP/1.1-Cache-Control
HTTP/1.1 使用了Cache-Contorl機制,就是告訴用戶端這個網頁能夠存活多長時間
這是HTTP/1.2協定使用的機制,表示不緩存
2、有效性再驗證:revalidate
當浏覽器在通路過一個網站之後,會将其中的一些靜态資源進行緩存,等到第二次使用到緩存中的資源的時候會向浏覽器發送一個條件式請求
(1) 如果原始内容未發生改變,則僅響應首部(不附帶body部分),響應碼304(Not Modified)
(2) 如果原始内容發生改變,則正常響應,響應碼為200
(3) 如果原始内容小時,則響應404,此時緩存中的cache object也應該被删除
條件式請求首部
If-Modified-Since: 基于請求内容的時間戳作驗證。驗證就是修改的時間戳是否一緻,如果不一緻那麼則伺服器端将最新的響應過來
注意:基于時間戳進行驗證有一個缺陷,時間戳最短隻能記錄1s鐘,如果某一個網站的頁面在不到1s鐘的時間發生了改變,這個時候該資源的時間戳是不會發生改變的。那麼浏覽器再次向伺服器請求該資源是否發生了改變,那麼伺服器端會響應一個304的響應碼。是以這個時候會使用到 If-None-Match
If-Match:如果比對
If-None-Match: 基于Etag進行判斷
Etag 就是一個随機的特征碼,浏覽器向伺服器端驗證這個特征碼是否發生了改變,如果改變了,那麼則将發生改變的資源響應過來
當使用有效性再驗證時,浏覽器發送的請求首部最常用的通常是 If-Modified-Since 與 If-None-Match
三、varnish 簡介
varnish 是使用 “鍵-值”存儲,鍵肯定是放置在記憶體中的。而值根據使用者定義,可能放置在記憶體中,也可能放置在磁盤上。
1、varnish 的結構
Management:varnish的管理程序
Command line:能夠在指令行的方式下接受使用者的請求
Child processs mgmt:能夠管理各個子程序
Initialization:初始化 varnish
Child/cache: 子線程
Command line:能夠在指令行的方式下接受使用者的請求
Storage/hashing:每個存儲都是轉換成 hash 方式鍵值存儲
Log/stats:用來記錄日志并統計各種資料
Accept: 接受新的連接配接請求
Backend communication:如果緩存沒有命中,那麼則需要将自己扮演成用戶端去後端伺服器上取得資料
worker threads:處理使用者請求
Object expiry:清理緩存中的過期對象
2、varnish如何存儲緩存對象
-
基于檔案的緩存。varnish會将内容緩存在單個檔案中,并在單個檔案内部組織成一個varnish檔案系統。由于這個是緩存在磁盤上的,是以
必然會産生大量的磁盤I/O,導緻性能下降。這個方式有一個好處,就是在緩存較大的對象的時候性能會比較好。
- 基于記憶體的緩存,将所有的緩存内容緩存至記憶體中。這樣性能會好很多。在這裡不建議給 varnish 劃分太大的記憶體空間,因為 varnish 需要不斷的進行建立新的緩存,将過期緩存清理,久而久之整個記憶體空間會千瘡百孔。
- 基于檔案的持久存儲。可以重新開機緩存也不會丢失。但是不穩定,在 varnish 第三版本之前一直都是實驗型項目,盡量不要使用
是以,在使用 varnish 的時候一般采用基于file的緩存機制,加上固态硬碟
3、varnish 的工作流程
vcl的狀态引擎:
state engine:各引擎之間存在一定程度上的相關性,前一個engine如果可以有多種下遊engine,則上遊engine需要用return指明要轉移的下遊engine
vcl_recv: varnish 接受請求
vcl_hash: 對請求的 URL 進行 hash
vcl_hit:判斷是否命中
vcl_miss:判斷是否沒命中
vcl_fetch:去後端取資料
vcl_deliver:封裝封包
vcl_pipe:目前端請求到達vcl_recv後,如果前端請求的不是一個http請求,是一個無法了解的請求,那麼則需要該函數将請求直接扔給後端伺服器
vcl_pass: 不能夠緩存的請求直接交給 vcl_fetch 引擎
vcl_error:目前端請求到達vcl_recv後,經過判斷發現此IP是一個經常攻擊伺服器的IP,則直接合成一個error資訊,傳回給此IP。
如果通過判斷,發現前端請求的是一個不存在的頁面資訊, 那麼varnish就可以不用将此請求轉交給後端伺服器。因為後端伺服器經過處理後傳回的還是一個錯誤頁面,那麼varnish何不直接合成一個錯誤頁面交給前端伺服器呢?
以下是 varnish-v4 工作流程圖,原理與第三版差别不大:
四、varnish 使用方法
配置檔案:
/etc/varnish/default.vcl
/etc/varnish/varnish.params
以.vcl結尾的表示varnish工作程序的配置檔案,除了可以使用這個配置檔案進行配置外,也可以實時進行修改
以.params表示主程式的工作特性的參數
啟動之後,預設監聽端口:tcp/6081和tcp/6082
6081:監聽在服務的端口
6082:監聽在管理的端口
配置varnish的三種應用:
1、定義 varnish 主程式的工作特性(Management):varnishd應用程式的指令行參數
監聽的socket,使用的存儲類型等,額外的配置參數
-p param=value
-r param, param,..:設定隻讀參數清單
在 CentOS 7 上通過這個檔案配置:/etc/varnish/varnish.params
2、定義各子程式的工作特性(Child):可在程式運作中,通過其CLI進行配置
3、定義線程的緩存機制(Cache):vcl , 配置緩存系統的緩存機制,通過編輯配置檔案進行修改
/etc/varnish/varnish.params 配置檔案選項:
VARNISH_VCL_CONF=/etc/varnish/default.vcl 主配置檔案,表示從哪個配置檔案中讀取VCL參數
VARNISH_LISTEN_PORT= 監聽的服務端口
VARNISH_ADMIN_LISTEN_ADDRESS=
VARNISH_ADMIN_LISTEN_PORT= 監聽的管理端口
VARNISH_SECRET_FILE=/etc/varnish/secret 秘鑰檔案
VARNISH_STORAGE="malloc,256M" 表示緩存類型,這裡以 malloc 類型緩存
malloc: 表示malloc緩存類型
M:表示緩存大小
也可以使用以下格式,表示基于檔案緩存,G表示檔案大小
VARNISH_STORAGE="file, /path/to/some/where ,1G"
VARNISH_TTL= 表示varnish連接配接後端主機的逾時時長
#DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300"
此選項是被注釋的,可以自己定義,表示線程池,可以在運作時進行修改
/etc/varnish/default.vcl 配置檔案選項
vcl ; 表示支援vcl
backend default { 表示後端主機的IP與端口
.host = "127.0.0.1";
.port = "8080";
}
varnish 指令行的工作方式(CLI)
(1) varnishadm
這是一個能夠連接配接 varnish 的管理界面指令,一般這樣使用
# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
-S:指定密鑰檔案
-T:指定 IP:port
進入 CLI 之後使用的子指令
ping 檢視子程序是否存在
status 檢視子程序運作狀态
vcl.list 列出目前所有可用的 VCL 配置檔案
vcl.load <configname> <filename> 裝載 VCL 的配置檔案,并取名
vcl.inline <configname> <quoted_VCLstring>
vcl.use <configname> 使用已經裝載的 VCL 配置檔案
vcl.discard <configname> 删除已經裝載的 VCL 配置檔案
vcl.list 列出目前所有已經裝載的 VCL 配置檔案
例如:一次簡單的應用
vcl.load test default.vcl 裝載 default.vcl ,并命名為 test
vcl.list
200
active 0 boot 系統預設的,在啟動 varnish 的時候就啟動的一個配置檔案,表示處于活動狀态
available 0 test 這是手動裝載的,表示處于可用狀态
vcl.use test 表示使用 test 這個配置檔案
200
VCL 'test' now active
vcl.list
200
available 0 boot 當切換之後系統預設的就處于可用狀态
active 0 test test 就處于活動狀态了
vcl.discard test 删除定義的配置檔案
200
vcl.list
200
active 0 boot
param.show 檢視 varnish 運作時的參數
param.set thread_pools 4 設定某個參數的值
param.show thread_pools 檢視某一個參數的詳細情況
panic.show
我們說核心會恐慌,那麼如果線程的記憶體耗盡也會恐慌,那麼我們可以使用這個指令檢視具體原因
panic.clear 清楚恐慌資訊
storage.list 顯示目前使用的存儲類型
200
Storage devices:
storage.Transient = malloc
storage.s0 = malloc
vcl.show boot 顯示 VCL 配置檔案的全部内容
backend.list 顯示後端伺服器
ban 當我們定義一個資源的緩存時間為 2h ,但是一個小時之後這個資源就已經被修改了,那麼這個時候我們就可以使用這個指令來清楚緩存。規則還沒說
ban.list 顯示定義的清理緩存的規則
(2) varnishlog
直接回車,顯示日志詳情。隻有通路的時候才會顯示日志
(3) varnishncsa
直接回車,以 combined 的形式顯示通路的時候的日志資訊
5、varnish 的内置變量
變量種類:
client 用戶端
server 服務端,varnish本身
req 用戶端向varnish請求
resp varnish向用戶端響應
bereq varnish向後端伺服器請求
beresp 後端伺服器向varnish響應
obj varnish 或者後端真實伺服器響應的資源屬性
storage varnish 緩存的資源的屬性
bereq
bereq.http.HEADERS:由varnish發往backend server的請求封包的指定首部
bereq.request:請求方法
bereq.url:請求的路徑
bereq.proto:請求的http的協定版本
bereq.bankup:後端主機
beresp
beresp.proto:後端伺服器響應的協定版本
beresp.status:後端伺服器響應的狀态碼
beresq.backend.ip:後端伺服器的ip
beresq.backend.name:後端伺服器的name
beresq.ttl:後端伺服器響應的内容的生存時長
obj
obj.ttl:對象的ttl值
obj.hits:對象的命中次數
五、varnish 的應用
1、強制對某資源的請求,不檢查緩存
vcl_recv {
if (req.url ~ "(?i)^/index.html$") { (?i)表示不區分大小寫
return (pass);
}
}
vcl_recv {
if (req.url ~ "(?i)^/login" || req.url ~ "(?i)^/admin") {
return (pass);
}
}
對 http://IP/admin/index.html 或者 http://IP/login/index.html 的資源通路不讓它從緩存中獲得
2、對特定類型的資源取消其私有的cookie辨別,并強行設定其緩存時長:
varnish-v4
sub vcl_backend_beresp {
if (beresp.http.cache-control !~ "s-maxage") {
if (bereq.url ~ "(?i)\.jpg$") {
set beresp.ttl = s;
unset beresp.http.Set-Cookie;
}
if (bereq.url ~ "(?i)\.css$") {
set beresp.ttl = s;
unset beresp.http.Set-Cookie;
}
}
}
如果某一個資源的首部中的 cache-control 中沒有設定 s-maxage(公共緩存時間) 值,如果後端的伺服器傳回的是一個圖檔類型的資源或者 css 類型的資源,那麼則給該資源設定一個緩存時長,并且如果該頁面有 cookie 值,那麼則取消 cookie 值.
s-maxage 這是公共緩存的時長,表示這個資源的緩存是在 varnish 中進行緩存的,而不是在浏覽器中進行緩存的時長,是以這個時候我們 在浏覽器中是看不到這個緩存時長的
這個是在 varnish-v4 中進行定義的,如果我們是在 varnish-v3 中的話那麼則要将該代碼放置在 “sub vcl_backend_fetch” 中,并且在進 行路徑定義的時候不要加上 be
vcl_v3
sub vcl_backend_fetch {
if (beresp.http.cache-control !~ "s-maxage") {
if (req.url ~ "(?i)\.jpg$") {
set beresp.ttl = s;
unset beresp.http.Set-Cookie;
}
if (req.url ~ "(?i)\.css$") {
set beresp.ttl = s;
unset beresp.http.Set-Cookie;
}
}
}
3、對緩存進行修剪
(1) purge:
使用 PURGE 這個方法去請求某一個資源,能夠将這個資源從緩存中清除
vcl ;
acl purgers { 可以定義在開頭的 vcl 之後
"127.0.0.0"/;
"172.18.0.0"/;
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purgers) { PURGE 這個操作很危險,是以要添加此類請求的通路控制 acl purgers
return(synth(,"Purging not allow for" + client.ip));
}
return(purge);
}
}
sub vcl_purge {
purge 這個域系統有預設定義,是以這裡不需要自定義加上 purge 這個域的定義
}
# curl -X PURGE http://IP/a.jpg
這樣系統會将 a.jpg 這個緩存資源進行強制清理
purge 隻能對某一個資源進行修剪
ban 對某一類資源進行修剪
(2) ban:
ban <field> <operator> <arg>
示例:
ban req.url ~ ^/javascripts
這個指令可以不用在配置檔案中進行定義,而是直接在 VCL 的指令行中進行修剪即可
4、對後端伺服器做負載均衡:
backend name {
.attribute = "value";
}
.host: BE主機的IP
.port:BE主機監聽的PORT
.probe:對BE做健康狀态檢測
.max_connections:并發連接配接最大數量
5、後端主機的健康狀态監測方式:
vcl ;
probe healthy {
.url = /index.html;
.interval = s; 表示每 s 檢測一次
.timeout = s; 檢測逾時時長
.window = ; 向後端伺服器檢測多少次
.threshold = ; 檢測成功多少次才能稱之為健康
}
backend websrv1 {
.host = "172.18.49.80";
.port = "80";
.proby = healthy;
}
對主機進行人為标記健康狀态
在 VCL 指令行模式下進行
(1) backend.set_health srv1 sick
将主機 srv1 标記為 down ,即使檢測成功也無法通路
(2) backend.set_health srv1 auto
這表示探測結果由探測機制進行檢測,決定狀态
(3) backend.set_health srv1 healthy
将主機 srv1 标記為 healthy,即使檢測失敗也會發往這個主機
設定後端主機的屬性:
backend websrv1 {
.host = "172.18.49.80";
.port = "80";
.proby = healthy;
.max_connections = ;
}
6、對後端兩個主機進行代理
backend websrv1 {
.host = "172.18.49.80";
.port = "80";
.probe = { 這是健康狀态查詢請求的頁面
.url = "/test1.html";
}
}
backend websrv2 {
.host = "172.18.49.81";
.port = "80";
.probe = {
.url = "/test1.html"
}
}
sub vcl_recv {
if (req.url ~ "(?i)\.(jpg|png|fig)$") {
set req.backend_hint = websrv1;
} else {
set req.backend_hint = websrv2;
}
}
7、對後端主機進行負載均衡
import directors;
要定義叢集,那麼首先要對建立一個新的叢集對象,并将所定義的主機加在定義的叢集對象中
sub vcl_init {
new mycluster = directors.round_robin(); 定義一個新的叢集對象,叢集的排程方式是論調
mycluster.add_backend(websrv1);
mycluster.add_backend(websrv2);
}
vcl_recv {
set req.backend_hint = mycluster.backend();
}
基于 cookie 的 session 綁定
sub vcl_init {
new h = directors.hash();
h.add_backend(srv1,)
h.add_backend(srv2,)
srv1 表示定義的某台後端的主機
表示權重
}
sub vcl_recv {
set req.backend_hint = h.backend(req.http.cookie)
}