Varnish 4全面解析

簡介

Varnish 是一款高性能且開源的反向代理服務器和 HTTP 加速器,其採用全新的軟件體系機構,和現在的硬件體系緊密配合,與傳統的 squid 相比,varnish 具有性能更高、速度更快、管理更加方便等諸多優點;

目前最新版本是4.0.0,而3.x的版本也是可以生產環境下使用的穩定版本,但yum源中的2.x版本過於陳舊,不建議使用;


Varnish與Squid的對比

相同點

  • 都是一個反向代理服務器;

  • 都是開源軟件;

Varnish的優勢

  • Varnish的穩定性很高,兩者在完成相同負荷的工作時,Squid服務器發生故障的機率要高於Varnish,因爲使用Squid要經常重啓;

  • Varnish訪問速度更快,因爲採用了“Visual Page Cache”技術,所有緩存數據都直接從內存讀取,而squid是從硬盤讀取,因而Varnish在訪問速度方面會更快;

  • Varnish可以支持更多的併發連接,因爲Varnish的TCP連接釋放要比Squid快,因而在高併發連接情況下可以支持更多TCP連接;

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

  • squid屬於是單進程使用單核CPU,但Varnish是通過fork形式打開多進程來做處理,所以可以合理的使用所有核來處理相應的請求;

Varnish的劣勢

  • varnish進程一旦Hang、Crash或者重啓,緩存數據都會從內存中完全釋放,此時所有請求都會發送到後端服務器,在高併發情況下,會給後端服務器造成很大壓力;

  • 在varnish使用中如果單個url的請求通過HA/F5等負載均衡,則每次請求落在不同的varnish服務器中,造成請求都會被穿透到後端;而且同樣的請求在多臺服務器上緩存,也會造成varnish的緩存的資源浪費,造成性能下降;

Varnish劣勢的解決方案

  • 針對劣勢一:在訪問量很大的情況下推薦使用varnish的內存緩存方式啓動,而且後面需要跟多臺squid服務器。主要爲了防止前面的varnish服務、服務器被重啓的情況下,大量請求穿透varnish,這樣squid可以就擔當第二層CACHE,而且也彌補了varnish緩存在內存中重啓都會釋放的問題;

  • 針對劣勢二:可以在負載均衡上做url哈希,讓單個url請求固定請求到一臺varnish服務器上;


對比Varnish 3.x的主要改進

  • 完全支持流對象;

  • 可後臺獲取失效的對象,即Client/backend分離;

  • 新的vanishlog查詢語言,允許對請求進行自動分組;

  • 複雜的請求時間戳和字節計數;

  • 安全方面的提升;

涉及VCL語法的改變點

  • vcl配置文件需明確指定版本:即在vcl文件的第一行寫上 vcl 4.0;

  • vcl_fetch函數被vcl_backend_response代替,且req.*不再適用vcl_backend_response;

  • 後端源服務器組director成爲varnish模塊,需import directors後再在vcl_init子例程中定義;

  • 自定義的子例程(即一個sub)不能以vcl_開頭,調用使用call sub_name;

  • error()函數被synth()替代;

  • return(lookup)被return(hash)替代;

  • 使用beresp.uncacheable創建hit_for_pss對象;

  • 變量req.backend.healty被std.healthy(req.backend)替代;

  • 變量req.backend被req.backend_hint替代;

  • 關鍵字remove被unset替代;

詳見:https://www.varnish-cache.org/docs/4.0/whats-new/index.html#whats-new-index


架構及文件緩存的工作流程

wKioL1NqU5PinnraAAMzb1EkLvA629.jpg

  • Varnish 分爲 master 進程和 child 進程;

  • Master 進程讀入存儲配置文件,調用合適的存儲類型,然後創建 / 讀入相應大小的緩存文件,接着 master 初始化管理該存儲空間的結構體,然後 fork 並監控 child 進程;

  • Child 進程在主線程的初始化的過程中,將前面打開的存儲文件整個 mmap 到內存中,此時創建並初始化空閒結構體,掛到存儲管理結構體,以待分配;

  • 對外管理接口分爲3種,分別是命令行接口、Telnet接口和Web接口;

  • 同時在運行過程中修改的配置,可以由VCL編譯器編譯成C語言,並組織成共享對象(Shared Object)交由Child進程加載使用;

wKioL1NqU9GBqFx9AAGUf5BMeEI252.jpg

Child 進程分配若干線程進行工作,主要包括一些管理線程和很多 worker 線程,可分爲:

  • Accept線程:接受請求,將請求掛在overflow隊列上;

  • Work線程:有多個,負責從overflow隊列上摘除請求,對請求進行處理,直到完成,然後處理下一個請求;

  • Epoll線程:一個請求處理稱爲一個session,在session週期內,處理完請求後,會交給Epoll處理,監聽是否還有事件發生;

  • Expire線程:對於緩存的object,根據過期時間,組織成二叉堆,該線程週期檢查該堆的根,處理過期的文件,對過期的數據進行刪除或重取操作;


HTTP請求基本處理流程

wKiom1NqVHSRED_tAAK8GTftr28587.jpg

Varnish 處理 HTTP 請求的過程如下

  1. Receive 狀態(vcl_recv):也就是請求處理的入口狀態,根據 VCL 規則判斷該請求應該 pass(vcl_pass)或是 pipe(vcl_pipe),還是進入 lookup(本地查詢);

  2. Lookup 狀態:進入該狀態後,會在 hash 表中查找數據,若找到,則進入 hit(vcl_hit)狀態,否則進入 miss(vcl_miss)狀態;

  3. Pass(vcl_pass)狀態:在此狀態下,會直接進入後端請求,即進入 fetch(vcl_fetch)狀態;

  4. Fetch(vcl_fetch)狀態:在 fetch 狀態下,對請求進行後端獲取,發送請求,獲得數據,並根據設置進行本地存儲;

  5. Deliver(vcl_deliver)狀態:將獲取到的數據發給客戶端,然後完成本次請求;

注:Varnish4中在vcl_fetch部分略有出入,已獨立爲vcl_backend_fetch和vcl_backend_response2個函數;

內置函數(也叫子例程)

  • vcl_recv:用於接收和處理請求;當請求到達併成功接收後被調用,通過判斷請求的數據來決定如何處理請求;

  • vcl_pipe:此函數在進入pipe模式時被調用,用於將請求直接傳遞至後端主機,並將後端響應原樣返回客戶端;

  • vcl_pass:此函數在進入pass模式時被調用,用於將請求直接傳遞至後端主機,但後端主機的響應並不緩存直接返回客戶端;

  • vcl_hit:在執行 lookup 指令後,在緩存中找到請求的內容後將自動調用該函數;

  • vcl_miss:在執行 lookup 指令後,在緩存中沒有找到請求的內容時自動調用該方法,此函數可用於判斷是否需要從後端服務器獲取內容;

  • vcl_hash:在vcl_recv調用後爲請求創建一個hash值時,調用此函數;此hash值將作爲varnish中搜索緩存對象的key;

  • vcl_purge:pruge操作執行後調用此函數,可用於構建一個響應;

  • vcl_deliver:將在緩存中找到請求的內容發送給客戶端前調用此方法;

  • vcl_backend_fetch:向後端主機發送請求前,調用此函數,可修改發往後端的請求;

  • vcl_backend_response:獲得後端主機的響應後,可調用此函數;

  • vcl_backend_error:當從後端主機獲取源文件失敗時,調用此函數;

  • vcl_init:VCL加載時調用此函數,經常用於初始化varnish模塊(VMODs)

  • vcl_fini:當所有請求都離開當前VCL,且當前VCL被棄用時,調用此函數,經常用於清理varnish模塊;


VCL中內置公共變量

變量(也叫object)適用範圍

wKioL1NqVKGwmVZcAAQKey0HSFg359.jpg

注:某些地方略有出入,詳細可參考官方文檔;

變量類型詳解

wKiom1NqVQeTaaG0AAH4p4fuFPM755.jpg

  • req:The request object,請求到達時可用的變量

  • bereq:The backend request object,向後端主機請求時可用的變量

  • beresp:The backend response object,從後端主機獲取內容時可用的變量

  • resp:The HTTP response object,對客戶端響應時可用的變量

  • obj:存儲在內存中時對象屬性相關的可用的變量

具體變量詳見:https://www.varnish-cache.org/docs/4.0/reference/vcl.html#reference-vcl


優雅模式(Garce mode)

Varnish中的請求合併

當幾個客戶端請求同一個頁面的時候,varnish只發送一個請求到後端服務器,然後讓其他幾個請求掛起並等待返回結果;獲得結果後,其它請求再複製後端的結果發送給客戶端;

但如果同時有數以千計的請求,那麼這個等待隊列將變得龐大,這將導致2類潛在問題:

  • 驚羣問題(thundering herd problem),即突然釋放大量的線程去複製後端返回的結果,將導致負載急速上升;

  • 沒有用戶喜歡等待;

故爲了解決這類問題,可以配置varnish在緩存對象因超時失效後再保留一段時間,以給那些等待的請求返回過去的文件內容(stale content),配置案例如下:

sub vcl_recv {
if (! req.backend.healthy) {
set req.grace = 5m;
} else {
set req.grace = 15s;
}
}
sub vcl_fetch {
set beresp.grace = 30m;
}
# 以上配置表示varnish將會將失效的緩存對象再多保留30分鐘,此值等於最大的req.grace值即可;
# 而根據後端主機的健康狀況,varnish可向前端請求分別提供5分鐘內或15秒內的過期內容;


安裝配置

# 安裝包下載地址:http://repo.varnish-cache.org/redhat/varnish-4.0/el6/
yum localinstall --nogpgcheck varnish-4.0.0-1.el6.x86_64.rpm varnish-libs-4.0.0-1.el6.x86_64.rpm varnish-docs-4.0.0-1.el6.x86_64.rpm
vi /etc/sysconfig/varnish # 編輯配置文件,修改如下項
VARNISH_STORAGE_SIZE=100M # 此值根據自身情況調整,測試環境可調低此值
VARNISH_STORAGE="malloc,${VARNISH_STORAGE_SIZE}" # Varnish 4中默認使用malloc(即內存)作爲緩存對象存儲方式;
service varnish start # 啓動varnish,默認外部請求的監聽端口6081,管理端口6082,後端主機127.0.0.1:80
===========
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 # 登錄管理命令行
varnish> vcl.list                 # 列出所有的配置
varnish> vcl.load test1 ./test.vcl  # 加載編譯新配置,test1是配置名,test.vcl是配置文件
varnish> vcl.use test1            # 使用配置,需指定配置名,當前使用的配置以最後一次vcl.use爲準
varnish> vcl.show test1           # 顯示配置內容,需指定配置名

實例解析

#
# 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;
import directors;    # 導入directors模塊;
probe backend_healthcheck {    # 創建健康監測機制;
    .url = "/health.html";
    .window = 5;
    .threshold = 2;
    .interval = 3s;
}
backend web1 {    # 創建後端主機;
    .host = "192.168.0.25";
    .port = "80";
    .probe = backend_healthcheck;
}
backend web2 {
    .host = "192.168.0.35";
    .port = "80";
    .probe = backend_healthcheck;
}
backend img1 {
    .host = "192.168.0.25";
    .port = "4040";
    .probe = backend_healthcheck;
}
backend img2 {
    .host = "192.168.0.35";
    .port = "4040";
    .probe = backend_healthcheck;
}
sub vcl_init {    # 創建後端主機組,即directors;
    new web_cluster = directors.random();
    web_cluster.add_backend(web1,1.0);
    web_cluster.add_backend(web2,1.0);
    new img_cluster = directors.random();
    img_cluster.add_backend(img1,1.0);
    img_cluster.add_backend(img2,1.0);
}
acl purgers {    # 定義可訪問來源IP;
        "127.0.0.1";
        "192.168.0.0"/24;
}
sub vcl_recv {
    if (req.method == "GET" && req.http.cookie) {    # 帶cookie首部的GET請求也緩存;
        return(hash);
    }
    if (req.url ~ "test.html") {    # test.html文件禁止緩存;
        return(pass);
    }
    if (req.method == "PURGE") {    # PURGE請求的處理;
        if (!client.ip ~ purgers) {
            return(synth(405,"Method not allowed"));
        }
        return(hash);
    }
    if (req.http.X-Forward-For) {    # 爲發往後端主機的請求添加X-Forward-For首部;
        set req.http.X-Forward-For = req.http.X-Forward-For + "," + client.ip;
    } else {
        set req.http.X-Forward-For = client.ip;
    }
    if (req.http.host ~ "(?i)^(www.)?lnmmp.com$") {    # 根據不同的訪問域名,分發至不同的後端主機組;
            set req.http.host = "www.lnmmp.com";
            set req.backend_hint = web_cluster.backend();
    } elsif (req.http.host ~ "(?i)^images.lnmmp.com$") {
            set req.backend_hint = img_cluster.backend();
    }
    return(hash);
}
sub vcl_hit {
    if (req.method == "PURGE") {    # PURGE請求的處理;
        purge;
        return(synth(200,"Purged"));
    }
}
sub vcl_miss {
    if (req.method == "PURGE") {    # PURGE請求的處理;
        purge;
        return(synth(404,"Not in cache"));
    }
}
sub vcl_pass {
    if (req.method == "PURGE") {    # PURGE請求的處理;
        return(synth(502,"PURGE on a passed object"));
    }
}
sub vcl_backend_response {     # 自定義緩存文件的緩存時長,即TTL值;
        if (bereq.url ~ "\.(jpg|jpeg|gif|png)$") {
                set beresp.ttl = 7200s;
        }
        if (bereq.url ~ "\.(html|css|js)$") {
                set beresp.ttl = 1200s;
        }
    if (beresp.http.Set-Cookie) {    # 定義帶Set-Cookie首部的後端響應不緩存,直接返回給客戶端;
        return(deliver);
    }
}
sub vcl_deliver {
    if (obj.hits > 0) {    # 爲響應添加X-Cache首部,顯示緩存是否命中;
        set resp.http.X-Cache = "HIT from " + server.ip;
    } else {
        set resp.http.X-Cache = "MISS";
    }
}


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