天天看點

萬字長文聊緩存(上)

深入解析springmvc核心原理:從手寫簡易版mvc架構開始(smartmvc) : https://github.com/silently9527/smartmvc idea多線程檔案下載下傳插件: https://github.com/silently9527/fastdownloadideaplugin 公衆号:貝塔學java

緩存的目的是為了提高系統的通路速度,讓資料更加接近于使用者,通常也是提升性能的常用手段。緩存在生活中其實也是無處不在,比如物流系統,他們基本上在各地都有分倉庫,如果本地倉庫有資料,那麼送貨的速度就會很快;cpu讀取資料也采用了緩存,寄存器->高速緩存->記憶體->硬碟/網絡;我們經常使用的maven倉庫也同樣有本地倉庫和遠端倉庫。現階段緩存的使用場景也越來越多,比如:浏覽器緩存、反向代理層緩存、應用層緩存、資料庫查詢緩存、分布式集中緩存。

本文我們就先從浏覽器緩存和nginx緩存開始聊起。

浏覽器緩存是指當我們去通路一個網站或者http服務的時候,伺服器可以設定http的響應頭資訊,其中如果設定緩存相關的頭資訊,那麼浏覽器就會緩存這些資料,下次再通路這些資料的時候就直接從浏覽器緩存中擷取或者是隻需要去伺服器中校驗下緩存時候有效,可以減少浏覽器與伺服器之間的網絡時間的開銷以及節省帶寬。

htpp相關的知識,歡迎去參觀 《面試篇》http協定

該指令是通用首部字段(請求首部和響應首部都可以使用),用于控制緩存的工作機制,該指令參數稍多,常用的參數:

no-cache: 表示不需要緩存該資源

max-age(秒): 緩存的最大有效時間,當max-age=0時,表示不需要緩存

控制資源失效的日期,當浏覽器接受到<code>expires</code>之後,浏覽器都會使用本地的緩存,在過期日期之後才會向務器發送請求;如果伺服器同時在響應頭中也指定了<code>cache-control</code>的<code>max-age</code>指令時,浏覽器會優先處理<code>max-age</code>。

如果伺服器不想要讓浏覽器對資源緩存,可以把<code>expires</code>和首部字段<code>date</code>設定相同的值

萬字長文聊緩存(上)

<code>last-modified</code> 用于指明資源最終被修改的時間。配合<code>if-modified-since</code>一起使用可以通過時間對緩存是否有效進行校驗;後面實戰會使用到這種方式。

萬字長文聊緩存(上)
萬字長文聊緩存(上)

如果請求頭中<code>if-modified-since</code>的日期早于請求資源的更新日期,那麼服務會進行處理,傳回最新的資源;如果<code>if-modified-since</code>指定的日期之後請求的資源都未更新過,那麼服務不會處理請求并傳回<code>304 mot modified</code>的響應,表示緩存的檔案有效可以繼續使用。

使用springmvc做緩存的測試代碼:

當第一次通路<code>http://localhost:8080/http/cache</code>的時候,我們可以看到如下的響應頭資訊:

萬字長文聊緩存(上)

前面我們已提到了<code>cache-control</code>的優先級高于<code>expires</code>,實際的項目中我們可以同時使用,或者隻使用<code>cache-control</code>。<code>expires</code>的值通常情況下都是<code>系統目前時間+緩存過期時間</code>

當我們在15秒之内再次通路<code>http://localhost:8080/http/cache</code>會看到如下的請求頭:

萬字長文聊緩存(上)

此時發送到伺服器端的頭資訊<code>if-modified-since</code>就是上次請求伺服器傳回的<code>last-modified</code>,浏覽器會拿這個時間去和伺服器校驗内容是否發送了變化,由于我們背景程式在15秒之内都表示沒有修改過内容,是以得到了如下的響應頭資訊

萬字長文聊緩存(上)

響應的狀态碼304,表示伺服器告訴浏覽器,你的緩存是有效的可以繼續使用。

萬字長文聊緩存(上)

請求首部字段<code>if-none-match</code>傳輸給伺服器的值是伺服器傳回的etag值,隻有當伺服器上請求資源的<code>etag</code>值與<code>if-none-match</code>不一緻時,伺服器才去處理該請求。

響應首部字段<code>etag</code>能夠告知客服端響應實體的辨別,它是一種可将資源以字元串的形式做唯一辨別的方式。伺服器可以為每份資源指定一個<code>etag</code>值。當資源被更新時,<code>etag</code>的值也會被更新。通常生成<code>etag</code>值的算法使用的是md5。

強etag值:不論實體發生了多麼細微的變化都會改變其值

弱etag值:隻用于提示資源是否相同,隻有當資源發送了根本上的變化,etag才會被改變。使用弱etag值需要在前面添加<code>w/</code>

通常建議選擇弱etag值,因為大多數時候我們都會在代理層開啟gzip壓縮,弱etag可以驗證壓縮和不壓縮的實體,而強etag值要求響應實體位元組必須完全一緻。

etag是用于發送到伺服器端進行内容變更驗證的,第一次請求<code>http://localhost:8080/http/etag</code>,擷取到的響應頭資訊:

萬字長文聊緩存(上)

在30秒之内,我們再次重新整理頁面,可以看到如下的請求頭資訊:

萬字長文聊緩存(上)

這裡的<code>if-none-match</code>就是上一次請求服務傳回的<code>etag</code>值,伺服器校驗<code>if-none-match</code>值與<code>etag</code>值相等,是以傳回了304告訴浏覽器緩存可以用。

萬字長文聊緩存(上)

通過上面的兩個事例我們可以看出<code>etag</code>需要伺服器先查詢出需要響應的内容,然後計算出etag值,再與浏覽器請求頭中<code>if-none-match</code>來比較覺得是否需要傳回資料,對于伺服器來說僅僅是節省了帶寬,原本應該伺服器調用後端服務查詢的資訊依然沒有被省掉;而<code>last-modified</code>通過時間的比較,如果内容沒有更新,伺服器不需要調用後端服務查詢出響應資料,不僅節省了伺服器的帶寬也降低了後端服務的壓力;

萬字長文聊緩存(上)
萬字長文聊緩存(上)

對比之後得出結論:通常來說為了降低後端服務的壓力<code>etag</code>适用于圖檔/js/css等靜态資源,而類似使用者詳情資訊需要調用後端服務的資料适合使用<code>last-modified</code>來處理。

通常情況下我們都會使用到nginx來做反向代理伺服器,我們可以通過緩沖、緩存來對nginx進行調優,本篇我們就從這兩個方面來聊聊nginx調優

預設情況下,nginx在傳回響應給用戶端之前會盡可能快的從上遊伺服器擷取資料,nginx會盡可能的将上有伺服器傳回的資料緩沖到本地,然後一次性的全部傳回給用戶端,如果每次從上遊伺服器傳回的資料都需要寫入到磁盤中,那麼nginx的性能肯定會降低;是以我們需要根據實際情況對nginx的緩存做優化。

<code>proxy_buffer_size</code>: 設定nginx緩沖區的大小,用來存儲upstream端響應的header。

<code>proxy_buffering</code>: 啟用代理内容緩沖,當該功能禁用時,代理一接收到上遊伺服器的傳回就立即同步的發送給用戶端,<code>proxy_max_temp_file_size</code>被設定為0;通過設定<code>proxy_buffering</code>為on,<code>proxy_max_temp_file_size</code>為0 可以確定代理的過程中不适用磁盤,隻是用緩沖區; 開啟後<code>proxy_buffers</code>和<code>proxy_busy_buffers_size</code>參數才會起作用

<code>proxy_buffers</code>: 設定響應上遊伺服器的緩存數量和大小,當一個緩沖區占滿後會申請開啟下一個緩沖區,直到緩沖區數量到達設定的最大值

<code>proxy_busy_buffers_size</code>: 在從上遊伺服器讀取響應資料時配置設定給發送到用戶端響應的緩沖區大小,所有連接配接共用<code>proxy_busy_buffers_size</code>設定的緩沖區大小,一旦<code>proxy_buffers</code>設定的buffer被寫入,直到buffer裡面的資料被完整的傳輸完(傳輸到用戶端),這個buffer将會一直處 在busy狀态,我們不能對這個buffer進行任何别的操作。所有處在busy狀态的buffer size加起來不能超過<code>proxy_busy_buffers_size</code>, 是以<code>proxy_busy_buffers_size</code>是用來控制同時傳輸到用戶端的buffer數量的; 典型是設定成<code>proxy_buffers</code>的兩倍。

萬字長文聊緩存(上)

nginx代理緩沖的設定都是作用到每一個請求的,想要設定緩沖區的大小到最佳狀态,需要測量出經過反向代理伺服器器的平均請求數和響應的大小;<code>proxy_buffers</code>指令的預設值 8個 4kb 或者 8個 8kb(具體依賴于作業系統),假如我們的伺服器是1g,這台伺服器隻運作了nginx服務,那麼排除到作業系統的記憶體使用,保守估計nginx能夠使用的記憶體是768m

每個活動的連接配接使用緩沖記憶體:8個4kb = 8 4 1024 = 32768位元組

系統可使用的記憶體大小768m: 768 1024 1024 = 805306368位元組

是以nginx能夠同時處理的連接配接數:805306368 / 32768 = 24576

經過我們的粗略估計,1g的伺服器隻運作nginx大概可以同時處理24576個連接配接。

假如我們測量和發現經過反向代理伺服器響應的平均資料大小是 900kb , 而預設的 8個4kb的緩沖區是無法滿足的,是以我們可以調整大小

這樣設定之後每次請求可以達到最快的響應,但是同時處理的連接配接數減少了,<code>(768 * 1024 * 1024) / (30 * 32 * 1024)</code>=819個活動連接配接;

如果我們系統的并發數不是太高,我們可以将<code>proxy_buffers</code>緩沖區的個數下調,設定稍大的<code>proxy_busy_buffers_size</code>加大往用戶端發送的緩沖區,以確定nginx在傳輸的過程中能夠把從上遊伺服器讀取到的資料全部寫入到緩沖區中。

萬字長文聊緩存(上)

nignx除了可以緩沖上遊伺服器的響應達到快速傳回給用戶端,它還可以是實作響應的緩存,通過上圖我們可以看到

1a: 一個請求到達nginx,先從緩存中嘗試擷取

1b: 緩存不存在直接去上遊伺服器擷取資料

1c: 上遊伺服器傳回響應,nginx把響應放入到緩存

1d: 把響應傳回到用戶端

2a: 另一個請求達到nginx, 到緩存中查找

2b: 緩存中有對應的資料,直接傳回,不去上遊伺服器擷取資料

nginx的緩存常用配置:

<code>proxy_cache_path</code>: 放置緩存響應和共享的目錄。<code>levels</code> 設定緩存檔案目錄層次, levels=1:2 表示兩級目錄,最多三層,其中 1 表示一級目錄使用一位16進制作為目錄名,2 表示二級目錄使用兩位16進制作為目錄名,如果檔案都存放在一個目錄中,檔案量大了會導緻檔案通路變慢。<code>keys_zone</code>設定緩存名字和共享記憶體大小,<code>inactive</code> 當被放入到緩存後如果不被通路的最大存活時間,<code>max_size</code>設定緩存的最大空間

<code>proxy_cache</code>: 定義響應應該存放到哪個緩存區中(<code>keys_zone</code>設定的名字)

<code>proxy_cache_key</code>: 設定緩存使用的key, 預設是完整的通路url,可以自己根據實際情況設定

<code>proxy_cache_lock</code>: 當多個用戶端同時通路一下url時,如果開啟了這個配置,那麼隻會有一個用戶端會去上遊伺服器擷取響應,擷取完成後放入到緩存中,其他的用戶端會等待從緩存中擷取。

<code>proxy_cache_lock_timeout</code>: 啟用了<code>proxy_cache_lock</code>之後,如果第一個請求超過了<code>proxy_cache_lock_timeout</code>設定的時間預設是5s,那麼所有等待的請求會同時到上遊伺服器去擷取資料,可能會導緻後端壓力增大。

<code>proxy_cache_min_uses</code>: 設定資源被請求多少次後才會被緩存

<code>proxy_cache_use_stale</code>: 在通路上遊伺服器發生錯誤時,傳回已經過期的資料給用戶端;當緩存内容對于過期時間不敏感,可以選擇采用這種方式

<code>proxy_cache_valid</code>: 為不同響應狀态碼設定緩存時間。如果設定<code>proxy_cache_valid 5s</code>,那麼所有的狀态碼都會被緩存。

設定所有的響應被緩存後最大不被通路的存活時間6小時,緩存的大小設定為1g,緩存的有效期是1天,配置如下:

如果目前響應中設定了set-cookie頭資訊,那麼目前的響應不會被緩存,可以通過使用<code>proxy_ignore_headers</code>來忽略頭資訊以達到緩存

如果這樣做了,我們需要把cookie中的值作為<code>proxy_cache_key</code>的一部分,防止同一個url響應的資料不同導緻緩存資料被覆寫,傳回到用戶端錯誤的資料

注意,這種情況還是有問題,因為在緩存的key中添加cookie資訊,那麼可能導緻公共資源被緩存多份導緻浪費空間;要解決這個問題我們可以把不同的資源分開配置,比如:

雖然我們設定了緩存加快了響應,但是有時候會遇到緩存錯誤的請求,通常我們需要為自己開一個後面,友善發現問題之後通過手動的方式及時的清理掉緩存。nginx可以考慮使用<code>ngx_cache_purge</code>子產品進行緩存清理。

該方法要限制通路權限; <code>proxy_cache_purge</code>緩存清理的子產品,<code>cache_one</code>指定的key_zone,<code>$host$1$is_args$args</code> 指定的生成緩存key的參數

如果有大的靜态檔案,這些靜态檔案基本不會别修改,那麼我們就可以不用給它設定緩存的有效期,讓nginx直接存儲這些檔案直接。如果上遊伺服器修改了這些檔案,那麼可以單獨提供一個程式把對應的靜态檔案删除。

請求首先會去<code>/img</code>中查找檔案,如果不存在再去上遊伺服器查找;<code>internal</code> 指令用于指定隻允許來自本地 nginx 的内部調用,來自外部的通路會直接傳回 404 not found 狀态。<code>proxy_store</code>表示需要把從上遊伺服器傳回的檔案存儲到 <code>/var/www/data</code>; <code>proxy_store_access</code>設定通路權限

<code>cache-control</code>和<code>expires</code> 設定資源緩存的有效期

使用<code>last-modified / if-modified-since</code>判斷緩存是否有效

使用<code>if-none-match / etag</code>判斷緩存是否有效

通過配置nginx緩沖區大小對nginx調優

使用nginx緩存加快請求響應速度

如何加快請求響應的速度,本篇我們主要圍繞着http緩存和nignx反向代理兩個方面來聊了緩存,你以為這樣就完了嗎,不!下一篇我們将從應用程式的次元來聊聊緩存

文中或許會存在或多或少的不足、錯誤之處,有建議或者意見也非常歡迎大家在評論交流。

最後,白嫖不好,創作不易,希望朋友們可以點贊評論關注三連,因為這些就是我分享的全部動力來源

繼續閱讀