Nginx+Varnish 實作動靜分離,為伺服器分流,降低伺服器負載
相必大家在看加快網站響應速度方面的文章時,都提過這麼一條:動靜分離。那怎樣實作動靜分離呢,這裡筆者就親自搭建相關服務實作動靜分離。
動靜分離是一種架構,就是把靜态檔案,比如JS、CSS、圖檔甚至有些靜态頁面交給獨立的伺服器叢集處理,進而進行分流,使伺服器降低壓力。
上面說把一些靜态的檔案分離出去,有讀者就會笑了,靜态檔案能有多少,能消耗多少資源。
讀者以實際經驗告訴大家,千萬不要小瞧這些靜态檔案,現在大部分網站都是以視訊、圖檔為主,試想下天貓、淘寶、京東之類,文字能有多少,圖檔、視訊又是多少,就知道這個量到底是多麼的龐大,而動靜分離從筆者公司實際使用經驗來看,效果是杠杠的。
筆者這裡的環境是: Centos6.4 Nginx Varnish , 其中Nginx響應請求,Varnish主要做緩存和反向代理(筆者這裡說個題外話,很多人疑惑反向代理和正向代理,這兩個概念說的都有些玄幻,筆者有個簡單粗暴的解釋. 反向代理:隐藏真實伺服器資訊, 正向代理:隐藏真實用戶端資訊)
筆者這裡弄三台主機:192.168.138.10 192.168.138.3 192.168.138.4 (10安裝Varnish,3、4安裝Nginx)
第一步:先在92.168.138.3 192.168.138.4 兩台機器上安裝Nginx,這個安裝相對容易,筆者就不做累贅了,隻要你看到下面兩個頁面就OK了

第二步:在192.168.138.10上安裝Varnish
這裡筆者從官網上下載下傳了比較新的 Varnish-3.0.7, 筆者準備使用源碼編譯安裝,安裝在/usr/local/varnish 目錄下
tar -zxvf varnish-3.0.7.tar.gz
安裝之前還是老話,依賴先安裝上,筆者在其官方網站上找到了其依賴的包
https://www.varnish-cache.org/docs/4.0/installation/install.html
yum install -y autoconf automake jemalloc-devel libedit-devel libtool ncurses-devel pcre-devel pkgconfig python-docutils python-sphinx
然後進行編譯前檢查
./configure \
--prefix=/usr/local/varnish/ \
--enable-dependency-tracking \
--enable-developer-warnings \
--enable-debugging-symbols
檢查無誤後開始編譯安裝
make && make install
第三步:開始配置varnish
其配置檔案位于 /usr/local/varnish/etc/varnish/default.vcl 可以看到裡面已經有些範例
筆者在這裡先要對其架構及一些常用函數加以說明,不然後面都看不懂在配置寫什麼玩意
varnish使用了一種叫VCL的語言去對其進行配置,至于這語言啥玩意,就不用過多關心了,大緻了解下就OK了,其文法可以看裡面的相關範例,裡面有些函數,下面一一列出
vcl_recv 函數: 接收和處理請求,當請求到達并成功接收後被調用,判斷請求的資料并決定如何處理請求;該函數傳回值有這麼些關鍵字-》
pass 表示進入pass模式,将請求交給vcl_pass 函數
pipe 表示進入pipe模式,将請求交個vcl_pipe函數
error code[reason] 表示傳回'code'給用戶端并放棄請求, 'code'是錯誤标示例如200 405等,'reason'是錯誤原因
vcl_pipe函數:該函數在進入pipe模式時被調用,用于将請求直接傳遞給後端主機,在請求和傳回内容沒有改變的情況下,将不變的内容傳回給用戶端,直到這個連結關閉;該函數有這麼些傳回值=>
error code[reason] 同vcl_recv
pipe
vcl_pass函數: 該函數進入pass模式被調用,用于直接将請求發送給後端主機,後端主機響應後發送給用戶端,不進行任何緩存,每次都傳回最新内容,該函數有幾個傳回值=>
error code[reason] 同vcl_recv
pass
lookup函數: 表示在緩存中查找到被請求的對象,并且根據查找的結果交給vcl_hit函數(命中)或vcl_miss(未命中)函數處理
vcl_hit函數:在執行lookup後,如果在緩存中找到對象,該函數将會被自動調用,該函數有以下幾個傳回值 =>
deliver 表示找到内容發送給用戶端,把控制權交給vcl_deliver函數
error code[reason] 同vcl_recv
pass
vcl_miss函數: 在執行lookup後,在緩存中沒有找到對象,該函數被調用,該函數可用于判斷是否從後端請求内容,該函數有以下幾個傳回值 =>
fetch 表示從後端擷取内容,并把控制權交給vcl_fetch函數
error code[reason] 同vcl_recv
pass
vcl_fetch函數:從後端主機更新緩存擷取内容後調用該函數,接着判斷擷取的内容是放入緩存還是直接給用戶端,該函數有以下幾個傳回值=》
deliver
vcl_deliver函數:将在緩存中找到的内容發送給用戶端調用的方法,該函數有以下幾個傳回值=》
error code[reason] 同vcl_recv
deliver
vcl_timeout函數:在緩存内容到期前調用該函數,該函數有以下幾個傳回值=》
discard 表示中緩存中清除該内容
fetch
vcl_discard函數:緩存内容到期後或者緩存空間不夠時自動被調用,該函數有以下幾個傳回值=》
keep 表示在内容中保留該緩存
discard
VCL處理流程可以用下面這張圖表示
Receive狀态:請求處理的入口狀态,根據VCL規則判斷該請求應該在Pass或Pipe,還是進入Lookup(到本地緩存中查詢)
Lookup狀态:進入此狀态後,會在hash表中查詢資料,如果找到則進入Hit狀态,否則進入Miss狀态
Pass狀态:在此狀态下會進入後端擷取内容,既進入Fetch狀态
Fetch狀态:從後端擷取請求,發送請求,獲得資料,并存儲到本地
Deliver狀态:将擷取的資料發送給用戶端,然後完成本次請求
VCL裡面還有些變量,可以用在VCL函數中,用于邏輯處理
請求到達後可以使用的内置公用變量:
req.backend => 指定對應的後端主機
server.ip => 表示伺服器端IP
client.ip => 表示用戶端IP
req.request => 指定請求的類型,如GET POST PUT DELETE
req.url => 知道請求的URL
req.proto => 表示用戶端發起請求的HTTP協定版本
req.http.header => 表示請求中的頭部資訊
req.restarts => 表示請求重新開機的次數,預設最大值為4
VCL向後端主機請求時使用的内置變量
beresp.request => 指定請求的類型,如GET POST
beresp.url => 指定請求的位址
beresp.proto => 指定請求的協定版本号
beresp.http.header => 對應請求的HTTP頭資訊
beresp.ttl => 表示緩存的周期,機關秒
從cache或後端主機擷取内容後可以使用的内置變量
obj.status => 表示傳回的狀态碼,如200 404 500 等
obj.cacheable => 表示傳回的内容是否可以緩存
obj.valid => 表示是否是有效的HTTP應答
obj.response => 表示應答的狀态資訊
obj.ttl => 表示傳回内容的生存周期,機關秒
obj.lastuse => 表示上次請求到現在的間隔,機關秒
用戶端應答時可以使用的變量
resp.status => 表示傳回給用戶端的狀态碼
resp.proto => 表示傳回給用戶端的HTTP協定版本
resp.http.header => 表示傳回給用戶端的頭資訊
resp.response => 表示傳回給用戶端的狀态資訊
上面可能不全,具體的可以到其官方網站上面檢視
下面筆者有個配置例子,裡面有注釋,可以瞅瞅
#配置兩台後端主機
backend myserver3 {
.host = "192.168.138.3";
.port = "80";
}
#配置兩台後端主機
backend myserver4 {
.host = "192.168.138.4";
.port = "80";
}
#這裡定義一個myserver的director
director myserver round-robin {
{.backend = myserver3;}
{.backend = myserver4;}
}
acl purge {
"localhost";
"127.0.0.1";
}
#recv狀态
sub vcl_recv {
#如果通路 laiwojia.la 或者 www.laiwojia.la 就使用該varnish進行代理 前提是你要在你本地配置laiwojia.la這個虛拟主機奧
if (req.http.host ~ "(www.)?laiwojia.la") {
set req.backend = myserver;
}
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;
}
}
#如果請求的類型不是GET HEAD PUT POST TRACE OPTIONS DELETE 進入pipe模式
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類型的請求 進入到pass模式
if (req.request != "GET" && req.request != "HEAD") {
/* We only deal with GET and HEAD by default */
return (pass);
}
#有認證或者cookie存在 進入到pass模式
if (req.http.Authorization || req.http.Cookie) {
/* Not cacheable by default */
return (pass);
}
#如果URL中含有.php .jsp .do 并且以此結尾或者後面還有?或者# 進入pass模式
if (req.url ~ "\.(php|jsp|do)($|\?|#)") {
return (pass)
}
if (req.request == "PURGE") {
if (!client.ip ~ purge) {
error 405 "Not allowed.";
}
return (lookup);
}
return (lookup);
}
ub vcl_pipe {
# # Note that only the first request to the backend will have
# # X-Forwarded-For set. If you use X-Forwarded-For and want to
# # have it set for all requests, make sure to have:
# # set bereq.http.connection = "close";
# # here. It is not set by default as it might break some broken web
# # applications, like IIS with NTLM authentication.
return (pipe);
}
#
sub vcl_pass {
return (pass);
}
#
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
return (hash);
}
sub vcl_hit {
if (req.request == "PURGE") {
purge;
error 200 "Purged.";
}
return (deliver);
}
#
sub vcl_miss {
if (req.request == "PURGE") {
purge;
error 200 "Purged.";
}
return (fetch);
}
sub vcl_fetch {
#如果請求的類型是GET 請求url以 html png gif jpeg 等等結尾 進行緩存 緩存時間600s
if (req.request == "GET" && req.url ~ "\.(html|png|gif|jpeg|pdf|ppt|doc|docx|rar|zip|bmp|swf|mp3|mp4|rmvb|mov|avi|css|js|jpeg|htm)$") {
set beresp.ttl = 600s;
}
return (deliver);
}
sub vcl_deliver {
#給HTTP頭上加上标示 用于區分是否命中緩存
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT from www.laiwojia.la";
} else {
set resp.http.X-Cache = "MISS from www.laiwojia.la";
}
return (deliver);
}
#當發生錯誤後傳回給用戶端的頁面
sub vcl_error {
set obj.http.Content-Type = "text/html; charset=utf-8";
set obj.http.Retry-After = "5";
synthetic {"
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>"} + obj.status + " " + obj.response + {"</title>
</head>
<body>
<h1>Error "} + obj.status + " " + obj.response + {"</h1>
<p>"} + obj.response + {"</p>
<h3>Guru Meditation:</h3>
<p>XID: "} + req.xid + {"</p>
<hr>
<p>Varnish cache server</p>
</body>
</html>
"};
return (deliver);
}
sub vcl_init {
return (ok);
}
#
sub vcl_fini {
return (ok);
}
好了,上面配置就是這樣的,然後你要在本地加上 www.laiwojia.la laiwojia.la 的解析
vim /etc/hosts
#添加
127.0.0.1 www.laiwojia.la laiwojia.la
千萬不要忘記在192.168.138.3 192.168.138.4 這兩台機器的nginx上配置 laiwojia.la 這個虛拟機奧,這裡筆者貼出配置檔案(nginx.conf 部分)
server{
listen 80;
server_name laiwojia.la;
root /usr/website/html/www.laiwojia.la;
index index.html index.htm index.php;
access_log /usr/website/logs/www.laiwojia.la/access.log;
error_log /usr/website/logs/www.laiwojia.la/errors.log;
}
當然上面的 /usr/website/html/www.laiwojia.la 這個目錄要有 然後在裡面建一個 test.html 裡面寫上内容,然後做好本地解析
vim /etc/hosts
#添加
127.0.0.1 www.laiwojia.la laiwojia.la
好,確定兩台機器上虛拟機運作沒有問題
最後一步:啟動varnish
/usr/local/varnish/sbin/varnishd -f /usr/local/varnish/etc/varnish/default.vcl -s malloc,1G -T 127.0.0.1:2000 -a 0.0.0.0:80
這個是筆者在其官方網站上找到的啟動方法 -f 就是配置檔案 -a 就是IP位址加上端口 我們這裡監聽80端口
OK,一切完結,我們開始測試下
curl -I www.laiwojia.la
激動人心的時刻來了
HTTP/1.1 200 OK
Server: nginx/1.6.2
Content-Type: text/html
Last-Modified: Thu, 28 Jan 2016 11:55:24 GMT
ETag: "56aa01ac-f"
Content-Length: 15
Accept-Ranges: bytes
Date: Thu, 28 Jan 2016 12:27:33 GMT
X-Varnish: 100516502
Age: 0
Via: 1.1 varnish
Connection: keep-alive
X-Cache: MISS from www.laiwojia.la
這下是不是要哭,怎麼是 X-Cache: MISS from www.laiwojia.la 沒有擊中啊,不要擔心,這是第一次,我們再來一次
Date: Thu, 28 Jan 2016 12:27:45 GMT
X-Varnish: 100516503 100516502
Age: 12
X-Cache: HIT from www.laiwojia.la
這下終于是久違的 X-Cache: HIT from www.laiwojia.la
OK,這裡,大體的配置就完了!
是不是有同學要問了,尼瑪這怎麼就叫做動靜分離了?
想想上面的laiwojia.la 這個域名 ,如果是大家的,大家可以把它配置成 img.xxx.com 之類,檔案都上傳到這個域名下面(也可以将叢集裡面的靜态檔案目錄定時複制到這個域名的主機下面),然後大家就可以通過 img.xxx.com 這樣的域名通路了。筆者公司是這樣做的,我們的靜态檔案都放在伺服器 static 目錄裡,然後靜态檔案伺服器叢集每5分鐘複制下這個目錄,同時所有上傳都上傳到靜态檔案伺服器,所有靜态檔案、圖檔引用都使用靜态檔案伺服器字首 img.xxx.com , 為了管理友善,筆者公司裡面的靜态檔案後面都有字尾 '?v=2016012801',隻要更換後面的版本号,緩存就會更新,當然了還有内部機制(有專門清理緩存的接口)。這樣動态檔案在規定的伺服器叢集,靜态檔案也在規定的伺服器叢集,互相分開,大大提升頁面加載速度,提高網站吞吐量。
好了,筆者不才,希望上面這些對大家有所幫助!