Varnish

一、Varnish簡介

    Varnish 的作者Poul-Henning Kamp是FreeBSD的內核開發者之一,他認爲現在的計算機比起1975年已經複雜許多。在1975年時,儲存媒介只有兩種:內存與硬盤。但現在計算機系統的內存除了主存外,還包括了CPU內的L1、L2,甚至有L3快取。硬盤上也有自己的快取裝置,因此Squid Cache自行處理物件替換的架構不可能得知這些情況而做到最佳化,但操作系統可以得知這些情況,所以這部份的工作應該交給操作系統處理,這就是 Varnish cache設計架構。 

    varnish項目是2006年發佈的第一個版本0.9.距今已經八年多了,此文檔之前也提過varnish還不穩定,那是2007年時候編寫的,經過varnish開發團隊和網友們的辛苦耕耘,現在的varnish已經很健壯。很多門戶網站已經部署了varnish,並且反應都很好,甚至反應比squid還穩定,且效率更高,資源佔用更少。相信在反向代理,web加速方面,varnish已經有足夠能力代替squid。



二、Web緩存部分首部介紹

    程序能夠緩存是因爲程序具有局部性,時間局部性和空間局部性。緩存一般存儲爲鍵值,key爲訪問路徑的url經過hash計算後的結果,value爲服務器內容,一般緩存存儲的是熱點數據,切緩存中存儲的數據不會大於後端web服務器的程序數據大小。緩存的對象是有生命週期的,當緩存空間耗盡的時候會根據LRU算法(最近最少使用)或其他算法對緩存空間進行清理。緩存中一般只能緩存非客戶私有數據,客戶私有數據和帶cookie的數據可以進行緩存,但是存在風險,所以一般不對客戶私有數據進行緩存。

    緩存處理的步驟:接收請求 --> 解析請求 (提取請求的URL及各種首部)--> 查詢緩存 --> 新鮮度檢測 --> 創建響應報文 --> 發送響應 --> 記錄日誌(此圖不一定準確,只是爲了好理解)

wKioL1Ysl-yz4WCYAAL8FDdACqQ325.jpg

新鮮度檢測機制:

  過期日期:

    HTTP/1.0 Expires(Expires:Thu, 04 Jun 2015 23:38:18 GMT)

    HTTP/1.1 Cache-Control: max-age(Cache-Control:max-age=600)

  條件式請求首部:

    If-Modified-Since:基於請求內容的時間戳作驗正;

    If-None-Match:基於被請求變量的實體值ETag;ETag是一個可以與Web資源關聯的記號

  有效性再驗正:revalidate

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

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

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


緩存控制機制(Cache-Control):

  請求:

    no-cache:不要給我緩存的內容,要從Web服務器現取的內容

    max-age:只接受Age值小於max-age的值,並且沒有過期的緩存對象

    (Age:當代理服務器用自己緩存的實體去響應請求時,用該頭部表明該實體從產生到現在經過多長時候)

    max-statle:可以接受過期的緩存對象,但是過期時間必須小於此值

    min-fresh:接收其新鮮生命期大於當前Age和min-fresh值之和的緩存對象

  響應:

    public:可以用Cache內容迴應任何用戶

    private:只能緩存內容迴應先前請求該內容的那個用戶

    no-cache:可以緩存,但是只有在跟Web服務器驗證了其有效後,才能返回客戶端

    no-store:不允許緩存



三、Varnish的結構和工作流程

wKioL1YsoXvB_donAAE02BxXtwc916.jpg

①、Management進程

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


②、Child/Cache 進程

Commad line 線程 : 管理接口

Storage/hashing 線程 :完成hash並進行緩存存儲

Log/stats 線程:查看記錄日誌並統計各種狀態

Accept 線程:接收新的連接請求並響應;

Backend Communication 線程:管理後端主機線程,當緩存中沒有緩存需要有此進程交由後端服務器處理

Worker 線程:child進程會爲每個會話啓動一個worker線程,因此,在高併發的場景中可能會出現數百個worker線程甚至更多;

Object Expiry 線程:從緩存中清理過期內容;


③、Varnish日誌

爲了與系統的其它部分進行交互,Child進程使用了可以通過文件系統接口進行訪問的共享內存日誌(shared memory log),因此,如果某線程需要記錄信息,其僅需要持有一個鎖,而後向共享內存中的某內存區域寫入數據,再釋放持有的鎖即可。而爲了減少競爭,每個worker線程都使用了日誌數據緩存。共享內存日誌大小一般爲90M,其分爲兩部分,前一部分爲計數器,後半部分爲客戶端請求的數據。varnish提供了多個不同的工具如varnishlog、varnishncsa或varnishstat等來分析共享內存日誌中的信息並能夠以指定的方式進行顯示。


④、VCL(Varnish Configuation Language)簡介

        Varnish Configuration Language (VCL)是varnish配置緩存策略的工具,它是一種基於“域”(domain specific)的簡單編程語言,它支持有限的算術運算和邏輯運算操作、允許使用正則表達式進行字符串匹配、允許用戶使用set自定義變量、支持if判斷語句,也有內置的函數和變量等。使用VCL編寫的緩存策略通常保存至.vcl文件中,其需要編譯成二進制的格式後才能由varnish調用。事實上,整個緩存策略就是由幾個特定的子例程如vcl_recv、vcl_fetch等組成,它們分別在不同的位置(或時間)執行,如果沒有事先爲某個位置自定義子例程,varnish將會執行默認的定義。

        VCL策略在啓用前,會由management進程將其轉換爲C代碼,而後再由gcc編譯器將C代碼編譯成二進制程序。編譯完成後,management負責將其連接至varnish實例,即child進程。正是由於編譯工作在child進程之外完成,它避免了裝載錯誤格式VCL的風險。因此,varnish修改配置的開銷非常小,其可以同時保有幾份尚在引用的舊版本配置,也能夠讓新的配置即刻生效。編譯後的舊版本配置通常在varnish重啓時纔會被丟棄,如果需要手動清理,則可以使用varnishadm的vcl.discard命令完成。


⑤、Varnish 的後端存儲

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

  • file:使用特定的文件存儲全部的緩存數據,並通過操作系統的mmap()系統調用將整個緩存文件映射至內存區域(如果條件允許);

  • malloc:使用malloc()庫調用在varnish啓動時向操作系統申請指定大小的內存空間以存儲緩存對象;

  • persistent(experimental):與file的功能相同,但可以持久存儲數據(即重啓varnish數據時不會被清除);仍處於測試期;

Varnish無法追蹤某緩存對象是否存入了緩存文件,從而也就無從得知磁盤上的緩存文件是否可用,因此,file存儲方法在varnish停止或重啓時會清除數據。而persistent方法的出現對此有了一個彌補,但persistent仍處於測試階段,例如目前尚無法有效處理要緩存對象總體大小超出緩存空間的情況,所以,其僅適用於有着巨大緩存空間的場景。


   一個Management可以啓動多個child/cache,每個child/cache子進程內部生成多個worker threads響應用戶請求。Varnish是單進程多線程模式的,每個線程處理一個用戶請求,但是不是所有的線程都用於用戶請求,部分線程工作於Backend Communication、Log/stats等


三、Varnish的安裝和配置

這裏使用Centos 7通過epel源安裝Varnish

[root@C7node1 /]# yum install varnish
jemalloc.x86_64 0:3.6.0-1.el7
#jemalloc 一個性能非常卓越的內存分配器,由於C提供的內存分配效率過低,所以這裏使用了更高性能的內存管理器
#用戶malloc內存緩存對象使用
[root@C7node1 /]# rpm -ql varnish
/etc/logrotate.d/varnish                #日誌回滾
/etc/varnish/default.vcl                #varnish的配置文件
/etc/varnish/varnish.params                #varnish的參數,用於配置varnish作爲緩存的工作屬性
/usr/lib/systemd/system/varnishlog.service              #從共享內存中抽取第二段數據日誌
/usr/lib/systemd/system/varnishncsa.service             #從共享內存中抽取第二段數據日誌

配置varnish的三種應用:

①、varnishd應用程序的命令行參數(/etc/varnish/varnish.params)

Varnishd常用選項

    -p param=value            # set parameter。配置參數

    -r param[,param...]        # make parameter read-only。設定只讀參數列表

    -f file            # VCL script,讀取VCL配置文件

    -a address:port        # HTTP listen address and port。指定http地址端口

    -d                  # debug。運行於調試模式    

    -s [name=]kind[,options]     # Backend storage specification。指定存儲類型

        #   -s malloc[,<size>]

        #   -s file,<dir_or_file>,<size>,<granularity>

        #   -s persist{experimental}

    -T address:port           # Telnet listen address and port。指定管理接口

    -S secret-file            # Secret file for CLI authentication。指定管理密鑰文件

    -t                   # Default TTL。前端服務器varnish連接各後端服務器時超時時間

②、varnishd Child/cahce:實時參數(child的進程數,worker的線程數)

    varnishadm

        param.set

③、vcl:配置緩存系統的緩存機制;

    通過vcl配置文件進行配置;先編譯,後應用;依賴於c編譯器;



四、Varnish工具的使用

①、varnishadm

[root@C7node1 varnish]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
help [<command>]                   #查詢幫助
ping [<timestamp>]                  #測試ping通信
auth <response>            
quit                          #退出varnishadm  
status                         #查看Child的狀態
start                          #開啓Child
stop                            #停止Child
vcl.load <configname> <filename>           #編譯加載指定vcl
vcl.inline <configname> <quoted_VCLstring>    
vcl.use <configname>                  #使用指定vcl
vcl.discard <configname>                #刪除指定vcl
vcl.list                        #列出所有vcl
vcl.show <configname>                                    #查看編譯好的vcl的配置文件
param.show [-l] [<param>]               #查看運行參數
param.set <param> <value>               #設置運行參數
panic.show                                               #查看Child恐慌掛掉的信息
panic.clear                                              #清除信息
storage.list                                            #查看使用的storage列表
backend.list [<backend_expression>]                     #查看backed後端服務器
backend.set_health <backend_expression> <state>          #調整backed狀態
ban <field> <operator> <arg> [&& <field> <oper> <arg>]... #清理緩存中的緩存對象
ban.list                                                   #列出定義的ban規則


②、varnishlog

③、varnishncsa

④、varnishtop

⑤、varnishstat



四、VCL配置文件及其相關參數

①、VCL的狀態引擎

    state engine:各引擎之間存一定程度上的相關性;前一個engine如果可以有多種下游engine,則上游engine需要用return指明要轉移的下游engine;

  • vcl_recv:接收客戶請求

  • vcl_hash:當域名做泛解析的時候,對同一類的url做規範化

  • vcl_hit:命中緩存

  • vcl_miss:沒有命中緩存

  • vcl_fetch:去後端服務器請求數據

  • vcl_deliver:投遞給客戶端

  • vcl_pipe:

  • vcl_pass:

  • vcl_error:

  • vcl_backend_fetch(V4新添加的)

  • vcl_backend_response(V4新添加的)

  • vcl_backend_error(V4新添加的)

  • vcl_purge(V4新添加的)

  • vcl_synth(V4新添加的)

wKioL1Y3i47jMwyWAASZ_3FssxE512.jpg


②、VCL語法

  • //、#或/* comment */  用於註釋;會被編譯器忽略

  • sub $name 定義函數 ,sub vcl_recv { }

  • 不支持循環,有衆多內置變量 ,變量的可調用位置與state engine有密切相關性

  • 使用終止語句,return(action),沒有返回值 

  • 域專用 

  • 操作符:=(賦值)、==(等值比較)、~(模式匹配)、!(取反)、&&(邏輯與)、||(邏輯或)

  • 條件判斷語句:if(comment){ }else{ }

  • 變量賦值和撤銷變量:set name=value,unset name



③、varnish中的內置變量:

  • client

        client.ip:客戶端IP地址

  • server

        server.ip:varnish ip地址

        server.hostname:varnish主機名

  • req

   req.http.HEADERS:客戶端發往varnish的請求報文的指定首部

        req.method:請求方法

        req.proto:請求協議版本

        req.ttl:請求ttl

        req.url:請求url

  • resp

        resp.http.HEADERS:varnish發往客戶端的響應報文的指定首部

        resp.proto:響應協議版本

        resp.reason:響應的原因短語 

        resp.status:響應的狀態碼   

  • bereq

        bereq.http.HEADERS: 由varnish發往backend server的請求報文的指定首部;

        bereq.request:請求方法;

        bereq.url:請求的url

        bereq.proto:請求的協議版本

        bereq.backend:指明要調用的後端主機;

  • beresp

        beresp.proto:又backend server響應的協議版本

        beresp.status:後端服務器的響應的狀態碼

        beresp.reason:原因短語;

        beresp.backend.ip:後端服務器的IP地址

        beresp.backend.name:後端服務器的主機名

        beresp.http.HEADER: 從backend server響應的報文的首部;

        beresp.ttl:後端服務器響應的內容的餘下的生存時長;

  • obj

        obj.ttl: 對象的ttl值;

        obj.hits:此對象從緩存中命中的次數;

  • storage


wKiom1Y3dm6Ae-ShAADegg0zUso684.jpg

官方文檔:https://www.varnish-cache.org/docs/4.0/reference/vcl.html#varnish-configuration-language


④、varnish中的內置方法:

    return():終止vcl引擎

    hash_data(input):進行hash計算並放回對應的hash值

    regsub(str, regex, sub):正則表達式查找替換,之查找替換一次

    regsuball(str, regex, sub):正則表達式查找替換,替換所有

    ban(expression):緩存對象清理



四、實驗總結

c7node1.wlw.com    192.168.0.56    Varnish服務器

C6node1.wlw.com    192.168.0.66    backend server 

C6node2.wlw.com    192.168.0.76    backend server 



定義在vcl_deliver中,向響應給客戶端的報文添加一個自定義首部X-Cache

[root@C7node1 varnish]# vim /etc/varnish/wlw.vcl
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_deliver {
    if(obj.hits > 0) {
        set resp.http.X-Cache = "HIT";

    }else{
        set resp.http.X-Cache = "Miss";
    }
}    

[root@C7node1 varnish]# varnishadm
vcl.load wlw wlw.vcl
200        
VCL compiled.
vcl.list 
200        
active          0 boot
available       0 wlw
vcl.use wlw
200        
VCL 'wlw' now active

[root@wlw ~]# curl -I http://192.168.0.56
X-Cache: Miss
[root@wlw ~]# curl -I http://192.168.0.56
X-Cache: HIT


讓Varnish支持虛擬主機

sub vcl_recv {
    if (req.http.host == "www.wlw.com") {
        return (pipe);
    }
}
sub vcl_pipe{
    set bereq.http.host = "www.wlw.com";
}


強制對某資源的請求,不檢查緩存

sub vcl_recv {
    if (req.url ~ "^/test1.html$") {
        return(pass);
    }
}
[root@C6node1 dz]# curl -I http://192.168.0.56/test1.html
HTTP/1.1 200 OK
X-Cache: Miss
[root@C6node1 dz]# curl -I http://192.168.0.56/test1.html
HTTP/1.1 200 OK
X-Cache: Miss
#可以看到多次請求都沒有命中緩存

sub vcl_recv {
    if (req.url ~ "(?i)^/login" || req.url ~ "(?i)^/admin") {
        return(pass);
    }
}
#對/login或者/admin的目錄都不查詢緩存,(?i) 表示對正則匹配不區分大小寫


對特定類型的資源(公共可用)取消其私有的cookie標識,並強行設定其可以varnish緩存的時長

sub vcl_backend_response {
    if (beresp.http.cache-control !~ "s-maxage") {
        if (bereq.url ~ "(?i)\.jpg$") {
            set beresp.ttl = 3600s;
            unset beresp.http.Set-Cookie;
        }       
        if (bereq.url ~ "(?i)\.css$") {
            set beresp.ttl = 600s;
            unset beresp.http.Set-Cookie;               
        }       
    }
}
#veresp.ttl爲設置在varnish上的緩存時長



定義backend server和後端主機的健康狀態檢測,並實現資源分別分配

backend websrv1 {
    .host = "192.168.0.66";
    .port = "80"; 
    .probe = {
        .url = "/test5.html";
    }
}

backend websrv2 {
    .host = "192.168.0.76";
    .port = "80"; 
    .probe = {
        .url = "/test5.html";
    }
}

sub vcl_recv {
    if (req.url ~ "(?i)\.(jpg|png|gif)$") {
        set req.backend_hint = websrv1;
    } else {
        set req.backend_hint = websrv2;
    }                        
}
#定義兩臺後端主機

測試查看結果

backend.list 
Backend name                   Refs   Admin      Probe
websrv1(192.168.0.66,,80)      1      probe      Healthy 8/8
websrv2(192.168.0.76,,80)      1      probe      Sick 0/8
backend.list
200        
Backend name                   Refs   Admin      Probe
websrv1(192.168.0.66,,80)      1      probe      Healthy 8/8
websrv2(192.168.0.76,,80)      1      probe      Healthy 6/8
#這裏因爲192.168.0.76忘記關閉防火牆導致請求test5.html檢測失敗,關閉防火牆後檢測通過並恢復



backend server的定義:

backend name {

    .attribute = "value";

}


.host: BE主機的IP;

.port:BE主機監聽的PORT;

.probe: 對BE做健康狀態檢測;

.max_connections:並連接最大數量;


後端主機的健康狀態檢測方式:

probe name {

     .attribute = "value";

}


.url: 判定BE健康與否要請求的url; 

.request:手動定義請求報文的內容

.expected_response:期望響應狀態碼;默認爲200;

.timeout:健康狀態檢測的超時時間,默認2s

.interval:每隔多長時間檢測一次,默認5s

.window:最近檢測的窗口,默認爲8

.threshold:默認爲3,只要在window定義的次數中有threshold次通過即爲正常

.initial:當Varnish主機剛啓動的時候檢測後端主機多少次成功即爲正常,默認爲1



基於Varnish的負載均衡

backend websrv1 {
    .host = "192.168.0.66";
    .port = "80";
    .probe = {
        .url = "/test5.html";
    }
}

backend websrv2 {
    .host = "192.168.0.76";
    .port = "80";
    .probe = {
        .url = "/test5.html";
    }
}

import directors;

sub vcl_init {
    new mycluster = directors.round_robin();
    mycluster.add_backend(websrv1);
    mycluster.add_backend(websrv2);
}

sub vcl_recv {
    if (req.url ~ "(?i)\.(jpg|png|gif)$") {
        set req.backend_hint = websrv1;
    } else {
        set req.backend_hint = websrv2;
    }
    set req.backend_hint = mycluster.backend();
}

[root@C6node1 dz]# curl http://192.168.0.56/test1.html
Page 1 Web2
[root@C6node1 dz]# curl http://192.168.0.56/test2.html
Page 2 Web1
[root@C6node1 dz]# curl http://192.168.0.56/test3.html
Page 3 Web2
[root@C6node1 dz]# curl http://192.168.0.56/test4.html
Page 4 Web1
#基於資源的負載均衡,負載均衡算法:fallback, random, round_robin, hash



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