在圖靈社群實現快取他人頭像,而對本人不快取

溫謙發表於2013-04-03

在圖靈社群的開發中有這樣一個情況:

會員的頭像有三種大小,由於檔案都很小,就選擇存在資料庫中了。每個頁面有大量的 GetAvatar(id, size) 請求,對應於 http://www.ituring.com.cn/users/getavatar/82554?size=small,根據會員Id和要求的尺寸這兩個引數,返回的相應的頭像檔案。

頭像很少變化,並且某個人的頭像換了,其他的人晚一點看到新頭像,不影響使用,因此可以使用快取。但是如果某個會員自己重新上傳了頭像,那麼必然要求能立刻看到新頭像,而不能等到若干小時之後快取更新以後才看到,這是一個必須的約束條件。

此外,這個快取要同時考慮 Server 端快取和 Browser 端的快取。

這裡要說明的主要是基於在 ASP.NET 框架本身的實現基於使用者的差異快取的方法,並不是討論這樣處理一個網站的頭像檔案儲存應該怎麼做更好的問題。這裡僅用頭像儲存作為一個具體的案例。

Step 1

在 GetAvatar() 方法上使用 OutputCacheAttribute 特性,並指定快取的時間為 1 天:

[OutputCache(Duration= 3600*24)]
public ActionResult GetAvatar(int id, AvatarSize size)
{
    // 省略
 }

Step 2

Step 1 中實現了基本的快取功能,同時在 Server 和 Browser 端都有效。但是問題在於,如果一個會員在其個人空間更新了頭像,那麼他也要等到下次更新的時候才能看到新頭像。因此使用OutputCacheAtribute的VaryByCustom引數:

[OutputCache(Duration= 3600*24, VaryByCustom="getAvatar")]
public ActionResult GetAvatar(int id, AvatarSize size)
{
    // 省略
 }

並在 Global.asax.cs 中重寫 GetVaryByCustomString:

public override string GetVaryByCustomString(HttpContext context, string arg)
{
    if (arg == "getAvatar")
    {
        var id = context.Request.Cookies["iTuringUserId"];

        return id != null && id.Value == Request.Path.Split('/')[3]
            ? DateTime.Now.Ticks.ToString()
            : context.Request.Path.Split('/')[3] + Request.Params["size"];
     }

    return base.GetVaryByCustomString(context, arg);
}

這個函式在呼叫 GetAvatar() 方法之前呼叫,OutputCacheAttribute 會根據你返回的值決定快取的版本。因此,在這個函式中,將請求的 cookies 中的會員的 Id 和所請求的頭像的 Id 進行比較,如果相同說明是本人的請求,則返回一個隨機數(這裡用時間,效果一樣),這樣每次返回這個值都是新值。因此就會取得新的頭像。如果不同,就返回Id和尺寸的組合,這樣就可以使用快取的版本了。

Step 3

Step 2 中已經可以實現根據會員來決定是否使用快取的頭像,但是剩下的問題是,對於本人的請求,第一次返回的時候,設定了 Browser 上的快取,這個快取設定是統一的,都是1天,這樣會導致某個頭像的本人瀏覽頁面時,由於瀏覽器上設定了快取,而根本不會像伺服器發出請求,從而無法得到新的頭像。

因此需要在返回頭像檔案的時候,根據使用者來決定是否設定HTTP頭的快取:

[OutputCache(Duration= 3600*24, VaryByCustom="getAvatar")]
public ActionResult GetAvatar(int id, AvatarSize size)
{
    if (Request.Cookies["iTuringUserId"] != null 
               && Request.Cookies["iTuringUserId"].Value == id.ToString())
    {
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
    }

    // 省略
}

好了,大功告成!

這樣的結果是,顯示非本人頭像的請求的CPU時間降到了0.1ms,而如果直接從資料庫取出並返回,則需要 50~100ms 。而圖靈社群上,很多頁面都要顯示幾十個頭像,因此這個優化還是很有價值的。

此外,這裡的方法具有通用性,能夠實現針對使用者輸出不同的快取版本。

相關文章