Asp.Net MVC 快取
快取是一種儲存資源副本並在下次請求時直接使用該副本的技術。當 web 快取發現請求的資源已經被儲存,它會攔截請求,返回該資源的拷貝。
Web應用快取技術大體上可以分為兩類:服務端快取和客戶端快取。兩種目標都是減少重複性內容的生成和網路傳輸工作,因為快取資料儲存的位置不同,而分為服務端快取和客戶端快取。
服務端快取
服務端快取技術關注於服務端資料查詢,生成或者操作技術。主要就是減少處理請求的工作量,減少資料庫查詢次數和生成HTML資料的CPU週期--減少每個bit的資料。
對於服務端快取來說,不管是重新整理頁面,重新輸入地址,還是Control+F5都不會規避快取,如果快取資料有效,一定是請求的快取資料。
輸出快取(Output Cache)
輸出快取是Asp.Net下最常用的快取機制。輸出快取,快取服務端生成的HTML資料--快取Action下返回資料(Html/Json)。這樣,在每次呼叫相同的Action時,就不需要再次執行Action方法。
快取位置(Location)
OutputCache
使快取的內容一般放在三個位置上:服務端,代理伺服器,瀏覽器客戶端。通過Loaction
屬性可以設定快取的位置。
Loaction
屬性有如下值:
- Any
- Client
- Downstream
- Server
- None
- ServerAndClient
預設值為Any
,就是在三個位置都會快取。但是應該根據不同的情況使用不同的快取位置。比如:要快取的內容是針對特定使用者的,每個使用者都會不同。這樣的話,該快取就不能儲存在伺服器上。應該儲存在瀏覽器客戶端上。
使用Output Cache
在Controller
或者Action
上新增[OutputCache]
特性,使得被新增的Controller
或Action
可以快取返回的資料。(在Action
新增會快取當前的Action
,在Controller
會快取該Controller
下的所有Action
)
如下程式碼:當第一次方法該Action時,開始計時10秒,此10秒內所有訪問該Action的請求都會請求快取資料。當10秒結束後,再重新開始等待新一次請求,開始新的10秒快取。就是每隔10秒丟掉舊快取,等待新的請求,更新快取資料。
using System.Web.Mvc;
using System.Web.UI;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
//快取時間10秒,快取變數為無,快取位置為服務端
[OutputCache(Duration=10, VaryByParam="none", Location = OutputCacheLocation.Server)]
public ActionResult Index()
{
return View();
}
}
}
View:
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>@DateTime.Now.ToString()</p>
點選F12,檢視請求
需要注意的是:
- 該快取時間是絕對時間。
- 此快取是對所有訪問該頁面的使用者都有效。
- 不能保證快取一定有效。當記憶體資源不夠時,快取就會自動地將沒用的或者優先順序低快取清除。
客戶端快取
除了服務端快取外,客戶端也可以快取資料。它避免了向伺服器重複提交獲取重複資料的請求,把一些重複資料快取到本地。服務端快取是為了更快的處理客戶端請求,而客戶端快取則是為了避免不必要的請求。
(瀏覽器會自動把靜態資源快取到瀏覽器)
MVC中指定Location
值為OutputCacheLocation.Client
使快取在瀏覽器客戶端上。
using System.Web.Mvc;
using System.Web.UI;
namespace MvcApplication1.Controllers
{
public class BadUserController : Controller
{
//快取時間為10秒,快取引數為無,快取位置為客戶端
[OutputCache(Duration = 10, VaryByParam = "none",Location = OutputCacheLocation.Client)]
public ActionResult ClientCache()
{
return View();
}
}
}
@{
ViewBag.Title = "ClientCache";
}
<h2>ClientCache</h2>
<p>@DateTime.Now.ToString()</p>
客戶端快取和服務端快取不一樣。
重新整理,重新輸入地址,和Control+F5都有可能破壞客戶端快取,從服務端重新獲取資料。
瀏覽器重新整理,和重新輸入地址會避免請求該URL頁面的客戶端快取,只避免請求該URL頁面的快取。(如果該頁面有其他URL是被客戶端快取的,這些資源或頁面的快取不會被避免)。
那麼什麼情況下客戶端快取才有效?
通過URL訪問,客戶端快取才有效。
(頁面的靜態資源)
比如:
- 頁面A是客戶端快取,同時頁面A有一個跳向頁面B的連結。
- 通過A到達頁面B,同時頁面B也有一個連結,這個連結跳向A。
- 通過B再次訪問A,此時頁面A獲取的資料就是客戶端的快取資料,並沒有請求服務端,是沒有請求服務端。不是304,status-code依然是200。
Status-Code:304/200(from cache)
304
只有當客戶端和服務端同時都快取了資料。且快取沒有更新的時候,才會有304。即這個快取是要到服務端驗證(根據ETag和If-Modify-Since),該快取是否最新。如果要更新快取,從服務端獲取資料,status code:200,否則status code:304.
304 和200(from cache)區別
304是會到服務端去校驗一次當前客戶端快取是否有效(根據ETag和If-Modify-Since)。而200(from cache)則沒有向服務端校驗,也沒有向服務端請求,直接使用了客戶端快取。
有時我們又需要避免這種沒有向服務端請求,直接使用快取的情況。解決辦法就是更改這個快取的url,新增一個版本號或唯一值。這樣因為url的更改使得在客戶端沒有對應的url快取,就會從服務端重新獲取,再快取該URL的資料。
不同內容的輸出快取
之前的快取都是Action返回相同的內容。如果Action每次返回的內容不同,那又該怎麼快取這些不同的內容呢?
使用OutputCache
特性的VaryByParam
屬性來解決這個問題。當表單引數或查詢字串引數變化時,該屬效能夠建立同一個Action下不同的快取。
如下程式碼:Master 獲取列表。Details 獲取列表中選擇項的詳細內容。通過使用VaryByParam
來快取不同的id
的列表項的詳細內容。
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class MoviesController : Controller
{
public MoviesController()
{
}
[OutputCache(Duration=int.MaxValue, VaryByParam="none")]
public ActionResult Master()
{
//獲取列表
return View();
}
[OutputCache(Duration = int.MaxValue, VaryByParam = "id")]
public ActionResult Details(int id)
{
//根據引數id,從資料庫中獲取指定詳細內容,並快取該內容。不同的id會得到不同的內容,自然也會有快取。
//但是如果設定VaryByParam="none"那麼不管id是多少,都直接從快取中獲取資料,不執行該Action,這樣就會只返回第一次選擇項的資料。
return View();
}
}
}
Details()
操作包括一個帶有值“Id”的VaryByParam
屬性。當將Id引數的不同值傳遞給控制器操作時,將生成不同的快取內容。
VaryByParam
可以根據引數快取不同的內容
- 當
VaryByParam="*"
: 每當表單或查詢字串引數變化時,建立一個不同的快取版本。 - 當
VaryByParam="none"
: 不建立不同的快取內容,不根據引數快取不同的內容,即只有一個內容的快取。 - 當
VaryByParam="引數列表"
: 為不同的引數建立不同的快取版本。
快取配置
除了在OutputCache
特性上直接配置快取策略,可以在web.config
檔案中使用快取配置檔案,同一管理快取的策略。使用配置檔案相比直接使用屬性有如下幾點好處:
可以實現一次定義,多處使用。
可以修改web配置檔案,而無需重新編譯應用程式。(如果想把已經部署到生產環境中的應用程式禁用快取,可以修改web配置檔案中定義的快取配置。對web配置檔案的任何更改都將被自動檢測並應用。)
例如,web.config部分定義了一個名為“cache1Hour”的快取配置檔案。使用該配置項時,只需指定CacheProfile=配置項名稱即可。
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="Cache1Hour" duration="3600" varyByParam="none"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
using System;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class ProfileController : Controller
{
//配置檔案中的快取策略名稱賦值給CacheProfile
[OutputCache(CacheProfile="Cache1Hour")]
public string Index()
{
return DateTime.Now.ToString();
}
}
}
簡單介紹下Http快取的頭相關資訊:
訊息頭 | 值 | 型別 | 說明 |
---|---|---|---|
Expires | Thu, 30 Nov 2017 08:21:14 GMT | 響應 | 過期時間,為格林威治時間 (GMT) |
Pragma | no-cache | 響應 | 忽略瀏覽器快取(Http1.1用Cache-Control代替) |
Cache-Control | no-cache | 請求/響應 | 客戶端快取驗證 |
Cache-Control | no-store | 請求/響應 | 不在任何地方儲存資料,不允許被快取 |
Cache-Control | max-age=[秒] | 請求/響應 | 設定瀏覽器快取最長時間 |
Cache-Control | public | 響應 | 快取在任何地方 |
Cache-Control | private | 響應 | 快取該使用者的瀏覽器 |
Last-Modified | Thu, 30 Nov 2017 08:21:14 GMT | 響應 | 告訴瀏覽器服務端最後一次修改的時間 |
If-Modified-Since | Thu, 30 Nov 2017 08:21:14 GMT | 請求 | 如果瀏覽器中Last-Modofied有值,在請求中把值給If-Modified-Since,提交給服務端 |
ETag | 3df04c15b968d31:0 | 響應 | 該資源及其版本在服務端的唯一標識 |
If-None-Match | 3df04c15b968d31:0 | 請求 | 把上次請求中獲取到的ETag值,賦值給If-None-Match並提交給服務端 |
Vary | Accept-Encoding | 響應 | 從多個快取副本中選擇匹配的版本 |
有幾個容易理解錯誤的點
no-cache
: 使用no-cache
指令的目的是為了防止從快取中使用過期的資源,所以每次使用快取時都要到服務端去驗證。從字面意思上很容易把no-cache
誤解成為不快取,但事實上no-cache
代表不快取過期的資源,快取會向源伺服器進行有效期確認後處理資源。
no-store
: 不儲存客戶端相關請求或伺服器響應的任何內容,即真正的不快取。
如有不對,請多多指教。
參考:
- Asp.Net MVC4 web程式設計
- Docs-Asp.Net