帶緩存的反向代理服務,Varnish調優手記

jopen 11年前發布 | 23K 次閱讀 Varnish 緩存服務器

一、介紹

  Varnish是一種專業的網站緩存軟件(其實就是帶緩存的反向代理服務),它可以把整個HTTP響應內容緩存到內存或文件中,從而提高Web服務器的響應速度。
  Varnish內置強大的VCL(Varnish Configuration Language)配置語言,允許通過各種條件判斷來靈活調整緩存策略。在程序啟動時,varnish就把VCL轉換成二進制代碼,因此性能非常高。

二、安裝

  epel源里也有varnish,但是卻2.x版本的。
  因為 varnish 3.0的配置文件與 2.x 的存在很大不同,因此varnish團隊不能再更新epel里的軟件源。如果你想安裝最新版本,推薦使用 rpm 方式。

RPM安裝

  在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</pre>

  varnish的安裝和配置路徑 

/etc/varnish/default.vcl  #默認配置文件存文件
/etc/sysconfig/varnish   #服務啟動參數腳本
/etc/init.d/varnish           #服務控制腳本

  可以通過調整 /etc/sysconfig/varnish 配置文件中的參數來調整啟動參數,設置線程池、緩存到內存還是文件等。當然如果你樂意也可以在varnishd后面帶上啟動參數手工啟動服務和管理。 

  現在可以通過服務的方式啟動 varnish了:

service varnish start (stop/restart) 
  將varnish設為開機自啟動:

chkconfig varnish on 

編譯安裝

安裝依賴包

yum install ncurses-devel.x86_64

  此步可選。 

  如果你在編譯varnish 后bin目錄中沒有發現 varnishstat、varnishtop、varnishhist這三個程序的話,就是因為編譯前沒有安裝與操作系統位數對應的 ncurses-devel。這些工具非常好用,因此建議先安裝這個依賴包。

安裝pcre

  varnish 依賴pcre進行url正則匹配。

cd pcre-8.12
./configure --prefix=/usr/local/
make&&make install

編譯

  解壓縮varnish源碼包

wget http://repo.varnish-cache.org/source/varnish-3.0.4.tar.gz
cd /root
tar -zxvf varnish-3.0.4.tar.gz
cd varnish-3.0.4
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
./configure --prefix=/usr/local/varnish
make && make install

三、VCL執行過程

  先介紹一下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
  • </ul>   首次請求時過程如下:
      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; } }</pre>

      其中,在backend 中添加 probe 選項,將可以對后端節點進行健康檢查。如果后端節點無法訪問,將會自動摘除掉該節點,直到這個節點恢復。 

      需要注意window 和threshold 兩個參數。當有后端服務器不可達時,varnish會時不時的報503錯誤。網上查出的資料都是改線程組什么的,經測試完全無效。后來發現,只要將 window 和threshold 兩個參數的值設成一樣的,503現象就再沒有發生了。

    六、優雅模式和神圣模式

    Grace mode

      如果后端需要很長時間來生成一個對象,這里有一個線程堆積的風險。為了避免這 種情況,你可以使用 Grace。他可以讓 varnish 提供一個存在的版本,然后從后端生成新 的目標版本。當同時有多個請求過來的時候,varnish只發送一個請求到后端服務器,在
      set beresp.grace = 30m; 
      時間內復制舊的請求結果給客戶端。

    Saint mode

      有時候,服務器很古怪,他們發出隨機錯誤,您需要通知 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);
    

    }</pre>


    八、管理命令

      跟隨varnish會一起安裝一些方便的調試工具,用好這些工具,對你更好的應用varnish有很大的幫助。

    varnishncsa(以 NCSA 的格式顯示日志) 

      通過這個命令,可以像類似于 nginx/apache一樣的顯示出用戶的訪問日志來。

    varnishlog(varnish詳細日志)

      如果你想跟蹤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  
    • </ul>   例如:
        varnishlog -c -o /auth/login  這個命令將告訴您來自客戶端(-c)的所有包含”/auth/login” 字段(-o)請求。
        varnishlog -c -o ReqStart 192.168.1.100  只跟蹤一個單獨的client請求 

      varnishtop 

          您可以使用varnishtop 確定哪些URL經常被透傳到后端。    
          適當的過濾使用  –I,-i,-X  和-x 選項,它可以按照您的要求顯示請求的內容,客
      戶端,瀏覽器等其他日志里的信息。 
          varnishtop -i rxurl    \您可以看到客戶端請求的 url次數。 
          Varnishtop -i txurl    \您可以看到請求后端服務器的url次數。 
          Varnishtop -i Rxheader -I Accept-Encoding \可以看見接收到的頭信息中有有多少次
      包含Accept-Encoding。 

      varnishstat 

        顯示一個運行varnishd實例的相關統計數據。 
        Varnish 包含很多計數器,請求丟失率,命中率,存儲信息,創建線程,刪除對象等,幾乎所有的操作。通過跟蹤這些計數器,可以很好的了解varnish運行狀態。  

      varnishadm 

        通過命令行,控制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
        此時新的配置文件已經生效!


      來自:http://my.oschina.net/u/572653/blog/178201
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!