天天看點

Varnish原理和配置詳解

Varnish 是一款高性能且開源的反向代理伺服器和 HTTP 加速器,其采用全新的軟體體系機構,和現在的硬體體系緊密配合,與傳統的squid 相比,varnish 具有性能更高、速度更快、管理更加友善等諸多優點,很多大型的網站都開始嘗試使用 varnish 來替換 squid,這些都促進varnish 迅速發展起來。

varnish主要運作兩個程序:Management程序和Child程序(也叫Cache程序)。它們的工作原理大緻如下圖:

<a href="http://guoting.blog.51cto.com/attachment/201409/25/8886857_1411663522CPX9.png" target="_blank"></a>

Management程序主要實作應用新的配置、編譯VCL、監控varnish、初始化varnish以及提供一個指令行接口等。Management程序會每隔幾秒鐘探測一下Child程序以判斷其是否正常運作,如果在指定的時長内未得到Child程序的回應,Management将會重新開機此Child程序。

Child程序包含多種類型的線程,常見的如:Acceptor線程:接收新的連接配接請求并響應;Worker線程:child程序會為每個會話啟動一個worker線程,是以,在高并發的場景中可能會出現數百個worker線程甚至更多;Expiry線程:從緩存中清理過期内容;

Varnish依賴“工作區(workspace)”以降低線程在申請或修改記憶體時出現競争的可能性。在varnish内部有多種不同的工作區,其中最關鍵的當屬用于管理會話資料的session工作區。

為了與系統的其它部分進行互動,Child程序使用了可以通過檔案系統接口進行通路的共享記憶體日志(shared memory log),是以,如果某線程需要記錄資訊,其僅需要持有一個鎖,而後向共享記憶體中的某記憶體區域寫入資料,再釋放持有的鎖即可。而為了減少競争,每個worker線程都使用了日志資料緩存。

共享記憶體日志大小一般為90M,其分為兩部分,前一部分為計數器,後半部分為用戶端請求的資料。varnish提供了多個不同的工具如varnishlog、varnishncsa或varnishstat等來分析共享記憶體日志中的資訊并能夠以指定的方式進行顯示。

varnish支援多種不同類型的後端存儲,這可以在varnishd啟動時使用-s選項指定。後端存儲的類型包括:

(1)file: 使用特定的檔案存儲全部的緩存資料,并通過作業系統的mmap()系統調用将整個緩存檔案映射至記憶體區域(如果條件允許);

(2)malloc: 使用malloc()庫調用在varnish啟動時向作業系統申請指定大小的記憶體空間以存儲緩存對象,類似于C語言中的malloc動态申請函數;

file和malloc存儲方法類似,重新開機緩存服務時,先前緩存的資料不複存在。

說道varnish的狀态引擎,不得不說vcl(Varnish Configuration Language:varnish配置緩存政策的工具)。它是基于域的一種簡單的程式設計語言,支援算數運算、允許使用正規表達式、支援if語句等。使用vcl語言編寫的緩存政策通常儲存于.vcl檔案中,其需要編譯成二進制的格式後才能由varnish調用。

VCL用于讓管理者定義緩存政策,而定義好的政策将由varnish的management程序分析、轉換成C代碼、編譯成二進制程式并連接配接至child程序。varnish内部有幾個所謂的狀态(state),在這些狀态上可以附加通過VCL定義的政策以完成相應的緩存處理機制,是以VCL也經常被稱作“域專用”語言或狀态引擎,“域專用”指的是有些資料僅出現于特定的狀态中。

具體的狀态的是通過定義内置函數來實作的,具體過程如下圖:

<a href="http://guoting.blog.51cto.com/attachment/201409/25/8886857_1411663525qP7d.png" target="_blank"></a>

1

<code>&lt;span style=</code><code>"font-size:16px;"</code><code>&gt;vcl各狀态引擎的功用:&lt;br&gt;vcl_recv:是在Varnish完成對請求封包的解碼為基本資料結構後第一個要執行的子例程&lt;br&gt;vcl_fetch:根據伺服器端的響應作出緩存決策&lt;br&gt;vcl_pipe:用于将請求直接發往後端主機;&lt;br&gt;vcl_hash:自定義</code><code>hash</code><code>生成時的資料來源&lt;br&gt;vcl_pass:用于将請求直接傳遞至後端主機;&lt;br&gt;vcl_hit:從緩存中查找到緩存對象時要執行的操作;&lt;br&gt;vcl_miss:從緩存中款查找到緩存對象時要執行的操作;&lt;br&gt;vcl_deliver:将使用者請求的内容響應給用戶端時用到的方法; &lt;br&gt;vcl_error:在varnish端合成錯誤響應而時;&lt;br&gt;&lt;</code><code>/span</code><code>&gt;</code>

緩存相關的HTTP首部:

<code>&lt;span style=</code><code>"font-size:16px;"</code><code>&gt;HTTP協定提供了多個首部用以實作頁面緩存及緩存失效的相關功能,這其中最常用的有:&lt;br&gt;(1)Expires:用于指定某web對象的過期日期/時間,通常為GMT格式;一般不應該将此設定的未來過&lt;br&gt;   長的時間,一年的長度對大多場景來說足矣;其常用于為純靜态内容如JavaScripts樣式表或圖檔&lt;br&gt;   指定緩存周期;&lt;br&gt;(2)Cache-Control:用于定義所有的緩存機制都必須遵循的緩存訓示,這些訓示是一些特定的指令,&lt;br&gt;   包括public、private、no-cache(表示可以存儲,但在重新驗正其有效性之前不能用于響應用戶端&lt;br&gt;   請求)、no-store、max-age、s-maxage以及must-revalidate等;Cache-Control中設定的時間會覆&lt;br&gt;   蓋Expires中指定的時間;&lt;br&gt;(3)Etag:響應首部,用于在響應封包中為某web資源定義版本辨別符;&lt;br&gt;(4)Last-Mofified:響應首部,用于回應用戶端關于Last-Modified-Since或If-None-Match首部的請&lt;br&gt;  求,以通知用戶端其請求的web對象最近的修改時間;&lt;br&gt;(5)If-Modified-Since:條件式請求首部,如果在此首部指定的時間後其請求的web内容發生了更改,&lt;br&gt;   則伺服器響應更改後的内容,否則,則響應304(not modified);&lt;br&gt;(6)If-None-Match:條件式請求首部;web伺服器為某web内容定義了Etag首部,用戶端請求時能擷取&lt;br&gt;  并儲存這個首部的值(即标簽);而後在後續的請求中會通過If-None-Match首部附加其認可的标簽列&lt;br&gt;  表并讓伺服器端檢驗其原始内容是否有可以與此清單中的某标簽比對的标簽;如果有,則響應304,&lt;br&gt;  否則,則傳回原始内容;&lt;br&gt;(7)Vary:響應首部,原始伺服器根據請求來源的不同響應的可能會有所不同的首部,最常用的是&lt;br&gt;   Vary: Accept-Encoding,用于通知緩存機制其内容看起來可能不同于使用者請求時&lt;br&gt;   Accept-Encoding-header首部辨別的編碼格式;&lt;br&gt;(8)Age:緩存伺服器可以發送的一個額外的響應首部,用于指定響應的有效期限;浏覽器通常根據此&lt;br&gt;   首部決定内容的緩存時長;如果響應封包首部還使用了max-age指令,那麼緩存的有效時長為&lt;br&gt;   “max-age減去Age”的結果;&lt;br&gt;&lt;</code><code>/span</code><code>&gt;</code>

<code>&lt;span style=</code><code>"font-size:16px;"</code><code>&gt;</code><code># rpm包下載下傳位址:https://repo.varnish-cache.org/&lt;br&gt;rpm -ivh varnish-3.0.5-1.el6.x86_64.rpm varnish-libs-3.0.5-1.el6.x86_64.rpm \&lt;br&gt;  varnish-docs-3.0.5-1.el6.x86_64.rpm&lt;br&gt;&lt;/span&gt;</code>

<a href="http://guoting.blog.51cto.com/attachment/201409/25/8886857_1411663529K5h3.png" target="_blank"></a>

修改varnish的相關參數:

<code>&lt;span style=</code><code>"font-size:16px;"</code><code>&gt;</code><code>## /etc/sysconfig/varnish 的有效參數如下:&lt;br&gt;NFILES=131072&lt;br&gt;MEMLOCK=82000&lt;br&gt;NPROCS="unlimited"&lt;br&gt;RELOAD_VCL=1&lt;br&gt;VARNISH_VCL_CONF=/etc/varnish/default.vcl&lt;br&gt;VARNISH_LISTEN_PORT=80           # 一般修改varnish的監聽端口&lt;br&gt;VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1&lt;br&gt;VARNISH_ADMIN_LISTEN_PORT=6082&lt;br&gt;VARNISH_SECRET_FILE=/etc/varnish/secret&lt;br&gt;VARNISH_MIN_THREADS=50&lt;br&gt;VARNISH_MAX_THREADS=1000&lt;br&gt;VARNISH_THREAD_TIMEOUT=120&lt;br&gt;VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin&lt;br&gt;VARNISH_STORAGE_SIZE=1G&lt;br&gt;VARNISH_STORAGE_MEM_SIZE=128M       # 使用malloc存儲方法時的記憶體大小&lt;br&gt;VARNISH_STORAGE="malloc,${VARNISH_STORAGE_MEM_SIZE}"    # 配置後端存儲方法為malloc&lt;br&gt;VARNISH_TTL=120&lt;br&gt;DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \&lt;br&gt;             -f ${VARNISH_VCL_CONF} \&lt;br&gt;             -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \&lt;br&gt;             -t ${VARNISH_TTL} \&lt;br&gt;             -w ${VARNISH_MIN_THREADS},${VARNISH_MAX_THREADS},${VARNISH_THREAD_TIMEOUT} \&lt;br&gt;             -u varnish -g varnish \&lt;br&gt;             -S ${VARNISH_SECRET_FILE} \&lt;br&gt;             -s ${VARNISH_STORAGE}"&lt;br&gt;&lt;/span&gt;</code>

配置vcl的相關參數:

<code>&lt;span style=</code><code>"font-size:16px;"</code><code>&gt;</code><code># 修改 /etc/varnish/default.vcl 中以下内容:&lt;br&gt;backend default {&lt;br&gt;  .host = "172.16.10.11";&lt;br&gt;  .port = "80";&lt;br&gt;}&lt;br&gt;&lt;/span&gt;</code>

<a href="http://guoting.blog.51cto.com/attachment/201409/25/8886857_1411663531bFYf.png" target="_blank"></a>

此時,可以通過varnishstat檢視緩存命中情況。

<a href="http://guoting.blog.51cto.com/attachment/201409/25/8886857_1411663532Y0oT.png" target="_blank"></a>

也可以自行定義緩存政策:

<code>&lt;span style=</code><code>"font-size:16px;"</code><code>&gt;Varnish内置變量:&lt;br&gt;請求到達時可用的内置變量:&lt;br&gt;    req.url&lt;br&gt;    req.request&lt;br&gt;    req.http.HEADER&lt;br&gt;    req.restarts: 請求被重新開機的次數;&lt;br&gt;    server.ip&lt;br&gt;    server.port&lt;br&gt;    server.</code><code>hostname</code><code>&lt;br&gt;    client.ip&lt;br&gt;    req.backend &lt;br&gt;&lt;br&gt;向後後端主機請求時可用的内置變量&lt;br&gt;    bereq.url&lt;br&gt;    bereq.request&lt;br&gt;    bereq.http.HEADER&lt;br&gt;    bereq.connect_timeout&lt;br&gt;    bereq.proto &lt;br&gt;&lt;br&gt;從後端主機擷取到響應的object時可用的内置變量&lt;br&gt;    beresp.status&lt;br&gt;    beresp.response&lt;br&gt;    beresp.http.HEADER&lt;br&gt;    beresp.ttl&lt;br&gt;    beresp.backend.name&lt;br&gt;    beresp.backend.ip&lt;br&gt;    beresp.backend.port &lt;br&gt;&lt;br&gt;緩存對象進入緩存時可用的内置變量(隻能用于vcl_hit或vcl_error,且大多為隻讀)&lt;br&gt;    obj.status&lt;br&gt;    obj.response&lt;br&gt;    obj.ttl&lt;br&gt;    obj.hits&lt;br&gt;    obj.http.HEADER &lt;br&gt;&lt;br&gt;響應給用戶端時可用的内置變量&lt;br&gt;    resp.proto&lt;br&gt;    resp.status&lt;br&gt;    resp.response&lt;br&gt;    resp.http.HEADER&lt;br&gt;&lt;</code><code>/span</code><code>&gt;</code>

自定義web.vcl,内容如下:

<code>&lt;span style=</code><code>"font-size:16px;"</code><code>&gt;[root@varnish ~]</code><code># cat /etc/varnish/web.vcl &lt;br&gt;# 定義後端主機,并提供健康狀态檢測&lt;br&gt;backend web1 {&lt;br&gt;  .host = "172.16.10.11";&lt;br&gt;  .probe = {&lt;br&gt;   .url="/index.html";&lt;br&gt; .interval=2s;&lt;br&gt;   .window=8;&lt;br&gt;  .threshold=2;&lt;br&gt;  }  &lt;br&gt;}&lt;br&gt;backend web2 {&lt;br&gt;  .host = "172.16.10.16";&lt;br&gt;  .probe = {&lt;br&gt;        .url="/index.html";&lt;br&gt;        .interval=2s;&lt;br&gt;        .window=8;&lt;br&gt;        .threshold=2;&lt;br&gt;  }&lt;br&gt;}&lt;br&gt;&lt;br&gt;director websrv round-robin {&lt;br&gt; { .backend = web1; }&lt;br&gt;    { .backend = web2; }&lt;br&gt;}&lt;br&gt;&lt;br&gt;# 定義一個acl 目的是進行緩存裁剪&lt;br&gt;acl purgers {&lt;br&gt;   "127.0.0.1";&lt;br&gt;    "172.16.0.0"/16;&lt;br&gt;}&lt;br&gt;sub vcl_recv {&lt;br&gt;   set req.backend = websrv;&lt;br&gt;    if (req.restarts == 0) {&lt;br&gt;       if (req.http.x-forwarded-for) {&lt;br&gt;        set req.http.X-Forwarded-For =&lt;br&gt;       req.http.X-Forwarded-For + ", " + client.ip;&lt;br&gt;      } else {&lt;br&gt;           set req.http.X-Forwarded-For = client.ip;&lt;br&gt;     }&lt;br&gt; }&lt;br&gt;   # 定義那些内容不緩存的&lt;br&gt;   if (req.url ~ "^/test.html$") {&lt;br&gt;      return(pass);&lt;br&gt;   }&lt;br&gt;   if (req.request == "PURGE") {&lt;br&gt;       if (!client.ip ~ purgers) {&lt;br&gt;         error 405 "Method not allowed";&lt;br&gt;      }&lt;br&gt; return (lookup);&lt;br&gt;   }&lt;br&gt;     if (req.request != "GET" &amp;&amp;&lt;br&gt;       req.request != "HEAD" &amp;&amp;&lt;br&gt;       req.request != "PUT" &amp;&amp;&lt;br&gt;       req.request != "POST" &amp;&amp;&lt;br&gt;       req.request != "TRACE" &amp;&amp;&lt;br&gt;       req.request != "OPTIONS" &amp;&amp;&lt;br&gt;       req.request != "DELETE") {&lt;br&gt;         /* Non-RFC2616 or CONNECT which is weird. */&lt;br&gt;         return (pipe);&lt;br&gt;     }&lt;br&gt;     if (req.request != "GET" &amp;&amp; req.request != "HEAD") {&lt;br&gt;         /* We only deal with GET and HEAD by default */&lt;br&gt;         return (pass);&lt;br&gt;     }&lt;br&gt;     if (req.http.Authorization || req.http.Cookie) {&lt;br&gt;         /* Not cacheable by default */&lt;br&gt;         return (pass);&lt;br&gt;     }&lt;br&gt;     return (lookup);&lt;br&gt;}&lt;br&gt;sub vcl_pipe {&lt;br&gt;    return (pipe);&lt;br&gt;}&lt;br&gt;# sub vcl_hash {&lt;br&gt;#     hash_data(req.url);&lt;br&gt;#     if (req.http.host) {&lt;br&gt;#         hash_data(req.http.host);&lt;br&gt;#     } else {&lt;br&gt;#         hash_data(server.ip);&lt;br&gt;#     }&lt;br&gt;#     return (hash);&lt;br&gt;# }&lt;br&gt;&lt;br&gt;sub vcl_hit {&lt;br&gt;        if (req.request == "PURGE") {&lt;br&gt;                purge;&lt;br&gt;                error 200 "Purged";&lt;br&gt;        }&lt;br&gt;     return (deliver);&lt;br&gt;} &lt;br&gt;&lt;br&gt;sub vcl_miss {&lt;br&gt;        if (req.request == "PURGE") { &lt;br&gt;                purge;&lt;br&gt;                error 404 "Not in cache";&lt;br&gt;        }&lt;br&gt;     return (fetch);&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;br&gt;sub vcl_pass {&lt;br&gt;        if (req.request == "PURGE") {&lt;br&gt;                error 502 "PURGE on a passed object";&lt;br&gt;        }&lt;br&gt;     return (pass);&lt;br&gt;} &lt;br&gt;# &lt;br&gt;# sub vcl_fetch {&lt;br&gt;#     if (beresp.ttl &lt;= 0s ||&lt;br&gt;#         beresp.http.Set-Cookie ||&lt;br&gt;#         beresp.http.Vary == "*") {&lt;br&gt;#        /*&lt;br&gt;#         * Mark as "Hit-For-Pass" for the next 2 minutes&lt;br&gt;#          */&lt;br&gt;#      set beresp.ttl = 120 s;&lt;br&gt;#       return (hit_for_pass);&lt;br&gt;#     }&lt;br&gt;#     return (deliver);&lt;br&gt;# }&lt;br&gt;&lt;br&gt;&lt;br&gt;sub vcl_deliver {&lt;br&gt;        set resp.http.X-Age = resp.http.Age;&lt;br&gt;        unset resp.http.Age;&lt;br&gt;&lt;br&gt;        if (obj.hits &gt; 0) {&lt;br&gt;                set resp.http.X-Cache = "HIT Via" + " " + server.hostname;&lt;br&gt;        } else {&lt;br&gt;                set resp.http.X-Cache = "MISS Via" + " " + server.hostname;&lt;br&gt;        }&lt;br&gt; return (deliver);&lt;br&gt;}&lt;br&gt;# sub vcl_deliver {&lt;br&gt;#&lt;br&gt;#&lt;br&gt;#     return (deliver);&lt;br&gt;# }&lt;br&gt;# &lt;br&gt;# sub vcl_error {&lt;br&gt;#     set obj.http.Content-Type = "text/html; charset=utf-8";&lt;br&gt;#     set obj.http.Retry-After = "5";&lt;br&gt;#     synthetic {"&lt;br&gt;# &lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;br&gt;# &lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br&gt;#  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;&lt;br&gt;# &lt;html&gt;&lt;br&gt;#   &lt;head&gt;&lt;br&gt;#     &lt;title&gt;"} + obj.status + " " + obj.response + {"&lt;/title&gt;&lt;br&gt;#   &lt;/head&gt;&lt;br&gt;#   &lt;body&gt;&lt;br&gt;#     &lt;h1&gt;Error "} + obj.status + " " + obj.response + {"&lt;/h1&gt;&lt;br&gt;#"} + obj.response + {"#     Guru Meditation:&lt;br&gt;#XID: "} + req.xid + {"#     &lt;hr&gt;&lt;br&gt;#Varnish cache server#   &lt;/body&gt;&lt;br&gt;# &lt;/html&gt;&lt;br&gt;# "};&lt;br&gt;#     return (deliver);&lt;br&gt;# }&lt;br&gt;# &lt;br&gt;sub vcl_init {&lt;br&gt;  return (ok);&lt;br&gt;}&lt;br&gt;&lt;br&gt;sub vcl_fini {&lt;br&gt;  return (ok);&lt;br&gt;}&lt;br&gt;&lt;/span&gt;</code>

<a href="http://guoting.blog.51cto.com/attachment/201409/25/8886857_1411663534Tfdb.png" target="_blank"></a>

<a href="http://guoting.blog.51cto.com/attachment/201409/25/8886857_1411663536kra7.png" target="_blank"></a>

<a href="http://guoting.blog.51cto.com/attachment/201409/25/8886857_1411663537nUsK.png" target="_blank"></a>

<a href="http://guoting.blog.51cto.com/attachment/201409/25/8886857_1411663539bsMI.png" target="_blank"></a>

vcl自動編譯的方法,編輯 /etc/sysconfig/varnish 檔案中的:

VARNISH_VCL_CONF=/etc/varnish/web.vcl

本文轉自 羊木狼 51CTO部落格,原文連結:http://blog.51cto.com/guoting/1558305,如需轉載請自行聯系原作者