WEB快取系統之varnish基礎入門(一)

  前文我們聊了下http協議里的快取控制機制以及varnish架構組件介紹,回顧請參考https://www.cnblogs.com/qiuhom-1874/p/12620538.html;今天我們來聊一下怎樣配置使用varnish;

  前邊我們說到過varnish有兩個配置文件,一個是/etc/varnish/varnish.params,這個配置文件主要是定義varnishd主控進程的一些運行時參數以及定義varnishd監聽在那個套接字上,以及連接varnish使用的密鑰文件;另外一個配置文件是/etc/varnish/default.vcl這個配置文件其實是varnish.params文件中指定的默認快取策略配置文件,這個裡面主要是配置快取相關策略,用varnish專有配置語言vcl寫的配置文件;我們先來了解下varnish.params配置文件吧

[root@test_node1-centos7 ~]# vim /etc/varnish/varnish.params    # Varnish environment configuration description. This was derived from  # the old style sysconfig/defaults settings    # Set this to 1 to make systemd reload try to switch VCL without restart.  RELOAD_VCL=1    # Main configuration file. You probably want to change it.  VARNISH_VCL_CONF=/etc/varnish/default.vcl    # Default address and port to bind to. Blank address means all IPv4  # and IPv6 interfaces, otherwise specify a host name, an IPv4 dotted  # quad, or an IPv6 address in brackets.  # VARNISH_LISTEN_ADDRESS=192.168.1.5  VARNISH_LISTEN_PORT=6081    # Admin interface listen address and port  VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1  VARNISH_ADMIN_LISTEN_PORT=6082    # Shared secret file for admin interface  VARNISH_SECRET_FILE=/etc/varnish/secret    # Backend storage specification, see Storage Types in the varnishd(5)  # man page for details.  VARNISH_STORAGE="malloc,256M"    # User and group for the varnishd worker processes  VARNISH_USER=varnish  VARNISH_GROUP=varnish    # Other options, see the man page varnishd(1)  #DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300"  

  提示:RELOAD_VCL這個參數主要是指定varnish是否支援不重啟切換VCL配置文件,1表示支援,0表示不支援;這裡一般都是設置成1,在工作中千萬不要隨意重啟varnish,一旦重啟好多快取項都將失效,很有可能因為快取失效,前端訪問壓力壓到後端真正的伺服器上,導致伺服器崩潰的情況;VARNISH_VCL_CONF該參數指定快取策略配置文件,默認是default.vcl,我們可以直接編輯這個文件,然後通過VCL編譯編譯成不同的配置名稱的vcl;VARNISH_LISTEN_PORT該參數指定varnishd對外提供訪問的埠,通常情況在前面沒有代理的情況,我們需要把這個埠改成80或者443;VARNISH_ADMIN_LISTEN_ADDRESS該參數是指定varnish的管理介面接聽地址,為了安全通常使用本機迴環地址,防止遠程連接;VARNISH_ADMIN_LISTEN_PORT該參數指定varnish的管理介面監聽的埠,通常這個埠可以不用更改,因為管理介面一般都只有管理員使用;VARNISH_SECRET_FILE該參數指定varnish的管理介面連接所用到認證文件,通常不需要更改;VARNISH_STORAGE該參數指定varnish的快取存儲方式,varnish的快取存儲方式有三種,第一種是malloc記憶體存儲,其配置語法是malloc[,size],這種存儲方式重啟後所有快取項都將失效;第二種是file文件,配置語法file[,path[,size[,granularity]]],通常我們只需要指定文件的路徑及文件大小,這種磁碟文件存儲的方式是黑盒,重啟後所有快取項都將失效;第三種也是磁碟文件的方式存儲,和第二種不同的是這種存儲方式重啟後所有快取項都有效,但是這種存儲方式在varnish4.0還處於試驗階段,所以我們能用的就兩種,一種是記憶體存儲,一種是文件黑盒存儲,這兩種方式都是重啟後所有快取項失效,所以varnish快取伺服器上不能隨意重啟的;VARNISH_USER和VARNISH_GROUP這兩個參數是指定varnishd進程的啟動用戶和組;DAEMON_OPTS是指定varnish運行時參數,每個參數都需要用-p來加以指定,可重複多次使用來指定不同的參數;-r表示死定指定參數為只讀狀態;這裡提示下varnish重載VCL配置文件是直接使用varnish的專用重載命令varnish_reload_vcl命令;了解了varnishd的vcl配置文件,接下來我們修改下對外提供服務端埠,然後嘗試啟動varnish看看;

   提示:因為本機上運行的有httpd把80埠給佔用了,我這裡以8000埠為例對外提供服務;同時我們也給定了varnish的快取是基於文件黑盒存儲方式,並指定其文件大小為500M;

   提示:可以看到varnish對外提供服務的埠已經起來了,但是用瀏覽器訪問8000埠提示503,說後端server沒有找到,這是因為默認情況下varnish指定的後端server是127.0.0.1:8080,我們要配置後端主機server可以在defalult.vcl中配置;如下所示

   提示:以上配置表示默認後端提供web服務的主機地址是127.0.0.1,埠是8080;通常情況我們是需要更改這個配置文件來指定後端主機和埠的,然後重新編譯該配置文件然後載入使用;

   提示::這樣修改配置文件後,需要用varnishadm這個工具連接到varnish提供的命令行介面上去編譯配置文件,然後再載入使用;首先我們來說說varnishadm這個工具怎麼使用吧

[root@test_node1-centos7 ~]# varnishadm --help  varnishadm: invalid option -- '-'  usage: varnishadm [-n ident] [-t timeout] [-S secretfile] -T [address]:port command [...]          -n is mutually exlusive with -S and -T  [root@test_node1-centos7 ~]#  

  提示:以上是varnishadm命令的使用幫助,其實這個命令很好使用,我們只需要用-S(大寫)來指定secret文件,然後用-T來指定varnish主機管理介面監聽的地址和埠即可,當然這樣去連接就是互動式連接,會給我們一個互動式介面輸入命令操作varnish,如果不想互動式使用,在後面可以給命令,有點類似mysql這個工具的用法;接下來我們用varinshadm這個工具來連接下varnish的管理介面;

[root@test_node1-centos7 ~]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082  200  -----------------------------  Varnish Cache CLI 1.0  -----------------------------  Linux,3.10.0-693.el7.x86_64,x86_64,-sfile,-smalloc,-hcritbit  varnish-4.0.5 revision 07eff4c29    Type 'help' for command list.  Type 'quit' to close CLI session.    quit  500  Closing CLI connection  [root@test_node1-centos7 ~]#  

  提示:看到以上介面就表示用varinshadm工具成功連接到varinsh的管理介面上了,輸入quit表示推出管理介面,輸入help表示參看命令列表

[root@test_node1-centos7 ~]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082  200  -----------------------------  Varnish Cache CLI 1.0  -----------------------------  Linux,3.10.0-693.el7.x86_64,x86_64,-sfile,-smalloc,-hcritbit  varnish-4.0.5 revision 07eff4c29    Type 'help' for command list.  Type 'quit' to close CLI session.    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  param.show [-l] [<param>]  param.set <param> <value>  panic.show  panic.clear  storage.list  vcl.show [-v] <configname>  backend.list [<backend_expression>]  backend.set_health <backend_expression> <state>  ban <field> <operator> <arg> [&& <field> <oper> <arg>]...  ban.list  

  提示:用varnishadm工具連接varnish後每執行一個命令就會返回類似http協議中的狀態碼的數字,其中200就表示命令成功執行並返回相應的內容,這個狀態的意思有點類似http里的狀態碼意思;從上面的help列出命令列表來看,它列出了各個命令的基本使用方法,比如hep命令可以直接使用help表示類出命令列表及命令使用格式,help 某個命令表示查看某個命令的使用方法;如下,我們要查看下ping命令的用法可以在命令行介面上敲help ping 

   提示:從上面的幫助資訊,我們可以了解到ping命令的主要作用是看看varnish是否存活,我們在varnish shell中敲ping命令返回200 和PONG 1585892735 1.0就表示varnish主機上存活的;

  繼續上面的話題,我們修改了default.vcl配置文件,我們需要怎麼樣去編譯呢?接續查看命令幫助

   提示:以上表示vcl.load命令的用法和說明,該命令主要是編譯和載入VCL文件,使用方法是vcl.load +配置名稱(這個名稱是我們自定義的,可以說任何合法名稱)+配置文件名稱;如下

   提示:以上就表示編譯default.vcl配置文件,並起名叫test1,我們可以在varnish shell 中敲 vcl.list來查看當前有幾個vcl配置

   提示:可以看到有兩個配置,一個是名字為boot的,其狀態是active表示當前正在使用的,另一個是我們剛才編譯指定的名稱test1,狀態是available表示有效的,可以用的,意思就是我們可以使用vcl.use來切換使用的;接下來我們來看看vcl.use的用法,並嘗試切換我們新編譯的配置;

   提示:vcl.use命令主要用來切換至指定配置名稱的配置;從上面的返回結果看,test1現在處於active的狀態,表示現在varnish應用的是test1的配置;接下來我們就可以在瀏覽器在嘗試訪問varnish對外提供訪問的埠;

   提示:可以看到我們現在訪問8000埠可以正常得到後端httpd伺服器的響應;說明我們配置的後端主機ip和埠沒有問題;同時上面的結果來看,varnish也是一款反向代理服務軟體,通常varnish可以做反向代理,但是它裡面的調度演算法很簡單只有輪詢和加權輪詢,之所以演算法少是因為它的強項不是做反向代理伺服器來用,它的強項是做快取伺服器來用,響應客戶端的請求,很少通過反向代理到後端取資源;

   配置好varnish的後端web主機後,接下來我們來了解下varnish的配置語言VCL的語法

  VCL(varnish configuration lanuage)是“域”專有類型的配置語言,主要用於編寫快取策略的,VCL有多個狀態引擎,狀態之間存在相關性,但狀態引擎彼此互相隔離;每個狀態引擎可使用return(X)指明至那個下一級引擎;每個狀態引擎對應於vcl文件中的一個配置端,即為subroutine,大概處理流程是這樣的,例如vcl_hash –> return(hit) –>vcl_hit;處理過程要看return是什麼,return(hit)就表示下一級處理的subroutine是vcl_hit;

  varnish4.0VCL語法有如下幾點:

  1)VCL文件必須是vcl 4.0;開始

  示例:

   提示:以上除了#號開頭的表示現有配置生效的指令;“#”表示注釋

  2)//和#號和/**/都表示注釋,前兩者表示單行注釋,最後一個表示多行注釋;

  3)subroutines必須要有sub關鍵字指定

  示例:

   提示:這就表示一個subroutine 名字為vcl_recv

  4)沒有循環,受限於引擎的內建變數

  5)用return()函數的參數作為下一個操作的關鍵字來結束語句;用return來實現狀態引擎切換;

  VCL有限狀態機特定:

  1)每項請求分別處理;

  2)每個請求在任何給定時間都是獨立於其他請求的;

  3)狀態是有相關性,但又各自隔離;

  4)return(action);退出一種狀態並指定varnish進入下一種狀態;

  5)內置的VCL程式碼始終存在,並附加在你自己的VCL下面;也就說我們不寫任何VCL程式碼,它默認都有自己內置的VCL程式碼,且這個程式碼始終不變;我們可以用在varnish shell中使用vcl.show -v 指定配置名稱來查看當前生效的配置詳情(默認VCL程式碼+自己寫的VCL配置程式碼),如下

[root@test_node1-centos7 ~]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082  200  -----------------------------  Varnish Cache CLI 1.0  -----------------------------  Linux,3.10.0-693.el7.x86_64,x86_64,-sfile,-smalloc,-hcritbit  varnish-4.0.5 revision 07eff4c29    Type 'help' for command list.  Type 'quit' to close CLI session.      varnish> vcl.list  200  active          0 boot      varnish> vcl.show -v boot  200  // VCL.SHOW 0 1221 input  #  # This is an example VCL file for Varnish.  #  # It does not do anything by default, delegating control to the  # builtin VCL. The builtin VCL is called when there is no explicit  # return statement.  #  # See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/  # and http://varnish-cache.org/trac/wiki/VCLExamples for more examples.    # Marker to tell the VCL compiler that this VCL has been adapted to the  # new 4.0 format.  vcl 4.0;    # Default backend definition. Set this to point to your content server.  backend default {      .host = "192.168.0.99";      .port = "80";  }    sub vcl_recv {      # Happens before we check if we have this in cache already.      #      # Typically you clean up the request here, removing cookies you don't need,      # rewriting the request, etc.  }    sub vcl_backend_response {      # Happens after we have read the response headers from the backend.      #      # Here you clean the response headers, removing silly Set-Cookie headers      # and other mistakes your backend does.  }    sub vcl_deliver {      # Happens when we have all the pieces we need, and are about to send the      # response to the client.      #      # You can do accounting or modifying the final object here.  }    // VCL.SHOW 1 5479 Builtin  /*-   * Copyright (c) 2006 Verdens Gang AS   * Copyright (c) 2006-2014 Varnish Software AS   * All rights reserved.   *   * Author: Poul-Henning Kamp <[email protected]>   *   * Redistribution and use in source and binary forms, with or without   * modification, are permitted provided that the following conditions   * are met:   * 1. Redistributions of source code must retain the above copyright   *    notice, this list of conditions and the following disclaimer.   * 2. Redistributions in binary form must reproduce the above copyright   *    notice, this list of conditions and the following disclaimer in the   *    documentation and/or other materials provided with the distribution.   *   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF   * SUCH DAMAGE.   *     *   * The built-in (previously called default) VCL code.   *   * NB! You do NOT need to copy & paste all of these functions into your   * own vcl code, if you do not provide a definition of one of these   * functions, the compiler will automatically fall back to the default   * code from this file.   *   * This code will be prefixed with a backend declaration built from the   * -b argument.   */    vcl 4.0;    #######################################################################  # Client side      sub vcl_recv {      if (req.method == "PRI") {          /* We do not support SPDY or HTTP/2.0 */          return (synth(405));      }      if (req.method != "GET" &&        req.method != "HEAD" &&        req.method != "PUT" &&        req.method != "POST" &&        req.method != "TRACE" &&        req.method != "OPTIONS" &&        req.method != "DELETE") {          /* Non-RFC2616 or CONNECT which is weird. */          return (pipe);      }        if (req.method != "GET" && req.method != "HEAD") {          /* 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 (hash);  }    sub vcl_pipe {      # By default Connection: close is set on all piped requests, to stop      # connection reuse from sending future requests directly to the      # (potentially) wrong backend. If you do want this to happen, you can undo      # it here.      # unset bereq.http.connection;      return (pipe);  }    sub vcl_pass {      return (fetch);  }    sub vcl_hash {      hash_data(req.url);      if (req.http.host) {          hash_data(req.http.host);      } else {          hash_data(server.ip);      }      return (lookup);  }    sub vcl_purge {      return (synth(200, "Purged"));  }    sub vcl_hit {      if (obj.ttl >= 0s) {          // A pure unadultered hit, deliver it          return (deliver);      }      if (obj.ttl + obj.grace > 0s) {          // Object is in grace, deliver it          // Automatically triggers a background fetch          return (deliver);      }      // fetch & deliver once we get the result      return (fetch);  }    sub vcl_miss {      return (fetch);  }    sub vcl_deliver {      return (deliver);  }    /*   * We can come here "invisibly" with the following errors: 413, 417 & 503   */  sub vcl_synth {      set resp.http.Content-Type = "text/html; charset=utf-8";      set resp.http.Retry-After = "5";      synthetic( {"<!DOCTYPE html>  <html>    <head>      <title>"} + resp.status + " " + resp.reason + {"</title>    </head>    <body>      <h1>Error "} + resp.status + " " + resp.reason + {"</h1>      <p>"} + resp.reason + {"</p>      <h3>Guru Meditation:</h3>      <p>XID: "} + req.xid + {"</p>      <hr>      <p>Varnish cache server</p>    </body>  </html>  "} );      return (deliver);  }    #######################################################################  # Backend Fetch    sub vcl_backend_fetch {      return (fetch);  }    sub vcl_backend_response {      if (beresp.ttl <= 0s ||        beresp.http.Set-Cookie ||        beresp.http.Surrogate-control ~ "no-store" ||        (!beresp.http.Surrogate-Control &&          beresp.http.Cache-Control ~ "no-cache|no-store|private") ||        beresp.http.Vary == "*") {          /*          * Mark as "Hit-For-Pass" for the next 2 minutes          */          set beresp.ttl = 120s;          set beresp.uncacheable = true;      }      return (deliver);  }    sub vcl_backend_error {      set beresp.http.Content-Type = "text/html; charset=utf-8";      set beresp.http.Retry-After = "5";      synthetic( {"<!DOCTYPE html>  <html>    <head>      <title>"} + beresp.status + " " + beresp.reason + {"</title>    </head>    <body>      <h1>Error "} + beresp.status + " " + beresp.reason + {"</h1>      <p>"} + beresp.reason + {"</p>      <h3>Guru Meditation:</h3>      <p>XID: "} + bereq.xid + {"</p>      <hr>      <p>Varnish cache server</p>    </body>  </html>  "} );      return (deliver);  }    #######################################################################  # Housekeeping    sub vcl_init {      return (ok);  }    sub vcl_fini {      return (ok);  }        varnish>  

  提示:以上就是我們沒有寫任何VCL配置程式碼,默認就有的VCL配置程式碼;從上面的配置來看,VCL配置語言主要有三類主要語法

  第一類定義subroutine,主要格式是

sub subroutine {  	...  }  

  第二類是if else條件判斷分支,格式如下

if CONDITION {  	...  } else {  	...  }    

  第三類就是每個subroutine都是需要由return語句結尾,指定下一跳subroutine

  了解了上面的基本語法,我們再來看看VCL的內建函數和關鍵字

  首先說函數吧,regsub(str,regex,sub)這個函數是VCL內置查找替換字串的一個函數,這個函數只會替換第一次匹配的字串,如果後面有多個字串匹配不予處理;regsuball(str,regex,sub)這個函數和上面的那個函數只有一個區別,這個函數是替換所有匹配的字串;ban(boolean expression)該函數用於清理快取項的; hash_data(input)對input做hash計算;synthetic(str)該函數用戶合成字元串,通常用於嵌入其他程式碼用;

  關鍵字:call subroutine,return(action),new,set,unset

  操作符:==, !=, ~, >, >=, <, <=,邏輯操作符&&,||,!,變數賦值=

  內建變數大概有5類,分別是req.*表示由客戶端發來的請求報文相關;如req.http.*就表示請求首部的變數,如req.http.User-Agent就表示引用http請求報文中的User-Agent首部的值;req.http.Referer就表示應用http請求首部Referer的值;bereq.*是有varnish發往後端主機的http請求相關;如bereq.http.*就表示引用發往後端主機的http請求首部的值,同req.http.*的邏輯上一樣的;beresp.*:由BE主機響應給varnish的響應報文相關;resp.*:由varnish響應給client相關;這四類變數都是同一種邏輯,.http.*就表示引用http對應首部的值;obj.*是存儲在快取空間中的快取對象的屬性;

  常用的變數:

bereq.*, req.*:      bereq.http.HEADERS      bereq.request:請求方法;      bereq.url:請求的url;      bereq.proto:請求的協議版本;      bereq.backend:指明要調用的後端主機;        req.http.Cookie:客戶端的請求報文中Cookie首部的值;      req.http.User-Agent ~ "chrome"          beresp.*, resp.*:      beresp.http.HEADERS      beresp.status:響應的狀態碼;      reresp.proto:協議版本;      beresp.backend.name:BE主機的主機名;      beresp.ttl:BE主機響應的內容的餘下的可快取時長;        obj.*      obj.hits:此對象從快取中命中的次數;      obj.ttl:對象的ttl值    server.*      server.ip      server.hostname  client.*      client.ip    

  用戶指定變數用set指令來設置,unset表示刪除之意;

  示例:指定響應首部,如果命中快取就把對應首部的值設置成“HIT via ”+服務端ip地址,沒有命中對應首部的值就是“MISS via” +服務端ip地址

 

   提示:以上這段配置需要寫在vcl_deliver中,vcl_deliver主要是varnish響應客戶端報文都要經由它處理;有點類似iptables里的postrouting;

  測試,在瀏覽器中訪問,看看響應首部X-Cache的值就可以判斷該此請求是否被快取命中;

  第一次訪問,肯定是不會被快取命中,因為壓根就沒有快取,談不上命中,所以第一次訪問X-Cache首部的值應該是”MISS via 192.168.0.99″

   提示:可以看到第一次訪問的確是MISS的,那麼第二次和後面的訪問會不會是miss的呢?

   提示:第二次訪問X-Cache響應首部的值就變成了hit via 192.168.0.99 說明第二次訪問被快取命中了;