Varnish緩存部署方式及原理詳解

一、WebCache

   WebCache,web緩存,是一種緩存技術,用於臨時存儲(緩存)的網頁文件,如HTML頁面和圖像等靜態資源(此處不絕對,也可以緩存動態頁面,但是存儲到本地後也爲靜態文件),減少帶寬以及後端服務器的壓力,通常一個WebCache也是一個反向代理軟件,既可以通過緩存響應用戶的請求,當本地沒有緩存時,可以代理用戶請求至後端主機。(自己學習總結,由於昨天剛接觸varnish,所以有錯的地方還請擔待)

   WebCache分爲正向和反向之分,一般正向WebCache不常用,本文以反向WebCache爲主。
   

   WebCache的由來

       由於程序具有局部性,而局部性分爲:時間局部性和空間局部性

            (1)時間局部性是指:在單位時間內,大部分用戶訪問的數據只是熱點數據(熱點數據指經常被訪問的數據)

            (2)空間局部性是指:比如,某新聞網站突然出來一個重大新聞,此新聞會被被反覆訪問。

   WebCache的新鮮度監測機制:數據都是可變的,所以緩存中的內容要做新鮮度檢測

       過期日期

           由於網站是可變的,可能緩存定義的時間在未到達之前,數據就已經發生了改變,這在大部分電商站點上是經常發現的,這個時候我們就不得不對數據做新鮮度檢測,其方式分爲:

           HTTP/1.0:Expires

               例如:expires:Sat, 20 May 2017 07:49:55 GMT 在具體時間到達之前緩存服務器不會去後端服務器請求,但是會有一個問題,不同地區的時間可能不同

           HTTP/1.1:Cache-Control:max-age

               例如:Cache-Control: max-age=600 爲了解決HTTP/1.0中對於新鮮度控制的策略而生,通過相對時間來控制緩存使用期限

       緩存有效性驗證機制

           如果原始內容未發生改變,則僅響應首部(不附帶body部分),響應碼304(Not Modified)

           如果原始內容發生改變,則正常響應,響應碼200

           如果原始內容消失,則響應404,此時緩存中的cache object應被刪除

       條件式請求首部

            If-Modified-Since:基於請求內容的時間戳作驗正,如果後端服務器數據的時間戳未發生改變則繼續使用,反之亦然

            If-None-Match:通過Etag來跟後端服務器進行匹配,如果數據的Etag未發生改變,既不匹配,則響應新數據,否則繼續使用當前數據

  WebCache的緩存控制機制:

   Cache-Control   = "Cache-Control" ":" 1#cache-directive

   cache-directive = cache-request-directive

        | cache-response-directive

   cache-request-directive =    //請求報文中的緩存指令

          "no-cache"                              //不要緩存的實體,要求現在從WEB服務器去取

        | "no-store" (backup)                     //不要緩存,其中可能包括用戶的敏感信息

        | "max-age" "=" delta-seconds             //只接受 Age 值小於 max-age 值,並且沒有過期的對象

        | "max-stale" [ "=" delta-seconds ]       //可以接受過去的對象,但是過期時間必須小於 max-stale 值

        | "min-fresh" "=" delta-seconds           //接受其新鮮生命期大於其當前 Age 跟 min-fresh 值之和的緩存對象          

        | "only-if-cached"                        //只有當緩存中有副本時,客戶端纔會獲取一份副本

        | cache-extension                  

    cache-response-directive =

          "public"                                //可以用 Cached 內容迴應任何用戶

        | "private" [ "=" <"> 1#field-name <"> ]  //只能用緩存內容迴應先前請求該內容的那個用戶

        | "no-cache" [ "=" <"> 1#field-name <"> ] //可以緩存,但是只有在跟WEB服務器驗證了其有效後,才能返回給客戶端

        | "no-store"                              //此內容不允許緩存到緩存服務器上,可能包含用戶的敏感信息

        | "no-transform"                          //未改變              

        | "max-age" "=" delta-seconds             //本響應包含的對象的過期時間

        | "s-maxage" "=" delta-seconds            //本響應包含的對象的過期時間

        | cache-extension

      常見WebCache軟件
   

NameOperating systemForward
               mode
Reverse
               mode
License
UntangleLinuxYesYesProprietary
ApplianSys CACHEboxLinuxYesYesProprietary
aiScaler Dynamic Cache ControlLinuxYesYesProprietary
NginxLinux, BSD variants, OS X, Solaris, AIX, HP-UX, other *nix flavorsNoYes2-clause BSD-like
VarnishLinux, UnixNo (possible with a VMOD)YesBSD
Traffic ServerLinux, UnixYesYesApache License 2.0
SquidLinux, Unix, WindowsYesYesGNU General Public License
Blue Coat ProxySGSGOSYesYesProprietary
WinGateWindowsYesYesProprietary / Free for 3 users
Microsoft Forefront Threat Management GatewayWindowsYesYesProprietary
PolipoWindows, OS X, Linux, OpenWrt, FreeBSDNoYesMIT License
Apache HTTP ServerWindows, OS X, Linux, Unix, FreeBSD, Solaris, Novell NetWare, OS/2, TPF, OpenVMS and eComStationNoYesApache License 2.0

二、Varnish架構

spacer.gifspacer.gifwKiom1dERZDQFiZcAADCJXeWhnc452.png


(1)Managentment管理進程

CLI interface:命令行接口來,目前Web interface爲收費接口,而telnet純文本傳輸,所以只能使用ClI interface.

 managentment主要用於編譯VCL並應用新配置、監控varnish、初始化varnish,並提供一個CLI。

(2)child/cache

   child/cache線程有幾類:

Acceptor:接收新的連接請求;

Worker:用於處理並響應用戶請求;

Expiry:從緩存中清理過期cache object

(3)log

   shared memory log,共享內容日誌方式存儲,一般其大小爲90MB,分爲兩部分:前一部分爲計數器、後一部分爲客戶請求相關的數據    

Varnish支持的後端緩存存儲機制:
   

   malloc[,size]  使用內存緩存機制

       VARNISH_STORAGE="malloc,64M"

   file[,path[,size[,granularity]]] 通過文件方式存儲

       VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}"

   persistent,path,size 前兩者在重啓後緩存都會消失,persistent可以永久保存緩存,但還爲開發階段

Varnish的 state engine:

   vcl配置的緩存策略會在state engine中生效
       

wKiom1dERcaSVUayAACqQzB9yrM143.jpg

上圖爲varnish中緩存控制的規則,每一個request進入vcl_recv後都會被髮往到各state engine上,不同的vcl規則會發往不同的state engine上,我們可以通過vcl規則來控制用戶請求,下面說一些常見的場景。

   未命中緩存時

wKiom1dERlbTGsvjAAA-Oxiwp-w994.jpg

   命中緩存時
   wKioL1dER1KAcOsDAAAtZEKu2wk133.jpg


   直接與後端服務器建立管道
   

wKioL1dER17hH_B0AAApexok4vg010.jpg

   經過vcl_pass交由後端服務器
wKiom1dERnnQrNf2AABCIFgIApE295.jpg

   

三、Varnish安裝配置

環境介紹
   

varnish_servervarnish3.0172.18.4.70CentOS7
backend_serverhttpd+php172.18.4.71CentOS7

zabbix官網的yum倉庫:https://repo.varnish-cache.org/        

安裝:

#yum install varnish gcc -y

配置啓動環境

# vim /etc/sysconfig/varnish
NFILES=131072                                                               //可打開最大的文件數
MEMLOCK=82000                                                               //鎖定的內存空間
RELOAD_VCL=1                                                                //是否在重啓varnish服務時裝載vcl配置文件
VARNISH_VCL_CONF=/etc/varnish/default.vcl                                   //vcl默認讀取配置文件路徑
VARNISH_LISTEN_PORT=80                                                      //varnish 默認監聽端口
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1                                      //varnish管理監聽地址
VARNISH_ADMIN_LISTEN_PORT=6082                                              //varnish管理監聽端口
VARNISH_SECRET_FILE=/etc/varnish/secret                                     //varnish 密鑰文件
VARNISH_MIN_THREADS=50                                                      //最小線程數,varnish進程啓動時啓動多少個線程
VARNISH_MAX_THREADS=1000                                          //最大線程數,一般varnish的總線程數不超過5000(線程池數x最大線程數)
VARNISH_THREAD_TIMEOUT=120                                                  //線程超時時間
VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin                   //varnish緩存文件,varnish將緩存存儲爲單個文件
VARNISH_STORAGE_SIZE=64M                                                     //varnish存儲大小
VARNISH_STORAGE="malloc,${VARNISH_STORAGE_SIZE}"                            //varnish存儲訪方式,內存方式

啓動服務

# systemctl start varnish

四、vcl配置

(1)常見變量
   

1、在任何引擎中均可使用:

now, .host, .port

2、用於處理請求階段:

client.ip, server.hostname, server.ip, server.port

req.request:請求方法

req.url: 請求的URL

req.proto: HTTP協議版本

req.backend: 用於服務此次請求的後端主機;

req.backend.healthy: 後端主機健康狀態;

req.http.HEADER: 引用請求報文中指定的首部;

req.can_gzip:客戶端是否能夠接受gzip壓縮格式的響應內容;

req.restarts: 此請求被重啓的次數;


3、varnish向backend主機發起請求前可用的變量

bereq.request: 請求方法

bereq.url:請求url

bereq.proto:請求協議

bereq.http.HEADER:請求首部

bereq.connect_timeout: 等待與be建立連接的超時時長


4、backend主機的響應報文到達本主機(varnish)後,將其放置於cache中之前可用的變量

beresp.do_stream: 流式響應;

beresp.do_gzip:是否壓縮之後再存入緩存;

beresp.do_gunzip:是否解壓縮之後存入緩存

beresp.http.HEADER:報文首部;

beresp.proto: 協議

beresp.status:響應狀態碼

beresp.response:響應時的原因短語

beresp.ttl:響應對象剩餘的生存時長,單位爲second;

beresp.backend.name: 此響應報文來源backend名稱;

beresp.backend.ip:後端主機ip

beresp.backend.port:後端主機的端口

beresp.storage:


5、緩存對象存入cache之後可用的變量

obj.proto:協議

obj.status:狀態

obj.response:響應報文

obj.ttl:生存週期

obj.hits:命中

obj.http.HEADER:http首部


6、在決定對請求鍵做hash計算時可用的變量

req.hash:將請求交給hash


7、在爲客戶端準備響應報文時可用的變量

resp.proto:協議

resp.status:狀態

resp.response:響應

resp.http.HEADER:http首部

(3)各變量可用的狀態引擎

wKioL1dER77A9FcFAADsP9B3G7w717.png

五、常用示例

爲了便於使用及理解,在介紹實例前,介紹一個varnish的命令行工具:varnishadm

在命令行下,直接敲varnishadm

# varnishadm
200       
-----------------------------
Varnish Cache CLI 1.0
-----------------------------
Linux,2.6.32-573.el6.x86_64,x86_64,-sfile,-smalloc,-hcritbit
varnish-3.0.6 revision 1899836
  
Type 'help' for command list.
Type 'quit' to close CLI session.
  
varnish>

會看到上面的一個界面,可以使用help命令來獲取幫助。

varnish> help
200       
help [command]
ping [timestamp]
auth response
quit
banner
status
start
stop
vcl.load <configname> <filename>
vcl.inline <configname> <quoted_VCLstring>
vcl.use <configname>
vcl.discard <configname>
vcl.list
vcl.show <configname>
param.show [-l] [<param>]
param.set <param> <value>
panic.show
panic.clear
storage.list
backend.list
backend.set_health matcher state
ban.url <regexp>
ban <field> <operator> <arg> [&& <field> <oper> <arg>]...
ban.list

常用的子命令有:

   vcl.list:用於列出當前使用的配置及狀態
   

   vcl.load:用於加載新配置
   

   vcl.show:查看配置中的內容
   

   ping:用來測試varnish是否正常

   vcl.use:切換新配置
   


(1)實例一:添加http首部,讓客戶端可知緩存是否從服務器中得到

修改後端主機:

# vim /etc/varnish/defatult.vcl
backend default {
  .host = "172.18.4.71";
  .port = "80";
}

添加vcl語句:

# vim /etc/varnish/defatult.vcl
sub vcl_deliver {
   
  if (obj.hits > 0) {
    set resp.http.X-Cache = "HIT";
  } else {
    set resp.http.X-Cache = "MISS";
  }
}
 sub vcl_hit {
     return (deliver);
 }


通過命令行工具varnishadm重載配置

varnish> vcl.list
200       
active          0 boot
varnish> vcl.load test1 /etc/varnish/default.vcl
200       
VCL compiled.
varnish> vcl.use test1
200       
varnish> vcl.list
200       
available       0 boot
active          0 test1

訪問並測試

wKioL1dESJby51NoABR9Ey6wCV0216.gif


   
(2)實例二,設置http首部,讓後端主機知道真實客戶端ip地址

修改配置文件

# vim /etc/varnish/default.vcl
sub vcl_recv {
    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;
       }
    }

重載配置

varnish> vcl.load test2 /etc/varnish/default.vcl
200       
VCL compiled.
  
varnish> vcl.use test2
200       
  
  
varnish> vcl.list
200       
available       0 boot
active          0 test2

修改後端web服務器配置文件

# vim /etc/httpd/conf/httpd.conf
    LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
# systemctl reload httpd

訪問並查看httpd日誌

    172.18.250.172 - - [23/May/2016:22:40:07 +0800] "GET / HTTP/1.1" 200 24 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"

並不是varnish:172.18.4.70


       
(3)實例三:移除某個對象

修改配置文件

acl purgers {
    "127.0.0.1";
    "172.18.0.0"/16;
}
   
sub vcl_recv {
    if (req.request == "PURGE") {
        if (!client.ip ~ purgers) {
            error 405 "Method not allowed";
        }
        return (lookup);
    }
}
sub vcl_hit {
    if (req.request == "PURGE") {
        purge;
        error 200 "Purged";
    }
}
sub vcl_miss {
    if (req.request == "PURGE") {
        purge;
        error 404 "Not in cache";
    }
}
sub vcl_pass {
    if (req.request == "PURGE") {
        error 502 "PURGE on a passed object";
    }
}

重載配置


varnish> vcl.load test3 /etc/varnish/default.vcl
200       
VCL compiled.
varnish> vcl.use test3
200

訪問並測試

客戶端在發起HTTP請求時,只需要爲所請求的URL使用PURGE方法即可,其命令使用方式如下:

   # curl -I -X PURGE http://varniship/path/to/someurl

wKiom1dER_7AkaUrAAFGd6ADfKo627.gif


啓用默認vcl_recv默認配置時使用的方式:

    sub vcl_recv {
     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.request == "PURGE" ) {
        if (!client.ip ~ purgers) {
            error 405 "Method not allowed.";
        }
     }
     if (req.request != "GET" &&
       req.request != "HEAD" &&
       req.request != "PUT" &&
       req.request != "POST" &&
       req.request != "TRACE" &&
       req.request != "OPTIONS" &&
       req.request != "DELETE" &&
       req.request != "PURGE" ) {
         /* Non-RFC2616 or CONNECT which is weird. */
         return (pipe);
     }
     if (req.request != "GET" && req.request != "HEAD" && req.request != "PURGE") {
         /* We only deal with GET and HEAD by default */
         return (pass);
     }
     if (req.http.Authorization || req.http.Cookie) {
         /* Not cacheable by default */
         return (pass);
     }
     return (lookup);
}

(4)實例四:控制指定來源地址可訪問的資源

修改配置文件

    # vim /etc/varnish/default.conf
acl admingroup {
   "127.0.0.1";
   "172.18.0.0"/16;
}
if (req.url ~ "login") {
   if (!client.ip ~ admingroup) {
   error 404 "no permission access";
  }
   return (lookup);
}


重載配置

wKioL1dEScbiRO_DAABHxbBL07Y266.jpg

爲了實現效果,這裏我修改配置文件,拒絕本機訪問

# vim /etc/varnish/default.conf
acl admingroup {
   "127.0.0.1";
#   "172.18.0.0"/16;  //註釋此行
}

重載配置並測試

wKiom1dESTfwLUdDAACAbH4YGNg433.png

varnish> vcl.load test5 /etc/varnish/default.vcl
200      
VCL compiled.
  
varnish> vcl.use test5
200


       

(5)實例五:varnish多主機配置

添加配置

backend web1 {
    .host = "172.18.4.71";
    .port = "80";
}
  
director webservers random {
  .retries = 5;
  {
    .backend = web1;
    .weight  = 2;
  }
  {
    .backend  = {
      .host = "172.18.4.72";
      .port = "80";
    }
    .weight         = 3;
  }
}
 sub vcl_recv {
        set req.backend = webservers;
     return (lookup);
 }

多主機配置中,常用算法有兩種:random和round-robin

重載配置

varnish> vcl.load test6 /etc/varnish/default.vcl
200      
VCL compiled.
  
varnish> vcl.use test6
200

訪問測試

varnish> backend.list
200       
Backend name                   Refs   Admin      Probe
default(172.18.4.71,,80)       4      probe      Healthy (no probe)
web1(172.18.4.71,,80)          1      probe      Healthy (no probe)
webservers[1](172.18.4.72,,80) 1      probe      Healthy (no probe)


由於本地是緩存服務器,所以測試效果不是很明顯,所以此次並沒有測試結果
   

(6)實例六:varnish健康狀態檢查

修改配置文件

backend server1 {
  .host = "server1.example.com";
  .probe = {
         .url = "/";
         .interval = 5s;
         .timeout = 1 s;
         .window = 5;
         .threshold = 3;
    }
  }
backend server2 {
   .host = "server2.example.com";
   .probe = {
         .url = "/";
         .interval = 5s;
         .timeout = 1 s;
         .window = 5;
         .threshold = 3;
   }
 }
sub vcl_recv {
     if (req.url ~ "\.php$") {
        set req.backend = web1;
       } else {
        set req.backend = web2;
       }
         return (pass);
     }

重載配置

varnish> vcl.load test8 default.vcl
200       
VCL compiled.
  
varnish> vcl.use test8
200

查看後端狀態
   

varnish> backend.list
200       
Backend name                   Refs   Admin      Probe
web1(172.18.4.71,,80)          1      probe      Healthy 8/8
web2(172.18.4.72,,80)          1      probe      Healthy 8/8

關閉其中一臺,並查看

# systemctl stop httpd
varnish> backend.list
200       
Backend name                   Refs   Admin      Probe
web1(172.18.4.71,,80)          1      probe      Sick 1/8
web2(172.18.4.72,,80)          1      probe      Healthy 8/8
  
varnish> backend.list
200       
Backend name                   Refs   Admin      Probe
web1(172.18.4.71,,80)          1      probe      Sick 0/8
web2(172.18.4.72,,80)          1      probe      Healthy 8/8


(6)生產案例

    backend shopweb {
          .host = "172.18.4.1";
          .port = "80";
        }
    
        acl purge {
          "localhost";
          "127.0.0.1";
          "10.1.0.0"/16;
          "192.168.0.0"/16;
        }
    
        sub vcl_hash {
          hash_data(req.url);
          return (hash);
        }
    
        sub vcl_recv {
          set req.backend = shopweb;
        #  set req.grace = 4h;
          if (req.request == "PURGE") {
            if (!client.ip ~ purge) {
              error 405 "Not allowed.";
            }
            return(lookup);
          }
          if (req.request == "REPURGE") {
            if (!client.ip ~ purge) {
              error 405 "Not allowed.";
            }
            ban("req.http.host == " + req.http.host + " && req.url ~ " + req.url);
            error 200 "Ban OK";
          }
          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.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);
          }
          if (req.request != "GET" && req.request != "HEAD") {
            /* We only deal with GET and HEAD by default */
            return (pass);
          }
          if (req.http.Authorization) {
            /* Not cacheable by default */
            return (pass);
          }
             
    
          if ( req.url == "/Heartbeat.html" ) {
            return (pipe);
          }
          if ( req.url == "/" ) {
            return (pipe);
          }
          if ( req.url == "/index.jsp" ) {
            return (pipe);
          }
    
          if (req.http.Cookie ~ "dper=") {
            return (pass);
          }
          if (req.http.Cookie ~ "sqltrace=") {
            return (pass);
          }
          if (req.http.Cookie ~ "errortrace=") {
            return (pass);
          }
        #   if ( req.request == "GET" && req.url ~ "req.url ~ "^/shop/[0-9]+$" ) {
          if ( req.url ~ "^/shop/[0-9]+$" || req.url ~ "^/shop/[0-9]?.*" ) {
            return (lookup);
          }
    
         if ( req.url ~ "^/shop/(\d{1,})/editmember" || req.url ~ "^/shop/(\d{1,})/map" || req.url ~ "^/shop/(\d+)/dish-([^/]+)" ) {
            return (lookup);
          }
    
          return (pass);
        #   return (lookup);
        }
    
        sub vcl_pipe {
          return (pipe);
        }
    
        sub vcl_pass {
          return (pass);
        }
    
        sub vcl_hit {
          if (req.request == "PURGE") {
            purge;
            error 200 "Purged.";
          }
          return (deliver);
        }
    
        sub vcl_miss {
          if (req.request == "PURGE") {
            error 404 "Not in cache.";
          }
        #   if (object needs ESI processing) {
        #     unset bereq.http.accept-encoding;
        #   }
          return (fetch);
        }
    
    
        sub vcl_fetch {
          set beresp.ttl = 3600s;
          set beresp.http.expires = beresp.ttl;
          #set beresp.grace = 4h;
        #   if (object needs ESI processing) {
        #     set beresp.do_esi = true;
        #     set beresp.do_gzip = true;
        #   }
    
          if ( req.url ~ "^/shop/[0-9]+$" || req.url ~ "^/shop/[0-9]?.*" ) {  
            set beresp.ttl = 4h;
          }
    
         if ( req.url ~ "^/shop/(\d{1,})/editmember" || req.url ~ "^/shop/(\d{1,})/map" || req.url ~ "^/shop/(\d+)/dish-([^/]+)" ) {
             set beresp.ttl = 24h;
          }
    
          if (beresp.status != 200){
            return (hit_for_pass);
          }
          return (deliver);
        }
    
        sub vcl_deliver {
          if (obj.hits > 0){
            set resp.http.X-Cache = "HIT";
          }
          else {
            set resp.http.X-Cache = "MISS";
          }
          set resp.http.X-Powered-By = "Cache on " + server.ip;
          set resp.http.X-Age = resp.http.Age;
          return (deliver);
        }
    
        sub vcl_error {
          set obj.http.Content-Type = "text/html; charset=utf-8";
          set obj.http.Retry-After = "5";
          synthetic {""} + obj.status + " " + obj.response + {""};
          return (deliver);
        }
    
        sub vcl_init {
          return (ok);
        }
    
        sub vcl_fini {
          return (ok);
        }


對於varnish就寫到這裏了,感覺寫的不錯可以給點個贊。 
 作者:Ace QQ1257465991 Linux運維攻城獅一隻 
 Q/A:如有問題請慷慨提出


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章