天天看點

Varnish緩存服務及應用

一、Varnish的基本介紹

Varnish與一般伺服器軟體類似,就是一個web緩存代理伺服器,分為master(management)程序和child(worker,主要做cache的工作)程序(也叫Cache程序)。master程序讀入指令,進行一些初始化,然後fork并監控child程序。child程序配置設定若幹線程進行工作,主要包括一些管理線程和很多woker線程。

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

   Child程序包含多種類型的線程,常見的如下:

       Acceptor線程:接收新的連接配接請求并響應;

       Worker線程:child程序會為每個會話啟動一個worker線程,是以,在高并發的場景中可能會出現數百個worker線程甚至更多;

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

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

二、Varnish的特性

1、Varnish的穩定性很高,兩者在完成相同負荷的工作時,Squid伺服器發生故障的幾率要高于Varnish,因為使用Squid要經常重新開機;

2、Varnish通路速度更快,因為采用了“Page Cache”技術,所有緩存資料都直接從記憶體讀取(映射),而squid是從硬碟讀取,因而Varnish在通路速度方面會更快;

3、Varnish可以支援更多的并發連接配接,因為Varnish的TCP連接配接釋放要比Squid快,因而在高并發連接配接情況下可以支援更多TCP連接配接;

4、Varnish可以通過管理端口,使用正規表達式批量的清除部分緩存,而Squid是做不到的;

5、squid屬于是單程序使用單核CPU,但Varnish是通過fork形式打開多程序來做處理,是以可以合理的使用所有核來處理相應的請求。

補充知識:Varnish和Squid的不同

Squid是從硬碟讀取緩存的資料,而Varnish是把資料存放在記憶體中,直接從讀取記憶體,避免了頻繁在記憶體、磁盤中交換檔案,是以Varnish要相對更高效,但也有缺點,記憶體中的緩存在伺服器重新開機後會丢失。

 varnish和squid在中小規模的應用上,varnish足夠輕量級,足夠好用,但是在巨大的并發請求來說,單個varnish所能夠承載的并發通路量大概在5000個連接配接請求左右,超出5000個可能就就得不穩定了;而在這裡squid就能表現出良好的性能了,是以在大規模的企業級應用中仍然是 以squid居多,而在中小規模的自己公司的反向代理緩存中varnish居多。

三、VCL的介紹

VCL,Varnish Configuration Language 是varnish配置緩存政策的工具,它是一種基于“域”(可想象與iptables的幾個鍊,也就是類似鈎子函數)的簡單程式設計語言,它支援有限的算術運算和邏輯運算操作、允許使用正規表達式進行字元串比對、允許使用者使用set自定義變量、支援if判斷語句,也有内置的函數和變量等。

使用VCL編寫的緩存政策通常儲存至.vcl檔案中,其需要編譯成二進制的格式後才能由varnish調用。事實上,整個緩存政策就是由幾個特定的子例程如vcl_recv、vcl_hash等組成,它們分别在不同的位置(或時間)執行,如果沒有事先為某個位置自定義子例程,varnish将會執行預設的定義。

VCL的工作方式是基于狀态引擎(state engine)來實作的。

vcl内部有很多state engine,類似于iptables上的鈎子函數,分别作用于不同的邏輯位置,常用的有:

vcl_recv:  當請求封包進來時,會使用這裡的規則去判斷,比如該請求是不是能緩存的類型,是否允許它通路本地的varnish服務等;

vcl_hash:在此處對請求封包中的特定屬性做hash計算,得出用于比對key的特征碼;

vcl_hit :  當緩存命中之後,我們要做什麼操作;

vcl_miss :  緩存miss之後,我們要做什麼操作;

vcl_backend_fetch:  與後端主機通信時,我們要做什麼操作;

vcl_deliver :響應資料給客戶前,我們要做什麼操作,相當于iptables的POSTROUTING鍊;

vcl_pass :  如果我們希望某些封包不要查詢緩存的話,可以直接送到vcl_pass中進行處理;vcl_pass中的封包會直接送往後端主機;

vcl_pipe : 比較特殊的engine,會直接把上遊來的請求封包送往後端伺服器;

vcl_error :  當拒絕使用者通路時,可以直接通過這個engine手動合成一個資訊頁面,告訴使用者拒絕通路原因之類的資訊。使用這個engine的好處是,當我們預先判定使用者的某些請求一定會被拒絕時,我們可以直接在varnish層就把使用者的請求打回去;

vcl_purge :當管理者要手動修剪緩存條目時,我們要執行什麼規則;

這些engine之間之前彼此是有關聯性的,也有先後的邏輯順序,和iptables的 PREROUTING -- > INPUT 這種邏輯很類似。比如vcl_recv基本上是所有engine的上遊,它需要根據制定好的規則,來判斷每個封包的下遊engine是哪一個。

state engine的常見資料流向:

vcl_recv --> lookup --> vcl_hash --> cached --> vcl_hit-->  vcl_deliver 

資料封包進來之後先檢查,如果可緩存,則送進緩存中比對,如果命中了,則直接響應給客戶。

vcl_recv --> lookup --> vcl_hash --> cached --> vcl_miss --> vcl_backend_fetch --> vcl_deliver 

資料封包進來之後先檢查,如果可緩存,則送進緩存中比對,如果沒命中緩存,varnish會替使用者去後端應用伺服器請求對應的頁面,先緩存下來,然後響應給客戶。

vcl_recv --> vcl_pipe --> backendserver

送往vcl_pipe的封包不會做任何處理直接送到後端伺服器。

vcl_recv --> vcl_pass --> vcl_backend_fetch --> vcl_deliver

通常用于不能緩存的内容,不檢查緩存直接送往vcl_pass,經由pass送到後端伺服器去響應

四、Varnish的工作原理及工作流程

上圖說明:

    vcl_recv的結果如果可以查詢緩存并可以識别,那就要到vcl_hash這步了,如果無法識别那就通過pipe(管道)送給vcl_pipe,如果能識别,但不是一個可緩存的對象,那就通過pass送到vcl_pass去,vcl_hash之後就可檢視緩存中有沒有了,有這個請求的對象就表示命中 (vcl_hit),如果沒有那就表示未命中(vcl_miss),如果命中的就可以直接通過deliver直接送給vcl_deliver響應了,如果 未命中就通過fetch交給vcl_fatch去後端伺服器上去取資料,取回資料之後如果資料可以緩存就緩存(cache),本地緩存完之後再建構響應, 如果不可以緩存就不做緩存交給vcl_deliver響應了;而如果命中了交給vcl_pass,交給pass之後就要到Fetch objet  from backend後端伺服器上去取資料了,這是因為這個命中的對象可能是過期或者是要做單獨立額外的處理的;這就是vcl的狀态引擎過程。

五、執行個體

實驗:實作基于Keepalived+Haproxy+Varnish+LNMP企業級架構

一、環境準備:

兩台haproxy(一台master,一台backup,VIP:172.17.253.115)(對varnish實作負載均衡)

一台varnish(IP分别為:192.168.159.139)

兩台後端伺服器(已實作lnmp)(IP分别為:192.168.159.120    192.168.159.121) 

二、安裝步驟:

1、在用作haproxy負載均衡的機器上安裝keepalived和haproxy

  yum install keepalived

  yum install haproxy

2、在用作varnish的伺服器上安裝varnish

  yum install varnish

三、修改配置檔案及啟動服務       

1、對主haproxy操作:主要代碼如下

①vim /etc/keepalived/keepalived.conf  實作高可用

! Configuration File for keepalived

global_defs {

   notification_email {

     root@localhost    #收件人

   }

   notification_email_from [email protected]

   smtp_server 127.0.0.1  #發件的伺服器

   smtp_connect_timeout 30

   router_id LVS_DEVEL2

}

    vrrp_instance VI_1 {

    state MASTER

    interface eth0

    virtual_router_id 14

    priority 100      

    advert_int 1

 authentication {

        auth_type PASS

       auth_pass 111111

    }

    virtual_ipaddress {

        172.17.253.115

啟動服務:systemctl start keepalived

②vim /etc/haproxy/haproxy.cfg  實作負載均衡

global

    log         127.0.0.1 local2

    chroot         /var/lib/haproxy

    pidfile        /var/run/haproxy.pid

    maxconn        4000

    user          haproxy

    group         haproxy

     daemon

     stats socket /var/lib/haproxy/stats

defaults

    mode                   http

    log                    global

    option                  httplog

    option                  dontlognull

    option http-server-close

    option forwardfor           except 127.0.0.0/8

    option                  redispatch

    retries                  3

    timeout http-request         10s

    timeout queue              1m

    timeout connect            10s

    timeout client          1m

    timeout server          1m

    timeout http-keep-alive 10s

    timeout check           10s

     maxconn                  3000

listen stats

        mode http                     #基于http協定

        bind 0.0.0.0:1080               #監聽1080端口

        stats enable                   #開啟統計報告服務

        stats hide-version               #隐藏統計報告版本資訊

        stats uri /haproxyadmin           #統計報告通路url

        stats realm Haproxy\ Statistics     #頁面登陸資訊

        stats auth admin:admin            #驗證賬号資訊

        stats admin if TRUE              #驗證模式

frontend  http-in

     bind *:80  

     default_backend       cache

backend cache

    balance    roundrobin    #負載均衡算法

    server     cache1 192.168.159.139:6081(varnish對應端口)  check

啟動服務:systemctl start haproxy  (打開了80和1080端口)

2、對從haproxy操作:

      ①vim /etc/keepalived/keepalived.conf

          配置基本同上面主的,隻需要修改下面兩行,然後啟動服務

           state BACKUP

           priority 90

      ②vim /etc/haproxy/haproxy.cfg

         配置完全同上面主的,配置完成後啟動服務

3、兩台varnish上進行同樣的操作: 

  ①vim /etc/varnish/default.vcl

     vcl 4.0;  #版本

     import directors; #導入子產品

     probe backend_healthcheck {   #定義健康狀态檢測

        .url = "/index.html";  #檢測的頁面

        .window = 5;        #視窗

        .threshold = 1;      #門檻,1表示至少有一個正常工作

        .interval = 3s;      #檢測頻度

          .timeout = 1s;       #逾時時長

backend web1 {                       #定義後端伺服器

        .host = "192.168.159.120";

        .port = "80";

        .probe = backend_healthcheck;  #健康狀态檢測

backend web2 {                       #定義後端伺服器

        .host = "192.168.159.121";

sub vcl_init {  #初始化

  new web_cluster = directors.round_robin(); #定義後端伺服器組,使用輪詢算法

        web_cluster.add_backend(web1);  #引用上面定義的伺服器

        web_cluster.add_backend(web2);

sub vcl_recv {                 #定義入口函數

     if(req.url ~ "index.php"){  #不緩存index.php頁面,直接跳到pass

           return (pass);

     }

       if(req.method == "GET"){   #請求方法為GET的就緩存

               return (hash); 

        if (req.method != "GET" &&

        req.method != "HEAD" &&

        req.method != "PUT" &&

        req.method != "POST" &&

        req.method != "TRACE" &&

        req.method != "OPTIONS" &&

        req.method != "PURGE" &&

        req.method != "DELETE"){

return (pipe); #如果不屬于以上列出的方法,那麼就通過管道直接傳遞到後端伺服器

     return (hash);

sub vcl_hash{                 #定義hash

        hash_data(req.url);  #對請求的url進行hash處理

sub vcl_backend_response {         #自定義緩存檔案時長,即TTL值

       if(bereq.url ~ "\.(jpg|jpeg|gif|png)$"){

         set beresp.ttl = 30d;  #緩存圖檔30天

        }

       if(bereq.url ~ "\.(html|css|js)$"){

         set beresp.ttl = 7d;   #緩存靜态頁面7天

       return (deliver);

sub vcl_deliver {         #為響應添加首部,顯示緩存是否命中

       if(obj.hits > 0){

          set resp.http.X-Cache = "HIT from " + server.ip; #命中

       else{

          set resp.http.X-Cache = "MISS";            #沒命中

    unset resp.http.Via;   #取消顯示varnish版本号 

②vim /etc/varnish/varnish.params

 VARNISH_STORAGE="file,/var/lib/varnish/bin,1G" #以檔案形式緩存,大小為1G,存在/var/lib/varnish/bin檔案中

 VARNISH_LISTEN_PORT=6081                #監聽端口為6081

 啟動服務:systemctl start varnish (打開了6081端口)

檢視頁面是否生效

六、Varnish指令行工具

Varnish常見工具使用

1>、varnishstat – Varnish Cache statistics 各種計數器

-1 批次顯示,隻顯示1次

-1 -f FILED_NAME

-l:可用于-f選項指定的字段名稱清單;

# varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss

# varnishstat -l -f MAIN -f MEMPOOL

2>、varnishtop – Varnish log entry ranking 将日志檔案中相關資料逆序排序

-1 Instead of a continously updated display, print the statistics once and exit. -i taglist,可以同時使用多個-i選項,也可以一個選項跟上多個标簽;篩選

-I <[taglist:]regex>

-x taglist:排除清單

-X <[taglist:]regex>

varnishtop -i RespStatus 檢視響應碼

3>、varnishlog – Display Varnish logs 檢視實時日志

4>、 varnishncsa – Display Varnish logs in Apache / NCSA combined log format 标準日志格式

七、Varnish的後端存儲

varnish的緩存對象在每次服務重新開機時都會被清空并重建立立,是以這些伺服器都是不應該随便去重新開機的,varnish為了把資料更持久化的存儲,引入了更多的存儲機制,是以varnish支援多種不同的後端存儲;

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

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

   (2)malloc:使用malloc()庫調用在varnish啟動時向作業系統申請指定大小的記憶體空間以存儲緩存對象;

   (3)persistent(experimental):與file的功能相同,但可以持久存儲資料(即重新開機varnish資料時不會被清除);仍處于測試期;

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

八、Varnish檢測後端主機的健康狀态

    Varnish可以檢測後端主機的健康狀态,在判定後端主機失效時能自動将其從可用後端主機清單中移除,而一旦其重新變得可用還可以自動将其設定為可用。為了避免誤判,Varnish在探測後端主機的健康狀态發生轉變時(比如某次探測時某後端主機突然成為不可用狀态),通常需要連續執行幾次探測均為新 狀态才将其标記為轉換後的狀态。

   每個後端伺服器目前探測的健康狀态探測方法通過.probe進行設定,其結果可由req.backend.healthy變量擷取,也可通過varnishlog中的Backend_health檢視或varnishadm的debug.health檢視。

.probe中的探測指令常用的有:

   (1) .url:探測後端主機健康狀态時請求的URL,預設為“/”;

   (2) .request: 探測後端主機健康狀态時所請求内容的詳細格式,定義後,它會替換.url指定的探測方式;比如:

   .request =

       "GET /.healthtest.html HTTP/1.1"

       "Host: www.magedu.com"

       "Connection: close";

   (3) .window:設定在判定後端主機健康狀态時基于最近多少次的探測進行,預設是8;

   (4) .threshold:在.window中指定的次數中,至少有多少次是成功的才判定後端主機正健康運作;預設是3;

   (5) .initial:Varnish啟動時對後端主機至少需要多少次的成功探測,預設同.threshold;

   (6) .expected_response:期望後端主機響應的狀态碼,預設為200;

   (7) .interval:探測請求的發送周期,預設為5秒;

   (8) .timeout:每次探測請求的過期時長,預設為2秒;

例如:

backend server1 {

.host = “172.16.42.3”;

.port = “80”;

.probe = {

.url= “/.healthcheck.html” #得先建立這個測試頁面;

.timeout= 1s;

.interval= 2s;

.window=5;

.threshold=5;

本文轉自 優果馥思 51CTO部落格,原文連結:http://blog.51cto.com/youguofusi/2048118