6

ASP.NET OutputCache 快取行為深入觀察

 3 years ago
source link: https://blog.darkthread.net/blog/aspnet-outputcache-experiment/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
ASP.NET OutputCache 快取行為深入觀察-黑暗執行緒

ASP.NET WebForm 及 ASP.NET MVC 支援 OutputCache 機制,針對不會頻繁變化的 aspx 或 MVC Action,可將輸出結果快取在伺服器端重複使用,同時還能控制瀏覽器的快取行為,藉此提高系統負載量。而 OutputCache 所提供的 Location、NoStore 等參數,將如何影響 HTTP Cache-Control Header 及伺服器快取行為?即是本篇文章要探討的重點,我們將透過簡單的實驗深入觀察 ASP.NET 輸出快取行為。

我們先建一個 ASP.NET MVC 專案,在 Home/Index Action 傳回執行當下分、秒數,測試時間隔兩秒呼叫兩次,比對傳回數字即可判斷回應為即時產生或來自伺服器快取:

using System.Web.Mvc;

namespace OutputCacheTest.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return Content($"{DateTime.Now:mmss}");
        }
    }
}

接著是測試程式,PowerShell 產生包含隨機參數的 URL,相隔一秒存取同一 URL,觀察傳回內容及 Cache 相關 Header:

$url = "http://localhost/OutputCacheTest/?rnd=$([System.DateTime]::Now.ToString("mmssfff"))"
function RunTest {
    $resp = (Invoke-WebRequest -Uri $url) 
    Write-Host " > $($resp.Content)" -ForegroundColor Yellow
    $resp.Headers.Keys | Where-Object { !$_.StartsWith('X-') } | ForEach-Object { 
        Write-Host " # $($_): $($resp.Headers[$_])" -ForegroundColor Gray
    }
}

Write-Host "URL = $url" -ForegroundColor White

Write-Host "First Request" -ForegroundColor Cyan
RunTest
Write-Host

Start-Sleep -Seconds 1

Write-Host "Second Request" -ForegroundColor Cyan
RunTest

在預設情況下,ASP.NET 預設的快取政策為 Cache-Control: private,允許瀏覽器快取其內容,但伺服器端沒有快取,二次取得分秒數不同:

接著我們來觀察 OutputCache 的效果。

ASP.NET MVC 提供 OutputCacheAttribute 可指定快取行為 ( 若要用在 ASP.NET 則是寫成 <%@ OutputCache Duration="60" VaryByParam="None"%> 參考 )。OutpuCacheAttribute 有以下屬性可控制快取行為:

  • Duration 快取保留時間(以秒為單位)
  • Location 快取儲存位置 參考
    Any - 可任意快取,包含瀏覽器、Proxy 及 ASP.NET 所在伺服器
    Client - 只可快取在瀏覽器用戶端
    Downstream - 可被快存在瀏覽器及 Proxy,但 ASP.NET 端不做快取
    None - 停用 OutputCache
    Server - 只在 ASP.NET 端快取,禁止瀏覽器及 Proxy 快取
    ServerAndClient - 只會快取在 ASP.NET 伺服器及瀏覽器,不包含 Proxy
  • NoStore 不允許被快取
  • ProfileName 由 config 設定決定快取行為
  • VaryByContentEncodingVaryByHeaderVaryByParamVaryByCustom
    依內容編碼、Header、Form & Query 參數或客製化條件產生多組快取內容。
    【延伸閱讀】
    * Output Caching in MVC
    * ASP.NET MVC 針對電腦與手機提供不同 OutputCache
    * 克服 VaryByParam 不支援 JSON 形式參數問題 by 軟體廚房

將 Duration 等設定寫死在 [OutputCache(Duration = 300... )] 的缺點是同一政策常會套用在多個 Action,搞出一大堆噁心的複製貼上;二則快取策略常需要滾動式修正,若調整需要改程式碼重新編譯上版,有點自找麻煩。因此,實務上較常見的做法是寫成 [OutputCache(CacheProfile = "CacheProfileName")] 指定採用的 OutputCacheProfile,而 OutputCacheProfile 則在 web.config system.web/caching 定義,要調整時改 config 即可:

<system.web>
    <caching>
        <outputCache enableOutputCache="true" />
        <outputCacheSettings>
            <outputCacheProfiles>
                <add name="StdCachePolicy" duration="300"></add>
            </outputCacheProfiles>
        </outputCacheSettings>
    </caching>
</system.web>

先看最基本的五分鐘快取設定 duration="300",其餘參數省略,當成對照組。

如上圖,快取控制 Header 為 Cache-Control: public, max-age=300,第二次為 max-age=298,而 Last-Modified 是 MVC Action 實際執行時間,Expires 則為過期時間,等於 Last-Modified 加 5 分鐘,兩次的 Last-Modified 與 Expires 值相同。(關於 Last-Modified、max-age 的觀察可參考 IIS HTML 檔 Cache 行為觀察)

實驗一 duration="300" location="Any"
兩次 Cache-Control 分別為 public, max-age=300 及 public, max-age=298,傳回內容相同,表示沿用伺服器端快取內容

實驗二 duration="300" location="Client"
兩次 Cache-Control 都是 private, max-age=300,兩次傳回內容不同,未使用伺服器端快取內容

實驗三 duration="300" location="Downstream"
兩次 Cache-Control 均為 public, max-age=300,兩次傳回內容不同,未使用伺服器端快取內容

實驗四 duration="300" location="None"
兩次 Cache-Control 均為 no-cache,並多了 Pragma: no-cache, Expires: -1,兩次傳回內容不同,未使用伺服器端快取內容

實驗五 duration="300" location="Server"
兩次均為 Cache-Control: no-cache, Pragma: no-cache, Expires: -1,兩次傳回內容相同,表示沿用伺服器端快取內容

實驗六 duration="300" location="ServerAndClient"
兩次 Cache-Control 分別為 private, max-age=300 及 private, max-age=298,兩次傳回內容相同,表示沿用伺服器端快取內容

實驗七 duration="0" noStore="true"
兩次均為 Cache-Control: public, no-store, max-age=0,兩次傳回內容不同,未使用伺服器端快取內容

將觀察結果歸納如以下:

參數Cache-Control其他 Header伺服器端
快取瀏覽器
快取Proxy
快取未指定publicYYYlocation="Any"publicYYYlocation="ServerAndClient"privateYYNlocation="Downstream"publicNYYlocation="Client"privateNYNlocation="None"no-cachePragma: no-cache,
Expires: -1NNNlocation="Server"no-cachePragma: no-cache,
Expires: -1YNNnoStore ="true"public, no-store, max-age=0NNN

透過 location,我們可以控制要不要啟用伺服器端快取,Cache-Control 要設 public、private 還是 no-cache,如要 no-store 則是使用 noStore="true" 控制。

另外,設定 <outputCache enableOutputCache="false"></outputCache> 會停用 OutputCache 機制,形同未宣告 [OutputCache()] 時的結果。(如第一張圖,Cache-Control: private)

最後補充 VaryByParam 參數,實測發現雖然 web.config 可以設定 varyByParam,但不會發揮作用,寫 <add name="StdCachePolicy" duration="300" varByParam="None"></add>,?rnd= 參數不同時無法沿用伺服器端的 Cache,但若將 Home/Index Action 改成 '''[OutputCache(CacheProfile = "StdCachePolicy", VaryByParam = "None")]''' 之後,如下圖所示,兩次 ?rnd= 參數不同但仍讀到同一份 OutputCache 內容。

了解上述規則,我們就能更精準控制 ASP.NET 快取行為囉~~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK