爲儘快解決問題,在安排人員不斷優化後端代碼的同時,考慮在nginx前增長varnish緩存層,只透傳部分動態請求過去,直接減小後端服務器的壓力。
在實際使用中,真正感覺到了varnish服務器強大的威力!在不斷的調優緩存命中率後,後端服務器cpu直接從80%降到了20%,再大的併發前端也能夠直接消化,後端服務器表示毫無壓力。有了這玩意,能夠不再用在後臺寫定時任務,不斷從新生成靜態頁面了,直接丟緩存裏完事!此外,varnish還支持一種叫「神聖模式」,在後端服務器報錯返回500的時候,varnish還能繼續優先返回過去緩存的內容,爲用戶屏蔽部分錯誤,這東東有時真算是救命稻草啊。
但同時,也趟了n多的坑,varnish中的VCL語言太過強大和靈活,稍微運用很差就會中槍。而網上公開的大多數varnish配置文件都是一大抄,根本沒法直接用於生產。在研究了幾天,翻閱了大量各類資料後,才總算把遇到的問題都解決了。
現將調優心得記錄以下:
php
Varnish是一種專業的網站緩存軟件(其實就是帶緩存的反向代理服務),它能夠把整個HTTP響應內容緩存到內存或文件中,從而提升Web服務器的響應速度。
Varnish內置強大的VCL(Varnish Configuration Language)配置語言,容許經過各類條件判斷來靈活調整緩存策略。在程序啓動時,varnish就把VCL轉換成二進制代碼,所以性能很是高。
css
epel源裏也有varnish,可是卻2.x版本的。
由於 varnish 3.0的配置文件與 2.x 的存在很大不一樣,所以varnish團隊不能再更新epel裏的軟件源。若是你想安裝最新版本,推薦使用 rpm 方式。
html
在redhat系服務器上能夠很容易的直接經過rpm包安裝:
前端
wget http://repo.varnish-cache.org/redhat/varnish-3.0/el6/x86_64/varnish/varnish-libs-3.0.4-1.el6.x86_64.rpm wget http://repo.varnish-cache.org/redhat/varnish-3.0/el6/x86_64/varnish/varnish-3.0.4-1.el6.x86_64.rpm wget http://repo.varnish-cache.org/redhat/varnish-3.0/el6/x86_64/varnish/varnish-docs-3.0.4-1.el6.x86_64.rpm yum localinstall *.rpm
varnish的安裝和配置路徑
nginx
/etc/varnish/default.vcl #默認配置文件存文件 /etc/sysconfig/varnish #服務啓動參數腳本 /etc/init.d/varnish #服務控制腳本
能夠經過調整 /etc/sysconfig/varnish 配置文件中的參數來調整啓動參數,設置線程池、緩存到內存仍是文件等。固然若是你樂意也能夠在varnishd後面帶上啓動參數手工啓動服務和管理。 web
如今能經過服務的方式啓動 varnish了:
正則表達式
service varnish start (stop/restart)
將varnish設爲開機自啓動:
apache
chkconfig varnish on
先介紹一下Varnish處理請求的主要處理方法和流程
VCL 需定義幾個默認的函數,在Varnish處理HTTP請求的各個階段會回調這些函數進行處理:
後端
vcl_recv,請求入口,判斷是否要進一步處理,仍是直接轉發給後端(pass)。 此過程當中可使用和請求相關的變量,例如客戶端請求的url,ip,user-agent,cookie等,此過程當中能夠把不需緩存的地址,經過判斷(相等、不相等、正則匹配等方法)透傳給後端,例如POST請求,及jsp、asp、do等擴展名的動態內容;瀏覽器
vcl_fetch,當從後端服務器獲取內容後會進入此階段,除了可使用客戶端的請求變量,還可使用從後端獲取的信息(bersp),如後端返回的頭信息,具體指定此信息的緩存時間TTL;
vcl_miss 緩存未命中時中要作的處理
vcl_hit 緩存命中後作的處理
vcl_delever 發送給客戶端前的處理
vcl_pass 交給後端服務器
vcl_hash 設置緩存的鍵值key
首次請求時過程以下:
recv->hash->miss->fetch->deliver
緩存後再次請求:
recv->hash->hit->deliver(fetch的過程沒了,這就是咱們要作的,把要緩存的頁面保存下來)
直接交給後端pass的狀況:
recv->hash->pass->fetch->deliver(直接從後端獲取數據後發送給客戶端,此時Varnish至關於一箇中轉站,只負責轉發)
安裝完成後,默認的配置文件位於
/etc/varnish/default.vcl
咱們能夠參考缺省配置項學習vcl語言的使用,並進行不斷的調優。
但直接修改配置,不斷的重啓調優效率很是低下痛苦!通過不斷摸索,我發現其實varnish裏內置了日誌模塊,咱們能夠在 defalut.vcl 最上邊引用std庫,以便輸出日誌:
import std;
在須要輸出日誌的地方,使用 std.log 便可:
std.log("LOG_DEBUG: URL=" + req.url);
這樣的話,就能夠經過日誌瞭解varnish的工做流程,很方便的優化啦,效率何止提升十倍!
相似於你想跟蹤哪些鏈接沒有命中緩存,能夠在vcl_miss函數中這樣寫:
sub vcl_miss { td.log("url miss!!! url=" + req.url); return (fetch); }
啓動varnish 後,經過 varnishlog工具跟蹤打印出的日誌
varnishlog -I LOG
Varnish能夠掛載多個後端服務器,並進行權重、輪詢,將請求轉發到後端節點上,以達到避免單點的問題。
舉例以下:
backend web1 { .host = "172.16.2.31"; .port = "80"; .probe = { .url = "/"; .interval = 10s; .timeout = 2s; .window = 3; .threshold = 3; } } backend web2 { .host = "172.16.2.32"; .port = "80"; .probe = { .url = "/"; .interval = 10s; .timeout = 2s; .window = 3; .threshold = 3; } } # 定義負載均衡組 director webgroup random { { .backend = web1; .weight = 1; } { .backend = web2; .weight = 1; } }
其中,在backend 中添加 probe 選項,將能夠對後端節點進行健康檢查。若是後端節點沒法訪問,將會自動摘除掉該節點,直到這個節點恢復。
須要注意window 和threshold 兩個參數。當有後端服務器不可達時,varnish會時不時的報503錯誤。網上查出的資料都是改線程組什麼的,經測試徹底無效。後來發現,只要將 window 和threshold 兩個參數的值設成同樣的,503現象就再沒有發生了。
若是後端須要很長時間來生成一個對象,這裏有一個線程堆積的風險。爲了不這 種狀況,你可使用 Grace。他可讓 varnish 提供一個存在的版本,而後從後端生成新 的目標版本。當同時有多個請求過來的時候,varnish只發送一個請求到後端服務器,在
set beresp.grace = 30m;
時間內複製舊的請求結果給客戶端。
有時候,服務器很古怪,他們發出隨機錯誤,您須要通知 varnish 使用更加優雅的方式處理 它,這種方式叫神聖模式(saint mode)。Saint mode 容許您拋棄一個後端服務器或者另外一個嘗試的後端服務器或者 cache 中服務陳舊的內容。
例如:
sub vcl_fetch { if (beresp.status == 500) { set beresp.saintmode = 10s; return (restart); } set beresp.grace = 5m; }
import std; backend web1 { .host = "172.16.2.31"; .port = "80"; .probe = { .url = "/"; .interval = 10s; .timeout = 2s; .window = 3; .threshold = 3; } } backend web2 { .host = "172.16.2.32"; .port = "80"; .probe = { .url = "/"; .interval = 10s; .timeout = 2s; .window = 3; .threshold = 3; } } # 定義負載均衡組 director webgroup random { { .backend = web1; .weight = 1; } { .backend = web2; .weight = 1; } } # 容許刷新緩存的ip acl purgeAllow { "localhost"; "172.16.2.5"; } sub vcl_recv { # 刷新緩存設置 if (req.request == "PURGE") { #判斷是否容許ip if (!client.ip ~ purgeAllow) { error 405 "Not allowed."; } #去緩存中查找 return (lookup); } std.log("LOG_DEBUG: URL=" + req.url); set req.backend = webgroup; 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); } # 只緩存 GET 和 HEAD 請求 if (req.request != "GET" && req.request != "HEAD") { std.log("LOG_DEBUG: req.request not get! " + req.request ); return(pass); } # http 認證的頁面也 pass if (req.http.Authorization) { std.log("LOG_DEBUG: req is authorization !"); return (pass); } if (req.http.Cache-Control ~ "no-cache") { std.log("LOG_DEBUG: req is no-cache"); return (pass); } # 忽略admin、verify、servlet目錄,以.jsp和.do結尾以及帶有?的URL,直接從後端服務器讀取內容 if (req.url ~ "^/admin" || req.url ~ "^/verify/" || req.url ~ "^/servlet/" || req.url ~ "\.(jsp|php|do)($|\?)") { std.log("url is admin or servlet or jsp|php|do, pass."); return (pass); } # 只緩存指定擴展名的請求, 並去除 cookie if (req.url ~ "^/[^?]+\.(jpeg|jpg|png|gif|bmp|tif|tiff|ico|wmf|js|css|ejs|swf|txt|zip|exe|html|htm)(\?.*|)$") { std.log("*** url is jpeg|jpg|png|gif|ico|js|css|txt|zip|exe|html|htm set cached! ***"); unset req.http.cookie; # 規範請求頭,Accept-Encoding 只保留必要的內容 if (req.http.Accept-Encoding) { if (req.url ~ "\.(jpg|png|gif|jpeg)(\?.*|)$") { remove req.http.Accept-Encoding; } elsif (req.http.Accept-Encoding ~ "gzip") { set req.http.Accept-Encoding = "gzip"; } elsif (req.http.Accept-Encoding ~ "deflate") { set req.http.Accept-Encoding = "deflate"; } else { remove req.http.Accept-Encoding; } } return(lookup); } else { std.log("url is not cached!"); return (pass); } } sub vcl_hit { if (req.request == "PURGE") { set obj.ttl = 0s; error 200 "Purged."; } return (deliver); } sub vcl_miss { std.log("################# cache miss ################### url=" + req.url); if (req.request == "PURGE") { purge; error 200 "Purged."; } } sub vcl_fetch { # 若是後端服務器返回錯誤,則進入 saintmode if (beresp.status == 500 || beresp.status == 501 || beresp.status == 502 || beresp.status == 503 || beresp.status == 504) { std.log("beresp.status error!!! beresp.status=" + beresp.status); set req.http.host = "status"; set beresp.saintmode = 20s; return (restart); } # 若是後端靜止緩存, 則跳過 if (beresp.http.Pragma ~ "no-cache" || beresp.http.Cache-Control ~ "no-cache" || beresp.http.Cache-Control ~ "private") { std.log("not allow cached! beresp.http.Cache-Control=" + beresp.http.Cache-Control); return (hit_for_pass); } if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") { /* Mark as "Hit-For-Pass" for the next 2 minutes */ set beresp.ttl = 120 s; return (hit_for_pass); } if (req.request == "GET" && req.url ~ "\.(css|js|ejs|html|htm)$") { std.log("gzip is enable."); set beresp.do_gzip = true; set beresp.ttl = 20s; } if (req.request == "GET" && req.url ~ "^/[^?]+\.(jpeg|jpg|png|gif|bmp|tif|tiff|ico|wmf|js|css|ejs|swf|txt|zip|exe)(\?.*|)$") { std.log("url css|js|gif|jpg|jpeg|bmp|png|tiff|tif|ico|swf|exe|zip|bmp|wmf is cache 5m!"); set beresp.ttl = 5m; } elseif (req.request == "GET" && req.url ~ "\.(html|htm)$") { set beresp.ttl = 30s; } else { return (hit_for_pass); } # 若是後端不健康,則先返回緩存數據1分鐘 if (!req.backend.healthy) { std.log("eq.backend not healthy! req.grace = 1m"); set req.grace = 1m; } else { set req.grace = 30s; } return (deliver); } # 發送給客戶端 sub vcl_deliver { if ( obj.hits > 0 ) { set resp.http.X-Cache = "has cache"; } else { #set resp.http.X-Cache = "no cache"; } return (deliver); }
跟隨varnish會一塊兒安裝一些方便的調試工具,用好這些工具,對你更好的應用varnish有很大的幫助。
經過這個命令,能夠像相似於 nginx/apache同樣的顯示出用戶的訪問日誌來。
若是你想跟蹤varnish處理每一個請求時的詳細處理狀況,可使用此命令。
直接使用這個命令,顯示的內容很是多,一般咱們能夠經過一些參數,使它只顯示咱們關心的內容。
-b \\只顯示varnish和backend server之間的日誌,當您想要優化命中率的時 候可使用這個參數。
-c \\和-b差很少,不過它表明的是 varnish和 client端的通訊。
-i tag \\只顯示某個 tag,好比「varnishlog –i SessionOpen」將只顯示新會話,注意,這個地方的tag名字是不區分大小寫的。
-I \\經過正則表達式過濾數據,好比「varnishlog -c -i RxHeader -I Cookie」將顯示全部接到到來自客戶端的包含 Cookie 單詞的頭信息。
-o \\聚合日誌請求 ID
例如:
varnishlog -c -o /auth/login 這個命令將告訴您來自客戶端(-c)的全部包含」/auth/login」 字段(-o)請求。
varnishlog -c -o ReqStart 192.168.1.100 只跟蹤一個單獨的client請求
您可使用varnishtop 肯定哪些URL常常被透傳到後端。
適當的過濾使用 –I,-i,-X 和-x 選項,它能夠按照您的要求顯示請求的內容,客
戶端,瀏覽器等其餘日誌裏的信息。
varnishtop -i rxurl \\您能夠看到客戶端請求的 url次數。
Varnishtop -i txurl \\您能夠看到請求後端服務器的url次數。
Varnishtop -i Rxheader -I Accept-Encoding \\能夠看見接收到的頭信息中有有多少次
包含Accept-Encoding。
顯示一個運行varnishd實例的相關統計數據。
Varnish 包含不少計數器,請求丟失率,命中率,存儲信息,建立線程,刪除對象等,幾乎全部的操做。經過跟蹤這些計數器,能夠很好的瞭解varnish運行狀態。
經過命令行,控制varnish服務器。能夠動態的刪除緩存,從新加載配置文件等。
管理端口有兩種連接方式:
1,telnet方式,能夠經過telnet來鏈接管理端口.如:"telnet localhost 6082"
2,varnishadm方式,能夠經過varnish自帶的管理程序傳遞命令.如: varnishadm -n vcache -T localhost:6082 help
動態清除緩存
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 ban.url /2011111.png
其中:ban.url 後的路徑必定不要帶abc.xxx.com域名之類的,不然緩存清除不了。
清除包含某個子目錄的URL地址:
/usr/local/varnish/bin/varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 url.purge /a/
不重啓加載配置文件
登錄到管理界面
/usr/local/varnish/bin/varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
加載配置文件
vcl.load new.vcl /etc/varnish/default.vcl
編譯出錯的話會有提示,成功會返回200
加載新配置文件
vcl.use new.vcl
此時新的配置文件已經生效!