數據緩存:例如MySQL到web應用服務器之間的緩存服務器緩存的資源是數據緩存javascript
過時時間驗證緩存是否失效顆粒度太大,若是頁面剛剛緩存應用服務器發生了變化,結果客戶端拿到的就是過時數據;從而加入了條件式驗證緩存的失效性,每次客戶端請求到達緩存服務器,緩存服務器都要拿本地的數據和應用服務器的數據比較時間戳,若是時間戳發生了變化則緩存新的數據;這樣雖然粒度小了,可是仍是會有問題,若是應用服務器在同一秒頁面數據變化了三次,而緩存服務器拿到的是第一份數據,這樣仍是會發生數據失效的問題;從而又引入了Etag(擴展標記)來標記惟一的頁面數據。此時雖然解決了數據失效性的問題,可是每次客戶端的請求都要去後端服務器作比較,對緩存和應用服務器都是不小的壓力,咱們不得不採起折中的解決方案就是「過時時間驗證+條件式驗證」,將不常常變更的頁面作過時時間驗證,變更頻繁的採用條件式驗證。php
請求報文用於通知緩存服務如何使用緩存響應請求:css
cache-request-directive = "no-cache" 不能使用緩存系統中的緩存響應我,必須先去應用服務器作緩存驗證 "no-store" 不能使用緩存系統中的緩存響應我,必須去應用服務器請求響應我 "max-age" "=" delta-seconds "max-stale" [ "=" delta-seconds ] "min-fresh" "=" delta-seconds "no-transform" "only-if-cached" cache-extension
響應報文用於通知緩存服務器如何存儲上級服務器響應的內容:html
cache-response-directive = "public" 全部緩存系統均可以緩存 "private" [ "=" <"> 1#field-name <"> ] 僅可以被私有緩存所緩存 "no-cache" [ "=" <"> 1#field-name <"> ],可緩存,但響應給客戶端以前須要revalidation,即必須發出條件式請求進行緩存有效性驗正 "no-store" ,不容許存儲響應內容於緩存中 "no-transform" 不能轉換格式 "must-revalidate" 必須從新驗證 "proxy-revalidate" "max-age" "=" delta-seconds 私有緩存最大緩存時長 "s-maxage" "=" delta-seconds 公共緩存最大緩存時長 cache-extension
Web Page Cache解決方案:squid和varnish,它們的關係就像Apache和Nginxjava
Varnish cache,或稱Varnish,是一套高性能的反向網站緩存服務器(reverse proxy server)web
varnish官方站點: http://www.varnish-cache.org/算法
varnish擁有倆套配置文件;一套配置文件用於varnish自身進程的參數配置,另外一套用於定義緩存規則;定義緩存規則須要使用靈活的語言來定義,這就是VCL(varnish語言);應用時須要將VCL編寫的規則送給VCC編譯後才能運行,因此安裝varnish須要依賴gcc編譯器。chrome
varnish的安裝:yum install varnish -y
,依賴epel源,目前CentOS7的epel源提供的版本是v4.0.5shell
varnish的程序環境:數據庫
/etc/varnish/varnish.params
: 配置varnish服務進程的工做特性,例如監聽的地址和端口,緩存機制/etc/varnish/default.vcl
:配置各Child/Cache線程的緩存策略/usr/sbin/varnishd
:主程序/usr/bin/varnishadm
:命令行工具/usr/bin/varnishhist
:/usr/bin/varnishlog
:查看內存中的日誌/usr/bin/varnishncsa
:以NCSA格式查看日誌/usr/bin/varnishstat
:查看緩存日誌狀態信息/usr/bin/varnishtop
:以rank方式查看日誌/usr/bin/varnishtest
:測試工具程序/usr/sbin/varnish_reload_vcl
:VCL配置文件重載程序/usr/lib/systemd/system/varnish.service
:varnish服務/usr/lib/systemd/system/varnishlog.service
:日誌持久的服務/usr/lib/systemd/system/varnishncsa.service
:日誌持久的服務# varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss #顯示指定參數的當前統計數據
# varnishstat -l -f MAIN -f MEMPOOL #列出指定配置段的每一個參數的意義
# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 #登陸管理程序
help [<command>] 獲取幫助 ping [<timestamp>] 測試服務器 auth <response> quit 退出cli banner status 顯示狀態 start 啓動 stop 中止 vcl.load <configname> <filename> 加載VCL配置文件 vcl.inline <configname> <quoted_VCLstring> vcl.use <configname> 激活VCL配置文件 vcl.discard <configname> 刪除VCL配置 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
默認配置文件:
RELOAD_VCL=1 VARNISH_VCL_CONF=/etc/varnish/default.vcl #指定加載VCL配置文件 VARNISH_LISTEN_ADDRESS=192.168.1.5 #服務監聽的地址 VARNISH_LISTEN_PORT=6081 #默認監聽端口 VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 #管理服務監聽的地址 VARNISH_ADMIN_LISTEN_PORT=6082 #管理服務監聽的端口 VARNISH_SECRET_FILE=/etc/varnish/secret #鏈接祕鑰 VARNISH_STORAGE="malloc,256M" #用內存提供保存緩存,大小爲256M VARNISH_USER=varnish #用戶身份 VARNISH_GROUP=varnish #組身份 DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300" #指定進程的運行參數
Varnish配置語言(VCL)是一種特定於域的語言,用於描述Varnish Cache的請求處理和文檔緩存策略。加載新配置時,由Manager進程建立的VCC進程將VCL代碼轉換爲C.此C代碼一般由gcc共享對象編譯。而後將共享對象加載到cacher進程中。
VCL有多個狀態引擎,狀態之間存在相關性,但狀態引擎彼此間互相隔離;每一個狀態引擎可以使用return(x)指明關聯至哪一個下一級引擎;每一個狀態引擎對應於vcl文件中的一個配置段,即爲subroutine
倆個特殊的引擎:
vcl_init:在處理任何請求以前要執行的vcl代碼:主要用於初始化VMODs; vcl_fini:全部的請求都已經結束,在vcl配置被丟棄時調用;主要用於清理VMODs;
默認VCL配置也叫作隱式規則,在配置文件中沒法看到,即便咱們修改了配置文件,默認配置規則也是在最後作處理。
varnish> vcl.show -v boot #在客戶端cli工具中查看 sub vcl_recv { if (req.method == "PRI") { #若是客戶端的請求方法是PRI,不支持SPDY或HTTP/2.0 return (synth(405)); #則構建一個405的包響應給客戶端 } if (req.method != "GET" && #若是客戶端的請求方法不是GET req.method != "HEAD" && #而且不是HEAD req.method != "PUT" && #而且不是PUT req.method != "POST" && #而且不是... req.method != "TRACE" && req.method != "OPTIONS" && req.method != "DELETE") { return (pipe); #即,不是標準HTTP請求方法的交給pipe(管道) } if (req.method != "GET" && req.method != "HEAD") { #請求方法不是GET和HEAD的 return (pass); #交給pass處理,也就是除了GAT和HEAD方法其餘的沒法緩存 } if (req.http.Authorization || req.http.Cookie) { #http的請求首部包含Authorization(認證)或Cookie,即我的專有信息 return (pass); #交給pass處理,由於這些帶有我的信息的數據沒法緩存 } return (hash); #以上的規則都沒有作處理的請求交給hash作處理,剩下的是能夠查詢緩存的請求了 } sub vcl_pipe sub vcl_pass sub vcl_hash sub vcl_purge sub vcl_hit sub vcl_miss sub vcl_deliver sub vcl_synth sub vcl_backend_fetch sub vcl_backend_response sub vcl_backend_error sub vcl_init sub vcl_fini
示例1:obj.hits是內建變量,用於保存某緩存項的從緩存中命中的次數
# vim /etc/varnish/varnish.params VARNISH_LISTEN_PORT=80 # vim /etc/varnish/default.vcl backend default { .host = "192.168.0.9"; .port = "80"; } sub vcl_deliver { if (obj.hits>0) { set resp.http.X-Cache = "HIT via" + " " + server.ip; } else { set resp.http.X-Cache = "MISS from " + server.ip; } } # systemctl restart varnish #謹慎重啓varnish服務,會致使以前的緩存失效 # for i in {1..5}; do curl -I -s 192.168.0.8 |grep "X-Cache"; done #在客戶端測試,第一次Miss X-Cache: MISS from 192.168.0.8 X-Cache: HIT via 192.168.0.8 X-Cache: HIT via 192.168.0.8 X-Cache: HIT via 192.168.0.8 X-Cache: HIT via 192.168.0.8
經常使用變量:
示例2:強制對某類資源的請求不檢查緩存
# vim /etc/varnish/default.vcl sub vcl_recv { if (req.url ~ "(?i)^/(login|admin)") { #"?i"表示忽略大小寫,匹配到url中帶有login或admin的不查詢緩存 return(pass); } } # varnish_reload_vcl # for i in {1..5}; do curl -I -s http://192.168.0.8/login |grep "X-Cache"; done #客戶端測試 X-Cache: MISS from 192.168.0.8 #所有Miss X-Cache: MISS from 192.168.0.8 X-Cache: MISS from 192.168.0.8 X-Cache: MISS from 192.168.0.8 X-Cache: MISS from 192.168.0.8 # for i in {1..5}; do curl -I -s http://192.168.0.8/admin |grep "X-Cache"; done X-Cache: MISS from 192.168.0.8 X-Cache: MISS from 192.168.0.8 X-Cache: MISS from 192.168.0.8 X-Cache: MISS from 192.168.0.8 X-Cache: MISS from 192.168.0.8 # for i in {1..5}; do curl -I -s http://192.168.0.8/ |grep "X-Cache"; done #其餘網頁正常查詢緩存 X-Cache: MISS from 192.168.0.8 X-Cache: HIT via 192.168.0.8 X-Cache: HIT via 192.168.0.8 X-Cache: HIT via 192.168.0.8 X-Cache: HIT via 192.168.0.8
示例3:對於特定類型的資源,例如公開的圖片等,取消其私有標識,並強行設定其能夠由varnish緩存的時長
sub vcl_backend_response { if (beresp.http.cache-control !~ "s-maxage") { if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)$") { unset beresp.http.Set-Cookie; set beresp.ttl = 3600s; } } }
示例4:在報文首部添加真正的客戶端IP,使得後端server能夠記錄真正客戶端來源
[root@varnish ~]# vim /etc/varnish/default.vcl sub vcl_recv { if (req.restarts == 0) { #匹配沒有被重寫的URL請求,即第一次請求 if (req.http.X-Forwarded-For) { #變量存在而且有值則爲真 set req.http.X-Forwarded-For = req.http.X-Forwarded-For + "," + client.ip; #將真正的client.ip添加到此變量中,用","隔開 } else { set req.http.X-Forwarded-For = client.ip; #若是變量不存在或值爲空,則直接將client.ip賦值與 } } } [root@varnish ~]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 varnish> vcl.load conf1 /etc/varnish/default.vcl varnish> vcl.use conf1 varnish> vcl.list available 0 boot available 0 reload_2018-07-14T09:55:58 active 0 conf1 #當前正在使用的配置 [root@web1 ~]# vim /etc/httpd/conf/httpd.conf LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined [root@web1 ~]# systemctl restart httpd [root@client ~]# for i in {1..5}; do curl -I -s http://192.168.0.8/login |grep "X-Cache"; done #在客戶端測試 [root@web1 ~]# tail /var/log/httpd/access_log 192.168.0.8 - - [14/Jul/2018:09:56:49 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" 192.168.0.8 - - [14/Jul/2018:09:56:49 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" 192.168.0.8 - - [14/Jul/2018:09:56:49 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" 192.168.0.8 - - [14/Jul/2018:09:56:49 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" 192.168.0.8 - - [14/Jul/2018:09:56:49 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" 192.168.0.7 - - [14/Jul/2018:10:25:11 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" 192.168.0.7 - - [14/Jul/2018:10:25:11 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" 192.168.0.7 - - [14/Jul/2018:10:25:11 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" 192.168.0.7 - - [14/Jul/2018:10:25:11 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" 192.168.0.7 - - [14/Jul/2018:10:25:11 +0800] "HEAD /login HTTP/1.1" 301 - "-" "curl/7.29.0" #拿到了真正客戶端IP,而不是以前的varnish服務器的IP
示例5:訪問控制,拒絕curl客戶端的訪問
sub vcl_recv { if(req.http.User-Agent ~ "curl") { return(synth(403)); } }
1) 能執行purge操做
sub vcl_purge { return (synth(200,"Purged")); }
2) 什麼時候執行purge操做
sub vcl_recv { if (req.method == "PURGE") { return(purge); } ... }
示例6:清除指定緩存
[root@varnish ~]# vim /etc/varnish/default.vcl acl purgers { "127.0.0.0"/8; "192.168.0.0"/24; } sub vcl_recv { if (req.method == "PURGE") { if (!client.ip ~ purgers) { return(synth(405,"Purging not allowed for " + client.ip)); } return(purge); } } varnish> vcl.load conf3 /etc/varnish/default.vcl varnish> vcl.use conf3 [root@client ~]# curl -I http://192.168.0.8/ X-Cache: HIT via 192.168.0.8 [root@client ~]# curl -I -X "PURGE" http://192.168.0.8/ [root@client ~]# curl -I http://192.168.0.8/ X-Cache: MISS from 192.168.0.8
1)varnishadm: ban <field> <operator> <arg>
varnish> ban req.url ~ (?i)^/javascripts
2)在配置文件中定義,使用ban()函數
sub vcl_recv { if (req.method == "BAN") { ban("req.http.host == " + req.http.host + " && req.url == " + req.url); #將規則拼接起來傳遞給ban函數 return(synth(200, "Ban added")); } } # curl -I -X "BAN" http://192.168.0.8/javascripts/
backend default { .host = "172.16.0.9"; .port = "80"; } backend appsrv { .host = "172.16.0.10"; .port = "80"; } sub vcl_recv { if (req.url ~ "(?i)\.php$") { set req.backend_hint = appsrv; } else { set req.backend_hint = default; } }
import directors; backend srv1 { .host = "192.168.0.9"; .port = "80"; } backend srv2 { .host = "192.168.0.10"; .port = "80"; } sub vcl_init { new websrvs = directors.round_robin(); #round_robin()調度算法,不支持加權 websrvs.add_backend(srv1); websrvs.add_backend(srv2); } sub vcl_recv { set req.backend_hint = websrvs.backend(); }
sub vcl_init { new h = directors.hash(); h.add_backend(one, 1); h.add_backend(two, 1); } sub vcl_recv { set req.backend_hint = h.backend(req.http.cookie); }
sub vcl_init { new websrvs = directors.random(); websrvs.add_backend(srv1, 1); websrvs.add_backend(srv2, 2); }
.probe:定義健康狀態檢測方法;
.url:檢測時要請求的URL,默認爲」/";
.request:發出的具體請求;
.request =
"GET /.healthtest.html HTTP/1.1"
"Host: www.dongfei.tech"
"Connection: close"
.window:基於最近的多少次檢查來判斷其健康狀態;
.threshold:最近.window中定義的這麼次檢查中至有.threshhold定義的次數是成功的;
.interval:檢測頻度;
.timeout:超時時長;
.expected_response:指望的響應碼,默認爲200;
import directors; probe http_chk { .url = "/index.html"; .interval = 2s; .timeout = 2s; .window = 10; #最近10次檢查 .threshold = 7; #有7次成功則爲健康主機 } backend srv1 { .host = "192.168.0.9"; .port = "80"; .probe = http_chk; } backend srv2 { .host = "192.168.0.10"; .port = "80"; .probe = http_chk; } sub vcl_init { new websrvs = directors.random(); websrvs.add_backend(srv1, 1); websrvs.add_backend(srv2, 2); } sub vcl_recv { set req.backend_hint = websrvs.backend(); }
varnish> backend.list #查看後端主機健康狀態信息 Backend name Refs Admin Probe srv1(192.168.0.9,,80) 3 probe Healthy 10/10 srv2(192.168.0.10,,80) 3 probe Healthy 10/10 varnish> backend.set_health srv1 sick|healthy|auto #手動標記主機狀態 down|up|probe
設置後端的主機屬性:
backend BE_NAME { ... .connect_timeout = 0.5s; #鏈接超時時間 .first_byte_timeout = 20s; #第一個字節20s不響應則爲超時 .between_bytes_timeout = 5s; #第一個字節和第二個字節間隔超時時間 .max_connections = 50; #最大鏈接數 }
最大併發鏈接數 = thread_pools * thread_pool_max
virnish的日誌默認存儲在80M的內存空間中,若是日誌記錄超出了則覆蓋前邊的日誌,服務器重啓後丟失;須要更改配置使其永久保存到磁盤
# varnishstat -1 -f MAIN #指定查看MAIN段的信息
# varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss #顯示指定參數的當前統計數據 MAIN.cache_hit 47 0.00 Cache hits MAIN.cache_miss 89 0.01 Cache misses
# varnishtop -1 -i ReqHeader #顯示指定的排序信息 165.00 ReqHeader Accept: */* 165.00 ReqHeader Host: 192.168.0.8 165.00 ReqHeader User-Agent: curl/7.29.0 165.00 ReqHeader X-Forwarded-For: 192.168.0.7
將日誌永久保存到:/var/log/varnish/varnish.log
# systemctl start varnishlog.service
以Apache/NCSA日誌格式顯示
# varnishncsa 192.168.0.7 - - [14/Jul/2018:12:34:23 +0800] "GET http://192.168.0.8/javascripts/test1.html HTTP/1.1" 200 11 "-" "curl/7.29.0"
# tree ansible-role-varnish/ ansible-role-varnish/ ├── files │ ├── default.vcl │ ├── secret │ └── varnish.params ├── handlers │ └── main.yml ├── tasks │ ├── copy.yml │ ├── main.yml │ ├── setup-varnish.yml │ └── start.yml └── templates
# find ansible-role-varnish/ -name *.yml -exec ls {} \; -exec cat {} \; ansible-role-varnish/handlers/main.yml - name: restart varnish service: name=varnish state=restarted - name: reload vcl command: varnish_reload_vcl ansible-role-varnish/tasks/start.yml - name: start service service: name=varnish state=started ansible-role-varnish/tasks/copy.yml - name: copy configure file copy: src=varnish.params dest=/etc/varnish/varnish.params notify: restart varnish - name: copy secret file copy: src=secret dest=/etc/varnish/secret notify: restart varnish - name: copy default.vcl file copy: src=default.vcl dest=/etc/varnish/default.vcl notify: reload vcl ansible-role-varnish/tasks/main.yml - include: setup-varnish.yml - include: copy.yml - include: start.yml ansible-role-varnish/tasks/setup-varnish.yml - name: install yum-utils yum: name={{ item }} state=present with_items: - yum-utils - pygpgme - name: Add epel repo yum_repository: name: alibaba description: epel baseurl: https://mirrors.aliyun.com/epel/7Server/x86_64/ repo_gpgcheck: no gpgcheck: no enabled: yes - name: install varnish yum: name=varnish state=present
# find ansible-role-varnish/files/* -exec ls {} \; -exec cat {} \; ansible-role-varnish/files/default.vcl #------------------------------------------------- vcl 4.0; import directors; backend default { .host = "127.0.0.1"; .port = "8080"; } probe http_chk { .url = "/index.html"; .interval = 2s; .timeout = 2s; .window = 10; .threshold = 7; } backend srv1 { .host = "192.168.0.9"; .port = "80"; .probe = http_chk; } backend srv2 { .host = "192.168.0.10"; .port = "80"; .probe = http_chk; } sub vcl_init { new websrvs = directors.random(); websrvs.add_backend(srv1, 1); websrvs.add_backend(srv2, 1); } sub vcl_recv { set req.backend_hint = websrvs.backend(); 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.http.User-Agent ~ "curl") { return(synth(403)); } } sub vcl_backend_response { if (beresp.http.cache-control !~ "s-maxage") { if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)$") { unset beresp.http.Set-Cookie; set beresp.ttl = 3600s; } } } sub vcl_deliver { if (obj.hits>0) { set resp.http.X-Cache = "HIT via" + " " + server.ip; } else { set resp.http.X-Cache = "MISS from " + server.ip; } } #------------------------------------------------- ansible-role-varnish/files/secret 7e40f334-d2e7-4edb-aecb-559519e456f9 ansible-role-varnish/files/varnish.params RELOAD_VCL=1 VARNISH_VCL_CONF=/etc/varnish/default.vcl VARNISH_LISTEN_ADDRESS=0.0.0.0 VARNISH_LISTEN_PORT=80 VARNISH_ADMIN_LISTEN_ADDRESS=0.0.0.0 VARNISH_ADMIN_LISTEN_PORT=6082 VARNISH_SECRET_FILE=/etc/varnish/secret VARNISH_STORAGE="malloc,256M" VARNISH_USER=varnish VARNISH_GROUP=varnish #DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300"
感謝閱讀!