Varnish與web架構實踐
一、前言
? 我國的互聯網處于飛速發展時期,網民隊伍飛速擴大,舉個栗子,家里的小學生跟爺爺一代都已經成為了使用智能手機獲取互聯網資源的新興勢力。龐大的網民群體必然要求主流或者想成為主流的互聯網企業具備承載海量并發訪問的能力,但是單純增加原始內容服務器數量去應對海量并發并不是有性價比的解決方案,因此各類緩存技術應運而生且誕生了一個口號“緩存為王”。
二、http協議緩存原理
? http協議緩存機制是指通過HTTP協議頭里的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段來控制文件緩存的機制。緩存會根據請求保存輸出內容的副本,例如html頁面,圖片,文件,當下一個請求來到的時候:如果是相同的URL,緩存直接使用副本響應訪問請求,而不是向源服務器再次發送請求。
常用首部:
-
Expires:指示響應內容過期的時間,格林威治時間GMT。
-
Cache-Control:用于更精細地控制 本地緩存的相關設置。
-
-
比如Cache-Control:max-age=600表示文件在本地緩存,且有效時長是600秒(從發出請求算起)。在接下來600秒內,如果有請求這個資源,瀏覽器不會發出HTTP請求,而是直接使用本地緩存的文件(強緩存)。
-
所以判斷緩存是否過期步驟是:
-
查看是否有cache-control 的max-age / s-maxage , s-maxage(share cache即public緩存最大保留時間)如果有,則用服務器時間date值 + max-age/s-maxage 的秒數計算出新的過期時間,將當前時間與過期時間進行比較,判斷是否過期。?
-
如果沒有cache-control 的max-age / s-maxage,則用expires 作為過期時間比較。
-
-
-
Last-Modified:標識文件在服務器上的最后一次更新時間。收到請求時,如果文件緩存過期,瀏覽器通過If-Modified-Since字段帶上這個時間,發送給服務器,由服務器比較時間戳來判斷文件是否有修改。如果沒有修改,服務器返回304告訴瀏覽器繼續使用緩存(協商緩存);如果有修改,則返回200,同時返回最新的文件。
-
Etag:web服務器響應請求時,告訴瀏覽器當前資源在服務器的唯一標識(生成規則由服務器決定)。Apache中,ETag的值,默認是對文件的索引節(INode),大?。⊿ize)和最后修改時間(MTime)進行Hash后得到的。為什么要使用Etag主要是為了解決以下問題:
-
如果某些文件會被定期生成,有時內容并沒有任何變化,但Last-Modified卻改變了,導致緩存未命中。
-
Last-Modified標注的最后修改只能精確到秒級,如果某些文件在1秒鐘以內,被修改多次的話,它將不能準確標注文件的修改時間。
-
Last-Modified與ETag是可以一起使用的,服務器會優先驗證ETag,一致的情況下,才會繼續比對Last-Modified,最后才決定是否返回304。
-
-
no-cache :并不是說限定的資源不能緩存,而是要求瀏覽器必須發出條件式請求進行緩存有效性驗證,所以不能結合expires使用。
-
no-store :所限定的資源不允許緩存
三、Varnish簡介
? Varnish是一款高性能的輕量級開源web cache加速器,常見的緩存技術有Varnish與Squid,兩者的關系類似于Nginx與Httpd。與Squid 相比,Varnish 具有性能更高、速度更快、管理更加方便等諸多優點 。
-
varnish系統架構
varnish主要運行兩個進程:Management進程和Child進程(也叫Cache進程)。
Management進程主要實現應用新的配置、編譯VCL、監控varnish、初始化varnish以及提供一個命令行接口等。Management進程會每隔幾秒鐘探測一下Child進程以判斷其是否正常運行,如果在指定的時長內未得到Child進程的回應,Management將會重啟此Child進程。
Child進程包含多種類型的線程,常見的如:Acceptor線程:接收新的連接請求并響應;Worker線程:child進程會為每個會話啟動一個worker線程,因此,在高并發的場景中可能會出現數百個worker線程甚至更多;Expiry線程:從緩存中清理過期內容;
Varnish依賴“工作區(workspace)”以降低線程在申請或修改內存時出現競爭的可能性。在varnish內部有多種不同的工作區,其中最關鍵的當屬用于管理會話數據的session工作區。
-
varnish日志
為了與系統的其它部分進行交互,Child進程使用了可以通過文件系統接口進行訪問的共享內存日志(shared memory log),因此,如果某線程需要記錄信息,其僅需要持有一個鎖,而后向共享內存中的某內存區域寫入數據,再釋放持有的鎖即可。而為了減少競爭,每個worker線程都使用了日志數據緩存。
共享內存日志大小一般為90M,其分為兩部分,前一部分為計數器,后半部分為客戶端請求的數據。varnish提供了多個不同的工具如varnishlog、varnishncsa或varnishstat等來分析共享內存日志中的信息并能夠以指定的方式進行顯示。
-
VCL
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停止或重啟時會清除數據。
選擇使用合適的存儲方式有助于提升系統性,從經驗的角度來看,建議在內存空間足以存儲所有的緩存對象時使用malloc的方法,反之,file存儲將有著更好的性能的表現。然而,需要注意的是,varnishd實際上使用的空間比使用-s選項指定的緩存空間更大,一般說來,其需要為每個緩存對象多使用差不多1K左右的存儲空間,這意味著,對于100萬個緩存對象的場景來說,其使用的緩存空間將超出指定大小1G左右。另外,為了保存數據結構等,varnish自身也會占去不小的內存空間。
為varnishd指定使用的緩存類型時,-s選項可接受的參數格式如下:???
-
malloc[,size]
-
file[,path[,size[,granularity]]]? #granularity用于設定緩存空間分配單位,默認單位是字節,所有其它的大小都會被圓整。
-
persistent,path,size {experimental}???
-
四、Varnish狀態引擎(state engine)
VCL用于讓管理員定義緩存策略,而定義好的策略將由varnish的management進程分析、轉換成C代碼、編譯成二進制程序并連接至child進程。varnish內部有幾個所謂的狀態(state),在這些狀態上可以附加通過VCL定義的策略以完成相應的緩存處理機制,因此VCL也經常被稱作“域專用”語言或狀態引擎,“域專用”指的是有些數據僅出現于特定的狀態中。
-
VCL狀態引擎
在VCL狀態引擎中,狀態之間具有相關性,但彼此間互相隔離,每個引擎使用return(x)來退出當前狀態并指示varnish進入下一個狀態。
varnish開始處理一個請求時,首先需要分析HTTP請求本身,比如從首部獲取請求方法、驗正其是否為一個合法的HTT請求等。當這些基本分析結束后就需要做出第一個決策,即varnish是否從緩存中查找請求的資源。這個決定的實現則需要由VCL來完成,簡單來說,要由vcl_recv方法來完成。如果管理員沒有自定義vcl_recv函數,varnish將會執行默認的vcl_recv函數。然而,即便管理員自定義了vcl_recv,但如果沒有為自定義的vcl_recv函數指定其終止操作(terminating),其仍將執行默認的vcl_recv函數。事實上,varnish官方強烈建議讓varnish執行默認的vcl_recv以便處理自定義vcl_recv函數中的可能出現的漏洞。
-
VCL語法
VCL的設計參考了C和Perl語言,因此,對有著C或Perl編程經驗者來說,其非常易于理解。其基本語法說明如下:
-
//、#或/* comment */用于注釋
-
sub $name 定義函數
-
不支持循環,有內置變量
-
使用終止語句,沒有返回值
-
域專用
-
操作符:=(賦值)、==(等值比較)、~(模式匹配)、!(取反)、&&(邏輯與)、||(邏輯或)?
VCL的函數不接受參數并且沒有返回值,因此,其并非真正意義上的函數,這也限定了VCL內部的數據傳遞只能隱藏在HTTP首部內部進行。VCL的return語句用于將控制權從VCL狀態引擎返回給Varnish,而非默認函數,這就是為什么VCL只有終止語句而沒有返回值的原因。同時,對于每個“域”來說,可以定義一個或多個終止語句,以告訴Varnish下一步采取何種操作,如查詢緩存或不查詢緩存等。
-
-
VCL的內置函數
VCL提供了幾個函數來實現字符串的修改,添加bans,重啟VCL狀態引擎以及將控制權轉回Varnish等。
-
regsub(str,regex,sub)|regsuball(str,regex,sub) :這兩個用于基于正則表達式搜索指定的字符串并將其替換為指定的字符串;但regsuball()可以將str中能夠被regex匹配到的字符串統統替換為sub,regsub()只替換一次;
-
ban(expression)|ban_url(regex): 禁用所有其URL能夠由regex匹配的緩存對象;
-
purge:從緩存中挑選出某對象以及其相關變種一并刪除,這可以通過HTTP協議的PURGE方法完成;
-
hash_data(str): 設定用于hash計算后匹配緩存項的字符串值;
-
return():當某VCL域運行結束時將控制權返回給Varnish,并指示Varnish如何進行后續的動作;其可以返回的指令包括:lookup、pass、pipe、hit_for_pass、fetch、deliver和hash等;但某特定域可能僅能返回某些特定的指令,而非前面列出的全部指令;
-
return(restart):重新運行整個VCL,即重新從vcl_recv開始進行處理;每一次重啟都會增加req.restarts變量中的值,而max_restarts參數則用于限定最大重啟次數。
-
-
vcl_recv
vcl_recv是在Varnish完成對請求報文的解碼為基本數據結構后第一個要執行的子例程,它通常有四個主要用途:
-
修改客戶端數據以減少緩存對象差異性;比如刪除URL中的www.等字符;
-
基于客戶端數據選用緩存策略;比如僅緩存特定的URL請求、不緩存POST請求等;
-
為某web應用程序執行URL重寫規則;
-
挑選合適的后端Web服務器;
可以使用下面的終止語句,即通過return()向Varnish返回的指示操作:
-
pass:繞過緩存,即不從緩存中查詢內容或不將內容存儲至緩存中;
-
pipe:不對客戶端進行檢查或做出任何操作,而是在客戶端與后端服務器之間建立專用“管道”,并直接將數據在二者之間進行傳送;此時,keep-alive連接中后續傳送的數據也都將通過此管道進行直接傳送,并不會出現在任何日志中;
-
lookup:在緩存中查找用戶請求的對象,如果緩存中沒有其請求的對象,后續操作很可能會將其請求的對象進行緩存;
-
error:(注意這是3.x版本的參數,4.x已經改為synth)由Varnish自己合成一個響應報文,一般是響應一個錯誤類信息、重定向類信息或負載均衡器返回的后端web服務器健康狀態檢查類信息;
vcl_recv也可以通過精巧的策略完成一定意義上的安全功能,以將某些特定的攻擊扼殺于搖籃中。同時,它也可以檢查出一些拼寫類的錯誤并將其進行修正等。 Varnish默認的vcl_recv專門設計用來實現安全的緩存策略,它主要完成兩種功能:
-
僅處理可以識別的HTTP方法,并且只緩存GET和HEAD方法;
-
不緩存任何用戶特有的數據;
安全起見,一般在自定義的vcl_recv中不要使用return()終止語句,而是再由默認vcl_recv進行處理,并由其做出相應的處理決策。
-
-
vcl_fetch
如前面所述,相對于vcl_recv是根據客戶端的請求作出緩存決策來說,vcl_fetch則是根據服務器端的響應作出緩存決策。在任何VCL狀態引擎中返回的pass操作都將由vcl_fetch進行后續處理。vcl_fetch中有許多可用的內置變量,比如最常用的用于定義某對象緩存時長的beresp.ttl變量。默認的vcl_fetch放棄了緩存任何使用了Set-Cookie首部的響應。
通過return()返回給varnish的操作指示有:
-
deliver:緩存此對象,并將其發送給客戶端(經由vcl_deliver);
-
hit_for_pass:不緩存此對象,但可以導致后續對此對象的請求直接送達到vcl_pass進行處理;
-
restart:重啟整個VCL,并增加重啟計數;超出max_restarts限定的最大重啟次數后將會返回錯誤信息;
-
error code [reason]:返回指定的錯誤代碼給客戶端并丟棄此請求;
-
五、修剪緩存對象
-
緩存內容修剪
提高緩存命中率的最有效途徑之一是增加緩存對象的生存時間(TTL),但是這也可能會帶來副作用,比如緩存的內容在到達為其指定的有效期之間已經失效。因此,手動檢驗緩存對象的有效性或者刷新緩存很有可能成為管理員的日常工作之一,相應地,Varnish為完成這類的任務提供了三種途徑:HTTP 修剪(HTTP purging)、禁用某類緩存對象(banning)和強制緩存未命令(forced cache misses)。
-
移除單個緩存對象
purge用于清理緩存中的某特定對象及其變種(variants),因此,在有著明確要修剪的緩存對象時可以使用此種方式。HTTP協議的PURGE方法可以實現purge功能,不過,其僅能用于vcl_hit和vcl_miss中,它會釋放內存工作并移除指定緩存對象的所有Vary:-變種,并等待下一個針對此內容的客戶端請求到達時刷新此內容。另外,其一般要與return(restart)一起使用。下面是個在VCL中配置的示例。
-
強制緩存未命中
在vcl_recv中使用return(pass)能夠強制到上游服務器取得請求的內容,但這也會導致無法將其緩存。使用purge會移除舊的緩存對象,但如果上游服務器宕機而無法取得新版本的內容時,此內容將無法再響應給客戶端。使用req.has_always_miss=ture,可以讓Varnish在緩存中搜尋相應的內容但卻總是回應“未命中”,于是vcl_miss將后續地負責啟動vcl_fetch從上游服務器取得新內容,并以新內容緩存覆蓋舊內容。此時,如果上游服務器宕機或未響應,舊的內容將保持原狀,并能夠繼續服務于那些未使用req.has_always_miss=true的客戶端,直到其過期失效或由其它方法移除。
-
Banning
-
varnishadm:? 適合讓客戶端重新緩存更新的內容,只攔截一次。???
? ban <field> <operator> <arg>???????????????????????????????????????
-
示例:假設JSP更新了,讓客戶端都重新從服務器獲取一次內容,臨時按需清理???????????????????????
-
ban req.url ~ ^/javascripts? ? ? ? ? ? ? ? ? ? ? ?
-
ban? req.url ~ .js$?? ??? ??? ??? ??? ?? ?
-
ban? req.url == / && req.http.host ~ “ilinux.io”#清空指定域緩存,慎用!??????????????
-
-
在配置文件中定義,使用ban()函數,示例:?????????????????
if (req.method == "BAN") {???????????????????????? ban("req.http.host == " + req.http.host + " && req.url == " + req.url);?? #相當于在命令行執行 ban req.http.host == host && req.url == /index.html? ??????????????? return(synth(200, "Ban added"));??????????????????? }? ? ? ? ? ? ?
-
六、Varnish檢測后端主機的健康狀態
Varnish可以檢測后端主機的健康狀態,在判定后端主機失效時能自動將其從可用后端主機列表中移除,而一旦其重新變得可用還可以自動將其設定為可用。為了避免誤判,Varnish在探測后端主機的健康狀態發生轉變時(比如某次探測時某后端主機突然成為不可用狀態),通常需要連續執行幾次探測均為新狀態才將其標記為轉換后的狀態。
每個后端服務器當前探測的健康狀態探測方法通過.probe進行設定,其結果可由req.backend.healthy變量獲取,也可通過varnishlog中的Backend_health查看或varnishadm的debug.health查看。
.probe中的探測指令常用的有:
-
.url:探測后端主機健康狀態時請求的URL,默認為“/”;
-
.request: 探測后端主機健康狀態時所請求內容的詳細格式,定義后,它會替換.url指定的探測方式;比如:????.request =????????“GET /.healthtest.html HTTP/1.1”????????“Host: www.magedu.com”????????“Connection: close”;
-
.window:設定在判定后端主機健康狀態時基于最近多少次的探測進行,默認是8;
-
.threshold:在.window中指定的次數中,至少有多少次是成功的才判定后端主機正健康運行;默認是3;
-
.initial:Varnish啟動時對后端主機至少需要多少次的成功探測,默認同.threshold;
-
.expected_response:期望后端主機響應的狀態碼,默認為200;
-
.interval:探測請求的發送周期,默認為5秒;
-
.timeout:每次探測請求的過期時長,默認為2秒;
如果Varnish在某時刻沒有任何可用的后端主機,它將嘗試使用緩存對象的“寬容副本”(graced copy),當然,此時VCL中的各種規則依然有效。因此,在VCL規則中判斷req.backend.healthy變量顯示某后端主機不可用時,可為此后端主機增大req.grace變量的值以設定適用的寬容期限長度。
七、 Varnish使用多臺后端主機
Varnish中可以使用director指令將一個或多個近似的后端主機定義為一個邏輯組,并可以指定的調度方式(也叫挑選方法)來輪流將請求發送至這些主機上。不同的director可以使用同一個后端主機,而某director也可以使用“匿名”后端主機(在director中直接進行定義)。每個director都必須有其專用名,且在定義后必須在VCL中進行調用,VCL中任何可以指定后端主機的位置均可以按需將其替換為調用某已定義的director。
backend web1 { ? .host = "backweb1.magedu.com"; ? .port = "80"; } ? director webservers random { .retries = 5; { ? .backend = web1; ? .weight ?= 2; } { ? .backend ?= { ? ? .host = "backweb2.magedu.com"; ? ? .port = "80"; ? } ? ? .weight ? ? ? ? = 3; } }
如上示例中,web1為顯式定義的后端主機,而webservers這個directors還包含了一個“匿名”后端主機(backweb2.magedu.com)。webservers從這兩個后端主機中挑選一個主機的方法為random,即以隨機方式挑選。
Varnish的director支持的挑選方法中比較簡單的有round-robin和random兩種。其中,round-robin類型沒有任何參數,只需要為其指定各后端主機即可,挑選方式為“輪叫”,并在某后端主機故障時不再將其視作挑選對象;random方法隨機從可用后端主機中進行挑選,每一個后端主機都需要一個.weight參數以指定其權重,同時還可以在director上下文中使用.retires參數來設定查找一個健康后端主機時的嘗試次數。
Varnish 2.1.0后,random挑選方法又多了兩種變化形式client和hash。client類型的director使用client.identity作為挑選因子,這意味著client.identity相同的請求都將被發送至同一個后端主機。client.identity默認為client.ip,但也可以在VCL中將其修改為所需要的標識符。類似地,hash類型的director使用hash數據作為挑選因子,這意味著對同一個URL的請求將被發往同一個后端主機,其常用于多級緩存的場景中。然而,無論是client還hash,當其傾向于使用后端主機不可用時將會重新挑選新的后端其機。 另外還有一種稱作fallback的director,用于定義備用服務器。
八、varnish管理進階
-
可調參數
Varnish有許多參數,雖然大多數場景中這些參數的默認值都可以工作得很好,然而特定的工作場景中要想有著更好的性能的表現,則需要調整某些參數??梢栽诠芾斫涌谥惺褂胮aram.show命令查看這些參數,而使用param.set則能修改這些參數的值。然而,在命令行接口中進行的修改不會保存至任何位置,因此,重啟varnish后這些設定會消失??梢酝ㄟ^啟動腳本多次使用-p選項設定參數,在varnishd啟動時調用。然而,除非特別需要對其進行修改,保持這些參數為默認值可以有效降低管理復雜度。
-
共享日志
共享內存日志(shared memory log)通常被簡稱為shm-log,它用于記錄日志相關的數據,大小為80M。varnish以輪轉(round-robin)的方式使用其存儲空間。
-
線程模型
varnish的child進程由多種不同的線程組成,分別用于完成不同的工作。例如:
-
cache-worker線程:每連接一個,用于處理請求;
-
cache-main線程:全局只有一個,用于啟動cache;
-
ban lurker線程:一個,用于清理bans;
-
acceptor線程:一個,用于接收新的連接請求;
-
epoll/kqueue線程:數量可配置,默認為2,用于管理線程池;
-
expire線程:一個,用于移除老化的內容;
-
backend poll線程:每個后端服務器一個,用于檢測后端服務器的健康狀況;
在配置varnish時,一般只需關注cache-worker線程,而且也只能配置其線程池的數量,而除此之外的其它均非可配置參數。線程池的數量也只能在流量較大的場景下才需要增加,一般不超過CPU的物理核心數 。
-
-
線程相關參數
varnish為每個連接使用一個線程,因此,其worker線程的最大數決定了varnish的并發響應能力。下面是線程池相關的各參數及其配置:
-
thread_pool_add_delay??????2 [milliseconds]
-
thread_pool_add_threshold??2 [requests]
-
thread_pool_fail_delay?????200 [milliseconds]
-
thread_pool_max????????????500 [threads]
-
thread_pool_min????????????5 [threads]
-
thread_pool_purge_delay????1000 [milliseconds]
-
thread_pool_stack??????????65536 [bytes]
-
thread_pool_timeout????????120 [seconds]
-
thread_pool_workspace??????16384 [bytes]
-
thread_pools???????????????2 [pools]
-
thread_stats_rate??????????10 [requests]
其中最關鍵的當屬thread_pool_max和thread_pool_min,它們分別用于定義每個線程池中的最大線程數和最少線程數(即允許的最大空閑線程數 )。因此,在某個時刻,至少有thread_pool_min乘thread_pools個worker線程在運行,但至多不能超出thread_pool_max乘thread_pools個。根據需要,這兩個參數的數量可以進行調整,varnishstat命令的n_wrk_queued可以顯示當前varnish的線程數量是否足夠,如果隊列中始終有不少的線程等待運行,則可以適當調大thread_pool_max參數的值。一般建議每個線程池上最多運行的worker線程數不要超過5000個。
-
九、Varnish的命令行工具
命令語法:varnishadm -t timeout -T address:port [command […]]
通過命令行的方式連接至varnishd進行管理操作的工具,指定要連接的varnish實例的方法有兩種:
-
-n name —— 連接至名稱為“name”的實例;
-
-T address:port —— 連接至指定套接字上的實例;
其運行模式有兩種,當不在命令行中給出要執行的”command”時,其將進入交互式模式;否則,varnishadm將執行指定的”command”并退出。
例如,要查看本地啟用的緩存,可使用如下命令進行。
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 storage.list
十、配置實踐
(一)網絡拓補
(二)軟件版本
-
系統均為CentOS 7.4
-
keepalived.x86_64???????????????????????? 1.3.5-6.el7
-
nginx.x86_64????????????????????????????? 1:1.12.2-2.el7?????? epel
-
wordpress-4.9.4-zh_CN
-
varnish.x86_64??????????????????????????? 4.0.5-1.el7????????????????? epel
-
nfs-utils.x86_64????????????????????????? 1:1.3.0-0.54.el7?????????? ?
-
mariadb.x86_64??????????????????????????? 1:5.5.56-2.el7
-
php-fpm.x86_64??????????????????????????? 5.4.16-45.el7
-
php.x86_64???????????????????????????????? 5.4.16-45.el7
(三)各server配置
-
調度器配置
僅做單臺調度器示例,192.168.7.121
-
keepalived
global_defs { ? ? ? ? ? ? ? ? ? notification_email { ? ? ? ? ? ? ? ? ? ? ? root@localhost ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? notification_email_from keepalived@localhost ? ? ? ? ? ? ? ? ? smtp_server 127.0.0.1 ? ? ? ? ? ? ? ? ? smtp_connect_timeout 30 ? ? ? ? ? ? ? ? ? router_id keepalivedR1 ? ? ? ? ? ? ? ? ? vrrp_mcast_group4 224.0.0.33 ? ? ? ? ? ? ? } ? ? ? ? vrrp_script chk_nginx { ? ? ? ? ? ? ? script "/etc/keepalived/chk_nginx.sh" ? ? ? ? ? ? ? interval 1 ? ? ? ? ? ? ? weight -15 ? ? ? ? ? ? ? fall 2 ? ? ? ? ? ? ? rise 1 ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? vrrp_instance VI_1 { ? ? ? ? ? ? ? ? ? state MASTER ? ? ? ? ? ? ? ? ? interface ens39 ? ? ? ? ? ? ? ? ? virtual_router_id 3 ? ? ? ? ? ? ? ? ? priority 99 ? ? ? ? ? ? ? ? ? advert_int 1 ? ? ? ? ? ? ? ? ? authentication { ? ? ? ? ? ? ? ? ? ? ? auth_type PASS ? ? ? ? ? ? ? ? ? ? ? auth_pass 736w4ib2 ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? virtual_ipaddress { ? ? ? ? ? ? ? ? ? ? ? ?192.168.7.120/24 dev ens39 ? ? ? ? ? ? ? ? ? } ? track_script { ? ? ? ? ? ? ? ? ? chk_nginx ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? notify_master "/etc/keepalived/notify.sh master" ? ? ? ? ? ? ? ? ? notify_backup "/etc/keepalived/notify.sh backup" ? ? ? ? ? ? ? ? ? notify_fault "/etc/keepalived/notify.sh fault" ? ? ? ? ? ? ? }
-
nginx
server { ? ? ? listen 192.168.7.120:80; ? ? ? server_name wind.com; ? ? ? index index.php; ? ? ? location ~ \.(js|css|htm|html|gif|jpg|jpeg|png|bmp|txt|ico)$ { ? ? ? ? ? ? ? ? proxy_set_header X-Real-IP $remote_addr; ? ? ? ? ? ? ? ? proxy_pass http://192.168.7.120:8081;#靜態資源代理到本機varnish監聽端口 ? ? ? } ? ? ? ? location ~ ^/$ { ?#當僅輸入主站ip時跳轉到動態服務器的默認主頁index.php ? ? ? ? ? ? ? proxy_pass http://192.168.7.126:80; ? ? ? ? ? ? ? proxy_set_header X-Real-IP $remote_addr; ? ? ? } ? ? ? location ~ \.php$ { #所有的動態資源交給動態資源服務器 ? ? ? ? ? ? ? proxy_pass http://192.168.7.126:80; ? ? ? ? ? ? ? proxy_set_header X-Real-IP $remote_addr; ? ? ? } }
-
varnish監聽本機8081端口
import directors; #啟用代理功能的時候需要聲明 ? probe check { ?#健康檢測機制定義 ? ? ? .url = "/health.html"; #檢測RealServer工作目錄下的health.html ? ? ? .timeout = 2s;訪問超時時間,超時即返回error ? ? ? .interval = 2s;#兩次檢測的間隔時間 ? ? ? .window = 5; #檢測5次 ? ? ? .threshold = 2; #在window指定的總檢測次數中至少有2次即認定狀態變更 } backend static { ? #定義后端靜態資源主機 ? .host = "192.168.7.125"; ? .port = "80"; ? .probe = check; #調用check健康檢測機制 } ? backend image { ?#定義后端圖片服務器主機 ? .host = "192.168.7.127"; ? .port = "80"; ? .probe = check; } acl purgers { ?#限定可以進行緩存purge的客戶端IP范圍 ? ?"127.0.0.0"/8; #本機 ? ?"192.168.7.0"/24; #服務器本地網段 ? ? ? ? ? ? ? } sub vcl_recv { ? ? ? ?if (req.method == "PURGE") { #如果客戶端的請求報文首部中有PURGE method ? ? ? ? ? ?if (!client.ip ~ purgers) {#如果客戶端的IP不是前文purgers限定范圍 ? ? ? ? ? ? ? return(synth(405,"Purging not allowed for " + client.ip)); ? ? ? ? ? ? ? ? ? } ?#返回客戶端IP不允許進行緩存修剪操作 ? ? ? ? ? ? ? return(purge);#如果客戶端IP在purgers范圍且首部包含PURGE method就執行緩存修剪操作 ? ? ? } ? ? ? ?if (req.url ~ "(?i)\.(js|css|htm|html|txt)$") { ? ? ? ? ? ? ? set req.backend_hint = static; #靜態資源請求代理到static服務器 ? ? ? } ? ? ? ?if (req.url ~ "(?i)\.(gif|jpg|jpeg|png|bmp|ico)$") { ? ? ? ? ? ? ? set req.backend_hint = image; #圖片資源請求代理到image服務器 ? ? ? } } ? ? sub vcl_backend_response { ? ? ? ? ?if (beresp.http.cache-control !~ "s-maxage") { ? ? ? ? ? ? ? ?if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)$") { ? ? ? ? ? ? ? ? ? unset beresp.http.Set-Cookie; #取消靜態資源cookie設置 ? ? ? ? ? ? ? ? ? ?set beresp.ttl = 3600s; ?#修改靜態資源緩存時長 ? ? ? ? ? ? ? } ? ? ? ? ? } } ? sub vcl_deliver { ? ? ? ? if (obj.hits>0) { ? ? ? ? ? ? ? ? ? ?set resp.http.X-Cache = "HIT via " + server.ip; ? ? ? ? ? ? ? } else { #設置通過本地緩存提供資源時顯示 HIT+服務器IP 即緩存命中 ? ? ? ? ? ? ? ? ? ?set resp.http.X-Cache = "MISS via " + server.ip; ? ? ? ? ? ? ? }# 設置如果沒有在本地緩存中找到資源即 MISS 沒有命中緩存 ?
-
-
圖片服務器設置
?? image服務器,設置nfs共享文件夾存儲圖片資源,提供動態資源服務器所需數據庫。
? /usr/share/nginx/wordpress/wp-content/uploads 192.168.7.*(rw,sync,root_squash) ? ? nginx: ? server { ? ? ? ? ? listen 80; ? ? ? ? ? root /usr/share/nginx/wordpress; ? ? ? ? ? } ?
?? mariadb:
?? 安裝軟件包后運行初始化程序/usr/bin/mysql_secure_installation 設置root用戶密碼
?? 為wordpress創建數據庫,并設置一個在動態資源服務器上遠程訪問的用戶,為此用戶設定對wordpress數據庫具有所有權限。
-
動態資源服務器設置
安裝php、php-fpm、nginx
-
nginx設置
server { ? ? ? listen 80; ? ? ? root /usr/share/nginx/wordpress; ? ? ? index index.php; ? ? ? location ~ \.php$ { ? ? ? ? ? ? ? fastcgi_pass 127.0.0.1:9000;#設置將php解析請求傳送到php-fpm程序監聽端口 ? ? ? ? ? ? ? fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; ? ? ? ? ? ? ? include fastcgi_params; ? ? ? ? ? ? ? } ? ? ? }
-
mariadb:
在wordpress初始化設置時設定數據庫訪問192.168.7.127服務器所提供的mariadb
十一、緩存命中測試
(一)博客上傳圖片
? <center> Varnish與web架構實踐 </center>
一、前言
? 我國的互聯網處于飛速發展時期,網民隊伍飛速擴大,舉個栗子,家里的小學生跟爺爺一代都已經成為了使用智能手機獲取互聯網資源的新興勢力。龐大的網民群體必然要求主流或者想成為主流的互聯網企業具備承載海量并發訪問的能力,但是單純增加原始內容服務器數量去應對海量并發并不是有性價比的解決方案,因此各類緩存技術應運而生且誕生了一個口號“緩存為王”。
二、http協議緩存原理
? http協議緩存機制是指通過HTTP協議頭里的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段來控制文件緩存的機制。緩存會根據請求保存輸出內容的副本,例如html頁面,圖片,文件,當下一個請求來到的時候:如果是相同的URL,緩存直接使用副本響應訪問請求,而不是向源服務器再次發送請求。
常用首部:
-
Expires:指示響應內容過期的時間,格林威治時間GMT。
-
Cache-Control:用于更精細地控制 本地緩存的相關設置。
-
-
比如Cache-Control:max-age=600表示文件在本地緩存,且有效時長是600秒(從發出請求算起)。在接下來600秒內,如果有請求這個資源,瀏覽器不會發出HTTP請求,而是直接使用本地緩存的文件(強緩存)。
-
所以判斷緩存是否過期步驟是:
-
查看是否有cache-control 的max-age / s-maxage , s-maxage(share cache即public緩存最大保留時間)如果有,則用服務器時間date值 + max-age/s-maxage 的秒數計算出新的過期時間,將當前時間與過期時間進行比較,判斷是否過期。?
-
如果沒有cache-control 的max-age / s-maxage,則用expires 作為過期時間比較。
-
-
-
Last-Modified:標識文件在服務器上的最后一次更新時間。收到請求時,如果文件緩存過期,瀏覽器通過If-Modified-Since字段帶上這個時間,發送給服務器,由服務器比較時間戳來判斷文件是否有修改。如果沒有修改,服務器返回304告訴瀏覽器繼續使用緩存(協商緩存);如果有修改,則返回200,同時返回最新的文件。
-
Etag:web服務器響應請求時,告訴瀏覽器當前資源在服務器的唯一標識(生成規則由服務器決定)。Apache中,ETag的值,默認是對文件的索引節(INode),大小(Size)和最后修改時間(MTime)進行Hash后得到的。為什么要使用Etag主要是為了解決以下問題:
-
如果某些文件會被定期生成,有時內容并沒有任何變化,但Last-Modified卻改變了,導致緩存未命中。
-
Last-Modified標注的最后修改只能精確到秒級,如果某些文件在1秒鐘以內,被修改多次的話,它將不能準確標注文件的修改時間。
-
Last-Modified與ETag是可以一起使用的,服務器會優先驗證ETag,一致的情況下,才會繼續比對Last-Modified,最后才決定是否返回304。
-
-
no-cache :并不是說限定的資源不能緩存,而是要求瀏覽器必須發出條件式請求進行緩存有效性驗證,所以不能結合expires使用。
-
no-store :所限定的資源不允許緩存
三、Varnish簡介
? Varnish是一款高性能的輕量級開源web cache加速器,常見的緩存技術有Varnish與Squid,兩者的關系類似于Nginx與Httpd。與Squid 相比,Varnish 具有性能更高、速度更快、管理更加方便等諸多優點 。
-
varnish系統架構
varnish主要運行兩個進程:Management進程和Child進程(也叫Cache進程)。
Management進程主要實現應用新的配置、編譯VCL、監控varnish、初始化varnish以及提供一個命令行接口等。Management進程會每隔幾秒鐘探測一下Child進程以判斷其是否正常運行,如果在指定的時長內未得到Child進程的回應,Management將會重啟此Child進程。
Child進程包含多種類型的線程,常見的如:Acceptor線程:接收新的連接請求并響應;Worker線程:child進程會為每個會話啟動一個worker線程,因此,在高并發的場景中可能會出現數百個worker線程甚至更多;Expiry線程:從緩存中清理過期內容;
Varnish依賴“工作區(workspace)”以降低線程在申請或修改內存時出現競爭的可能性。在varnish內部有多種不同的工作區,其中最關鍵的當屬用于管理會話數據的session工作區。
-
varnish日志
為了與系統的其它部分進行交互,Child進程使用了可以通過文件系統接口進行訪問的共享內存日志(shared memory log),因此,如果某線程需要記錄信息,其僅需要持有一個鎖,而后向共享內存中的某內存區域寫入數據,再釋放持有的鎖即可。而為了減少競爭,每個worker線程都使用了日志數據緩存。
共享內存日志大小一般為90M,其分為兩部分,前一部分為計數器,后半部分為客戶端請求的數據。varnish提供了多個不同的工具如varnishlog、varnishncsa或varnishstat等來分析共享內存日志中的信息并能夠以指定的方式進行顯示。
-
VCL
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停止或重啟時會清除數據。
選擇使用合適的存儲方式有助于提升系統性,從經驗的角度來看,建議在內存空間足以存儲所有的緩存對象時使用malloc的方法,反之,file存儲將有著更好的性能的表現。然而,需要注意的是,varnishd實際上使用的空間比使用-s選項指定的緩存空間更大,一般說來,其需要為每個緩存對象多使用差不多1K左右的存儲空間,這意味著,對于100萬個緩存對象的場景來說,其使用的緩存空間將超出指定大小1G左右。另外,為了保存數據結構等,varnish自身也會占去不小的內存空間。
為varnishd指定使用的緩存類型時,-s選項可接受的參數格式如下:???
-
malloc[,size]
-
file[,path[,size[,granularity]]]? #granularity用于設定緩存空間分配單位,默認單位是字節,所有其它的大小都會被圓整。
-
persistent,path,size {experimental}???
-
四、Varnish狀態引擎(state engine)
VCL用于讓管理員定義緩存策略,而定義好的策略將由varnish的management進程分析、轉換成C代碼、編譯成二進制程序并連接至child進程。varnish內部有幾個所謂的狀態(state),在這些狀態上可以附加通過VCL定義的策略以完成相應的緩存處理機制,因此VCL也經常被稱作“域專用”語言或狀態引擎,“域專用”指的是有些數據僅出現于特定的狀態中。
-
VCL狀態引擎
在VCL狀態引擎中,狀態之間具有相關性,但彼此間互相隔離,每個引擎使用return(x)來退出當前狀態并指示varnish進入下一個狀態。
varnish開始處理一個請求時,首先需要分析HTTP請求本身,比如從首部獲取請求方法、驗正其是否為一個合法的HTT請求等。當這些基本分析結束后就需要做出第一個決策,即varnish是否從緩存中查找請求的資源。這個決定的實現則需要由VCL來完成,簡單來說,要由vcl_recv方法來完成。如果管理員沒有自定義vcl_recv函數,varnish將會執行默認的vcl_recv函數。然而,即便管理員自定義了vcl_recv,但如果沒有為自定義的vcl_recv函數指定其終止操作(terminating),其仍將執行默認的vcl_recv函數。事實上,varnish官方強烈建議讓varnish執行默認的vcl_recv以便處理自定義vcl_recv函數中的可能出現的漏洞。
-
VCL語法
VCL的設計參考了C和Perl語言,因此,對有著C或Perl編程經驗者來說,其非常易于理解。其基本語法說明如下:
-
//、#或/* comment */用于注釋
-
sub $name 定義函數
-
不支持循環,有內置變量
-
使用終止語句,沒有返回值
-
域專用
-
操作符:=(賦值)、==(等值比較)、~(模式匹配)、!(取反)、&&(邏輯與)、||(邏輯或)?
VCL的函數不接受參數并且沒有返回值,因此,其并非真正意義上的函數,這也限定了VCL內部的數據傳遞只能隱藏在HTTP首部內部進行。VCL的return語句用于將控制權從VCL狀態引擎返回給Varnish,而非默認函數,這就是為什么VCL只有終止語句而沒有返回值的原因。同時,對于每個“域”來說,可以定義一個或多個終止語句,以告訴Varnish下一步采取何種操作,如查詢緩存或不查詢緩存等。
-
-
VCL的內置函數
VCL提供了幾個函數來實現字符串的修改,添加bans,重啟VCL狀態引擎以及將控制權轉回Varnish等。
-
regsub(str,regex,sub)|regsuball(str,regex,sub) :這兩個用于基于正則表達式搜索指定的字符串并將其替換為指定的字符串;但regsuball()可以將str中能夠被regex匹配到的字符串統統替換為sub,regsub()只替換一次;
-
ban(expression)|ban_url(regex): 禁用所有其URL能夠由regex匹配的緩存對象;
-
purge:從緩存中挑選出某對象以及其相關變種一并刪除,這可以通過HTTP協議的PURGE方法完成;
-
hash_data(str): 設定用于hash計算后匹配緩存項的字符串值;
-
return():當某VCL域運行結束時將控制權返回給Varnish,并指示Varnish如何進行后續的動作;其可以返回的指令包括:lookup、pass、pipe、hit_for_pass、fetch、deliver和hash等;但某特定域可能僅能返回某些特定的指令,而非前面列出的全部指令;
-
return(restart):重新運行整個VCL,即重新從vcl_recv開始進行處理;每一次重啟都會增加req.restarts變量中的值,而max_restarts參數則用于限定最大重啟次數。
-
-
vcl_recv
vcl_recv是在Varnish完成對請求報文的解碼為基本數據結構后第一個要執行的子例程,它通常有四個主要用途:
-
修改客戶端數據以減少緩存對象差異性;比如刪除URL中的www.等字符;
-
基于客戶端數據選用緩存策略;比如僅緩存特定的URL請求、不緩存POST請求等;
-
為某web應用程序執行URL重寫規則;
-
挑選合適的后端Web服務器;
可以使用下面的終止語句,即通過return()向Varnish返回的指示操作:
-
pass:繞過緩存,即不從緩存中查詢內容或不將內容存儲至緩存中;
-
pipe:不對客戶端進行檢查或做出任何操作,而是在客戶端與后端服務器之間建立專用“管道”,并直接將數據在二者之間進行傳送;此時,keep-alive連接中后續傳送的數據也都將通過此管道進行直接傳送,并不會出現在任何日志中;
-
lookup:在緩存中查找用戶請求的對象,如果緩存中沒有其請求的對象,后續操作很可能會將其請求的對象進行緩存;
-
error:(注意這是3.x版本的參數,4.x已經改為synth)由Varnish自己合成一個響應報文,一般是響應一個錯誤類信息、重定向類信息或負載均衡器返回的后端web服務器健康狀態檢查類信息;
vcl_recv也可以通過精巧的策略完成一定意義上的安全功能,以將某些特定的攻擊扼殺于搖籃中。同時,它也可以檢查出一些拼寫類的錯誤并將其進行修正等。 Varnish默認的vcl_recv專門設計用來實現安全的緩存策略,它主要完成兩種功能:
-
僅處理可以識別的HTTP方法,并且只緩存GET和HEAD方法;
-
不緩存任何用戶特有的數據;
安全起見,一般在自定義的vcl_recv中不要使用return()終止語句,而是再由默認vcl_recv進行處理,并由其做出相應的處理決策。
-
-
vcl_fetch
如前面所述,相對于vcl_recv是根據客戶端的請求作出緩存決策來說,vcl_fetch則是根據服務器端的響應作出緩存決策。在任何VCL狀態引擎中返回的pass操作都將由vcl_fetch進行后續處理。vcl_fetch中有許多可用的內置變量,比如最常用的用于定義某對象緩存時長的beresp.ttl變量。默認的vcl_fetch放棄了緩存任何使用了Set-Cookie首部的響應。
通過return()返回給varnish的操作指示有:
-
deliver:緩存此對象,并將其發送給客戶端(經由vcl_deliver);
-
hit_for_pass:不緩存此對象,但可以導致后續對此對象的請求直接送達到vcl_pass進行處理;
-
restart:重啟整個VCL,并增加重啟計數;超出max_restarts限定的最大重啟次數后將會返回錯誤信息;
-
error code [reason]:返回指定的錯誤代碼給客戶端并丟棄此請求;
-
五、修剪緩存對象
-
緩存內容修剪
提高緩存命中率的最有效途徑之一是增加緩存對象的生存時間(TTL),但是這也可能會帶來副作用,比如緩存的內容在到達為其指定的有效期之間已經失效。因此,手動檢驗緩存對象的有效性或者刷新緩存很有可能成為管理員的日常工作之一,相應地,Varnish為完成這類的任務提供了三種途徑:HTTP 修剪(HTTP purging)、禁用某類緩存對象(banning)和強制緩存未命令(forced cache misses)。
-
移除單個緩存對象
purge用于清理緩存中的某特定對象及其變種(variants),因此,在有著明確要修剪的緩存對象時可以使用此種方式。HTTP協議的PURGE方法可以實現purge功能,不過,其僅能用于vcl_hit和vcl_miss中,它會釋放內存工作并移除指定緩存對象的所有Vary:-變種,并等待下一個針對此內容的客戶端請求到達時刷新此內容。另外,其一般要與return(restart)一起使用。下面是個在VCL中配置的示例。
-
強制緩存未命中
在vcl_recv中使用return(pass)能夠強制到上游服務器取得請求的內容,但這也會導致無法將其緩存。使用purge會移除舊的緩存對象,但如果上游服務器宕機而無法取得新版本的內容時,此內容將無法再響應給客戶端。使用req.has_always_miss=ture,可以讓Varnish在緩存中搜尋相應的內容但卻總是回應“未命中”,于是vcl_miss將后續地負責啟動vcl_fetch從上游服務器取得新內容,并以新內容緩存覆蓋舊內容。此時,如果上游服務器宕機或未響應,舊的內容將保持原狀,并能夠繼續服務于那些未使用req.has_always_miss=true的客戶端,直到其過期失效或由其它方法移除。
-
Banning
-
varnishadm:? 適合讓客戶端重新緩存更新的內容,只攔截一次。???
ban <field> <operator> <arg>???????????????????????????????????????
-
示例:假設JSP更新了,讓客戶端都重新從服務器獲取一次內容,臨時按需清理???????????????????????
-
ban req.url ~ ^/javascripts? ? ? ? ? ? ? ? ? ? ? ?
-
ban? req.url ~ .js$?? ??? ??? ??? ??? ?? ?
-
ban? req.url == / && req.http.host ~ “ilinux.io”#清空指定域緩存,慎用!??????????????
-
-
在配置文件中定義,使用ban()函數,示例:?????????????????
if (req.method == "BAN") {???????????????????????? ban("req.http.host == " + req.http.host + " && req.url == " + req.url);?? #相當于在命令行執行 ban req.http.host == host && req.url == /index.html? ??????????????? return(synth(200, "Ban added"));??????????????????? }? ? ? ? ? ? ?
-
六、Varnish檢測后端主機的健康狀態
Varnish可以檢測后端主機的健康狀態,在判定后端主機失效時能自動將其從可用后端主機列表中移除,而一旦其重新變得可用還可以自動將其設定為可用。為了避免誤判,Varnish在探測后端主機的健康狀態發生轉變時(比如某次探測時某后端主機突然成為不可用狀態),通常需要連續執行幾次探測均為新狀態才將其標記為轉換后的狀態。
每個后端服務器當前探測的健康狀態探測方法通過.probe進行設定,其結果可由req.backend.healthy變量獲取,也可通過varnishlog中的Backend_health查看或varnishadm的debug.health查看。
.probe中的探測指令常用的有:
-
.url:探測后端主機健康狀態時請求的URL,默認為“/”;
-
.request: 探測后端主機健康狀態時所請求內容的詳細格式,定義后,它會替換.url指定的探測方式;比如:????.request =????????“GET /.healthtest.html HTTP/1.1”????????“Host: www.magedu.com”????????“Connection: close”;
-
.window:設定在判定后端主機健康狀態時基于最近多少次的探測進行,默認是8;
-
.threshold:在.window中指定的次數中,至少有多少次是成功的才判定后端主機正健康運行;默認是3;
-
.initial:Varnish啟動時對后端主機至少需要多少次的成功探測,默認同.threshold;
-
.expected_response:期望后端主機響應的狀態碼,默認為200;
-
.interval:探測請求的發送周期,默認為5秒;
-
.timeout:每次探測請求的過期時長,默認為2秒;
如果Varnish在某時刻沒有任何可用的后端主機,它將嘗試使用緩存對象的“寬容副本”(graced copy),當然,此時VCL中的各種規則依然有效。因此,在VCL規則中判斷req.backend.healthy變量顯示某后端主機不可用時,可為此后端主機增大req.grace變量的值以設定適用的寬容期限長度。
七、 Varnish使用多臺后端主機
Varnish中可以使用director指令將一個或多個近似的后端主機定義為一個邏輯組,并可以指定的調度方式(也叫挑選方法)來輪流將請求發送至這些主機上。不同的director可以使用同一個后端主機,而某director也可以使用“匿名”后端主機(在director中直接進行定義)。每個director都必須有其專用名,且在定義后必須在VCL中進行調用,VCL中任何可以指定后端主機的位置均可以按需將其替換為調用某已定義的director。
backend web1 { ? .host = "backweb1.magedu.com"; ? .port = "80"; } ? director webservers random { .retries = 5; { ? .backend = web1; ? .weight ?= 2; } { ? .backend ?= { ? ? .host = "backweb2.magedu.com"; ? ? .port = "80"; ? } ? ? .weight ? ? ? ? = 3; } }
如上示例中,web1為顯式定義的后端主機,而webservers這個directors還包含了一個“匿名”后端主機(backweb2.magedu.com)。webservers從這兩個后端主機中挑選一個主機的方法為random,即以隨機方式挑選。
Varnish的director支持的挑選方法中比較簡單的有round-robin和random兩種。其中,round-robin類型沒有任何參數,只需要為其指定各后端主機即可,挑選方式為“輪叫”,并在某后端主機故障時不再將其視作挑選對象;random方法隨機從可用后端主機中進行挑選,每一個后端主機都需要一個.weight參數以指定其權重,同時還可以在director上下文中使用.retires參數來設定查找一個健康后端主機時的嘗試次數。
Varnish 2.1.0后,random挑選方法又多了兩種變化形式client和hash。client類型的director使用client.identity作為挑選因子,這意味著client.identity相同的請求都將被發送至同一個后端主機。client.identity默認為client.ip,但也可以在VCL中將其修改為所需要的標識符。類似地,hash類型的director使用hash數據作為挑選因子,這意味著對同一個URL的請求將被發往同一個后端主機,其常用于多級緩存的場景中。然而,無論是client還hash,當其傾向于使用后端主機不可用時將會重新挑選新的后端其機。 另外還有一種稱作fallback的director,用于定義備用服務器。
八、varnish管理進階
-
可調參數
Varnish有許多參數,雖然大多數場景中這些參數的默認值都可以工作得很好,然而特定的工作場景中要想有著更好的性能的表現,則需要調整某些參數??梢栽诠芾斫涌谥惺褂胮aram.show命令查看這些參數,而使用param.set則能修改這些參數的值。然而,在命令行接口中進行的修改不會保存至任何位置,因此,重啟varnish后這些設定會消失??梢酝ㄟ^啟動腳本多次使用-p選項設定參數,在varnishd啟動時調用。然而,除非特別需要對其進行修改,保持這些參數為默認值可以有效降低管理復雜度。
-
共享日志
共享內存日志(shared memory log)通常被簡稱為shm-log,它用于記錄日志相關的數據,大小為80M。varnish以輪轉(round-robin)的方式使用其存儲空間。
-
線程模型
varnish的child進程由多種不同的線程組成,分別用于完成不同的工作。例如:
-
cache-worker線程:每連接一個,用于處理請求;
-
cache-main線程:全局只有一個,用于啟動cache;
-
ban lurker線程:一個,用于清理bans;
-
acceptor線程:一個,用于接收新的連接請求;
-
epoll/kqueue線程:數量可配置,默認為2,用于管理線程池;
-
expire線程:一個,用于移除老化的內容;
-
backend poll線程:每個后端服務器一個,用于檢測后端服務器的健康狀況;
在配置varnish時,一般只需關注cache-worker線程,而且也只能配置其線程池的數量,而除此之外的其它均非可配置參數。線程池的數量也只能在流量較大的場景下才需要增加,一般不超過CPU的物理核心數 。
-
-
線程相關參數
varnish為每個連接使用一個線程,因此,其worker線程的最大數決定了varnish的并發響應能力。下面是線程池相關的各參數及其配置:
-
thread_pool_add_delay??????2 [milliseconds]
-
thread_pool_add_threshold??2 [requests]
-
thread_pool_fail_delay?????200 [milliseconds]
-
thread_pool_max????????????500 [threads]
-
thread_pool_min????????????5 [threads]
-
thread_pool_purge_delay????1000 [milliseconds]
-
thread_pool_stack??????????65536 [bytes]
-
thread_pool_timeout????????120 [seconds]
-
thread_pool_workspace??????16384 [bytes]
-
thread_pools???????????????2 [pools]
-
thread_stats_rate??????????10 [requests]
其中最關鍵的當屬thread_pool_max和thread_pool_min,它們分別用于定義每個線程池中的最大線程數和最少線程數(即允許的最大空閑線程數 )。因此,在某個時刻,至少有thread_pool_min乘thread_pools個worker線程在運行,但至多不能超出thread_pool_max乘thread_pools個。根據需要,這兩個參數的數量可以進行調整,varnishstat命令的n_wrk_queued可以顯示當前varnish的線程數量是否足夠,如果隊列中始終有不少的線程等待運行,則可以適當調大thread_pool_max參數的值。一般建議每個線程池上最多運行的worker線程數不要超過5000個。
-
九、Varnish的命令行工具
命令語法:varnishadm -t timeout -T address:port [command […]]
通過命令行的方式連接至varnishd進行管理操作的工具,指定要連接的varnish實例的方法有兩種:
-
-n name —— 連接至名稱為“name”的實例;
-
-T address:port —— 連接至指定套接字上的實例;
其運行模式有兩種,當不在命令行中給出要執行的”command”時,其將進入交互式模式;否則,varnishadm將執行指定的”command”并退出。
例如,要查看本地啟用的緩存,可使用如下命令進行。
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 storage.list
十、配置實踐
(一)網絡拓補
(二)軟件版本
-
系統均為CentOS 7.4
-
keepalived.x86_64???????????????????????? 1.3.5-6.el7
-
nginx.x86_64????????????????????????????? 1:1.12.2-2.el7?????? epel
-
wordpress-4.9.4-zh_CN
-
varnish.x86_64??????????????????????????? 4.0.5-1.el7????????????????? epel
-
nfs-utils.x86_64????????????????????????? 1:1.3.0-0.54.el7?????????? ?
-
mariadb.x86_64??????????????????????????? 1:5.5.56-2.el7
-
php-fpm.x86_64??????????????????????????? 5.4.16-45.el7
-
php.x86_64???????????????????????????????? 5.4.16-45.el7
(三)各server配置
-
調度器配置
僅做單臺調度器示例,192.168.7.121
-
keepalived
global_defs { ? ? ? ? ? ? ? ? ? notification_email { ? ? ? ? ? ? ? ? ? ? ? root@localhost ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? notification_email_from keepalived@localhost ? ? ? ? ? ? ? ? ? smtp_server 127.0.0.1 ? ? ? ? ? ? ? ? ? smtp_connect_timeout 30 ? ? ? ? ? ? ? ? ? router_id keepalivedR1 ? ? ? ? ? ? ? ? ? vrrp_mcast_group4 224.0.0.33 ? ? ? ? ? ? ? } ? ? ? ? vrrp_script chk_nginx { ? ? ? ? ? ? ? script "/etc/keepalived/chk_nginx.sh" ? ? ? ? ? ? ? interval 1 ? ? ? ? ? ? ? weight -15 ? ? ? ? ? ? ? fall 2 ? ? ? ? ? ? ? rise 1 ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? vrrp_instance VI_1 { ? ? ? ? ? ? ? ? ? state MASTER ? ? ? ? ? ? ? ? ? interface ens39 ? ? ? ? ? ? ? ? ? virtual_router_id 3 ? ? ? ? ? ? ? ? ? priority 99 ? ? ? ? ? ? ? ? ? advert_int 1 ? ? ? ? ? ? ? ? ? authentication { ? ? ? ? ? ? ? ? ? ? ? auth_type PASS ? ? ? ? ? ? ? ? ? ? ? auth_pass 736w4ib2 ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? virtual_ipaddress { ? ? ? ? ? ? ? ? ? ? ? ?192.168.7.120/24 dev ens39 ? ? ? ? ? ? ? ? ? } ? track_script { ? ? ? ? ? ? ? ? ? chk_nginx ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? notify_master "/etc/keepalived/notify.sh master" ? ? ? ? ? ? ? ? ? notify_backup "/etc/keepalived/notify.sh backup" ? ? ? ? ? ? ? ? ? notify_fault "/etc/keepalived/notify.sh fault" ? ? ? ? ? ? ? }
-
nginx
server { ? ? ? listen 192.168.7.120:80; ? ? ? server_name wind.com; ? ? ? index index.php; ? ? ? location ~ \.(js|css|htm|html|gif|jpg|jpeg|png|bmp|txt|ico)$ { ? ? ? ? ? ? ? ? proxy_set_header X-Real-IP $remote_addr; ? ? ? ? ? ? ? ? proxy_pass http://192.168.7.120:8081;#靜態資源代理到本機varnish監聽端口 ? ? ? } ? ? ? ? location ~ ^/$ { ?#當僅輸入主站ip時跳轉到動態服務器的默認主頁index.php ? ? ? ? ? ? ? proxy_pass http://192.168.7.126:80; ? ? ? ? ? ? ? proxy_set_header X-Real-IP $remote_addr; ? ? ? } ? ? ? location ~ \.php$ { #所有的動態資源交給動態資源服務器 ? ? ? ? ? ? ? proxy_pass http://192.168.7.126:80; ? ? ? ? ? ? ? proxy_set_header X-Real-IP $remote_addr; ? ? ? } }
-
varnish監聽本機8081端口
import directors; #啟用代理功能的時候需要聲明 ? probe check { ?#健康檢測機制定義 ? ? ? .url = "/health.html"; #檢測RealServer工作目錄下的health.html ? ? ? .timeout = 2s;訪問超時時間,超時即返回error ? ? ? .interval = 2s;#兩次檢測的間隔時間 ? ? ? .window = 5; #檢測5次 ? ? ? .threshold = 2; #在window指定的總檢測次數中至少有2次即認定狀態變更 } backend static { ? #定義后端靜態資源主機 ? .host = "192.168.7.125"; ? .port = "80"; ? .probe = check; #調用check健康檢測機制 } ? backend image { ?#定義后端圖片服務器主機 ? .host = "192.168.7.127"; ? .port = "80"; ? .probe = check; } acl purgers { ?#限定可以進行緩存purge的客戶端IP范圍 ? ?"127.0.0.0"/8; #本機 ? ?"192.168.7.0"/24; #服務器本地網段 ? ? ? ? ? ? ? } sub vcl_recv { ? ? ? ?if (req.method == "PURGE") { #如果客戶端的請求報文首部中有PURGE method ? ? ? ? ? ?if (!client.ip ~ purgers) {#如果客戶端的IP不是前文purgers限定范圍 ? ? ? ? ? ? ? return(synth(405,"Purging not allowed for " + client.ip)); ? ? ? ? ? ? ? ? ? } ?#返回客戶端IP不允許進行緩存修剪操作 ? ? ? ? ? ? ? return(purge);#如果客戶端IP在purgers范圍且首部包含PURGE method就執行緩存修剪操作 ? ? ? } ? ? ? ?if (req.url ~ "(?i)\.(js|css|htm|html|txt)$") { ? ? ? ? ? ? ? set req.backend_hint = static; #靜態資源請求代理到static服務器 ? ? ? } ? ? ? ?if (req.url ~ "(?i)\.(gif|jpg|jpeg|png|bmp|ico)$") { ? ? ? ? ? ? ? set req.backend_hint = image; #圖片資源請求代理到image服務器 ? ? ? } } ? ? sub vcl_backend_response { ? ? ? ? ?if (beresp.http.cache-control !~ "s-maxage") { ? ? ? ? ? ? ? ?if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)$") { ? ? ? ? ? ? ? ? ? unset beresp.http.Set-Cookie; #取消靜態資源cookie設置 ? ? ? ? ? ? ? ? ? ?set beresp.ttl = 3600s; ?#修改靜態資源緩存時長 ? ? ? ? ? ? ? } ? ? ? ? ? } } ? sub vcl_deliver { ? ? ? ? if (obj.hits>0) { ? ? ? ? ? ? ? ? ? ?set resp.http.X-Cache = "HIT via " + server.ip; ? ? ? ? ? ? ? } else { #設置通過本地緩存提供資源時顯示 HIT+服務器IP 即緩存命中 ? ? ? ? ? ? ? ? ? ?set resp.http.X-Cache = "MISS via " + server.ip; ? ? ? ? ? ? ? }# 設置如果沒有在本地緩存中找到資源即 MISS 沒有命中緩存 ?
-
-
圖片服務器設置
image服務器,設置nfs共享文件夾存儲圖片資源,提供動態資源服務器所需數據庫。
? /usr/share/nginx/wordpress/wp-content/uploads 192.168.7.*(rw,sync,root_squash) ? ? nginx: ? server { ? ? ? ? ? listen 80; ? ? ? ? ? root /usr/share/nginx/wordpress; ? ? ? ? ? } ?
?? mariadb:
?? 安裝軟件包后運行初始化程序/usr/bin/mysql_secure_installation 設置root用戶密碼
?? 為wordpress創建數據庫,并設置一個在動態資源服務器上遠程訪問的用戶,為此用戶設定對wordpress數據庫具有所有權限。
-
動態資源服務器設置
安裝php、php-fpm、nginx
-
nginx設置
server { ? ? ? listen 80; ? ? ? root /usr/share/nginx/wordpress; ? ? ? index index.php; ? ? ? location ~ \.php$ { ? ? ? ? ? ? ? fastcgi_pass 127.0.0.1:9000;#設置將php解析請求傳送到php-fpm程序監聽端口 ? ? ? ? ? ? ? fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; ? ? ? ? ? ? ? include fastcgi_params; ? ? ? ? ? ? ? } ? ? ? }
-
mariadb:
在wordpress初始化設置時設定數據庫訪問192.168.7.127服務器所提供的mariadb
十一、緩存命中測試
博客上傳圖片
(http://192.168.7.120/wp-content/uploads/2018/07/cluster.jpg)
在地址欄貼入圖片url測試可以看到響應報文頭部第一次是miss沒有命中;刷新后就變成了hit:
?? X-Cache: HIT via 192.168.7.120
本文來自投稿,不代表Linux運維部落立場,如若轉載,請注明出處:http://www.www58058.com/103828