緩存是開發(fā)高性能和高可用性Web應(yīng)用的重要手段之一。作為ASP.NET AJAX的關(guān)鍵功能,從客戶端訪問Script Method會被大量用于使用ASP.NET開發(fā)的AJAX應(yīng)用。以下是這一功能最簡單的例子。
10年的西夏網(wǎng)站建設(shè)經(jīng)驗,針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。成都全網(wǎng)營銷的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整西夏建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)從事“西夏網(wǎng)站設(shè)計”,“西夏網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。
頁面上有一個ScriptManager控件,其ScriptMode屬性被設(shè)為Debug,這樣我們就可以使用Sys.Debug.trace方法向ID為TraceConsole的TextArea元素中添加文本了。當點擊頁面上的按鈕時,我們會連續(xù)訪問6次服務(wù)器端的方法以獲得服務(wù)器端的時間,每兩次請求之間將會有一個3秒的間隔。服務(wù)器端的方法定義如下:
[ScriptService] public class CacheService : System.Web.Services.WebService { [WebMethod] public DateTime GetServerTime() { return DateTime.Now; } }
打開頁面,點擊按鈕,我們可以在頁面中看到如下的結(jié)果:
服務(wù)器端緩存在ASP.NET AJAX中訪問Script Method的功能有一個內(nèi)置的服務(wù)器端緩存能力,但是似乎很少有人用到它——它的確有一定的局限性。大部分的開發(fā)人員都會將數(shù)據(jù)存放到HttpContext.Cache對象或者其他一些地方,然后在需要時從緩存中重新獲取對象。這是開發(fā)ASP.NET應(yīng)用時最常用的緩存方式之一,但是我們有時可以用一種更加方便,更加有效的方法。請看下面的代碼是如何打開這個功能的。
[WebMethod(CacheDuration=10)] public DateTime GetServerTime() { return DateTime.Now; }
就像使用ASP.NET開發(fā)Web Services一樣,我們可以使用相同的方法來讓ASP.NET為相同資源并且有相同參數(shù)的請求緩存輸出,而我們只需要設(shè)置WebMethodAttribute的CacheDuration屬性即可。在上面的代碼片斷中,方法的結(jié)果會被調(diào)用10秒鐘。我們可以從System.Web.Extenssions.dll中System.Web.Script.Services.RestHandler類的InitializeCachePolicy靜態(tài)方法中得知到底發(fā)生了什么事情:
private static void InitializeCachePolicy(WebServiceMethodData methodData, HttpContext context) { int cacheDuration = methodData.CacheDuration; if (cacheDuration > 0) { context.Response.Cache.SetCacheability(HttpCacheability.Server); context.Response.Cache.SetExpires(DateTime.Now.AddSeconds((double) cacheDuration)); context.Response.Cache.SetSlidingExpiration(false); context.Response.Cache.SetValidUntilExpires(true); if (methodData.ParameterDatas.Count > 0) { context.Response.Cache.VaryByParams["*"] = true; } else { context.Response.Cache.VaryByParams.IgnoreParams = true; } } else { context.Response.Cache.SetNoServerCaching(); context.Response.Cache.SetMaxAge(TimeSpan.Zero); } }
當ASP.NET AJAX發(fā)現(xiàn)即將執(zhí)行的方法被設(shè)置了CacheDuration時,它會將HttpCacheability.Server作為參數(shù)調(diào)用當前上下文中HttpCachePolicy的SetCacheability方法,這樣請求的結(jié)果將會被緩存以便將來使用。如果將要執(zhí)行的方法含有參數(shù),那么通過VaryByParams屬性從HttpCachePolicy中得到的HttpCacheVaryByParams對象里“*”這一項將被設(shè)為true,由此可知,ASP.NET會為不同的參數(shù)組合緩存不同的結(jié)果。
我們來看一下緩存的效果:
與通過自己編程來緩存數(shù)據(jù)相比,設(shè)置CacheDuration屬性來緩存結(jié)果的優(yōu)勢就在于使用方式是在簡單?,F(xiàn)在我們就可以將注意力完全放在方法的自身實現(xiàn)上而不用處理緩存中會出現(xiàn)的問題(例如:同步問題)。這個方法也提高了少許性能,因為現(xiàn)在已經(jīng)無需將結(jié)果序列化成JSON對象了,ASP.NET將全權(quán)負責將緩存的數(shù)據(jù)發(fā)送到各客戶端。但是在某些時候由我們來緩存數(shù)據(jù)會更合適一些,因為這樣可以省下服務(wù)器端的資源。例如,以下是一個接受四個參數(shù)的方法,其中第二個參數(shù)表示是否應(yīng)該將剩下的兩個參數(shù)忽略:
public string GetResult(int key, bool ignoreRest, string args1, string args2) { ... }
在這種情況下,幾乎所有的程序員都會在ignoreRest參數(shù)為true的情況下僅僅根據(jù)key參數(shù)的不同值來緩存數(shù)據(jù)。但是ASP.NET無法得知參數(shù)的含義,因此它會為所有的參數(shù)組合形式各緩存一份數(shù)據(jù),而不去關(guān)心它們是否相同。
客戶端緩存我使用HttpWatch Basic Edition來捕獲客戶端和服務(wù)器端之間的通信。這是捕獲結(jié)果的截圖:
每當我們訪問Script Method時,相同的內(nèi)容會被POST到服務(wù)器端,并且得到相同的結(jié)果。盡管結(jié)果被緩存了,我們只是節(jié)省了方法的執(zhí)行時間,但是round-trip的數(shù)量依舊沒有減少。這意味著如果結(jié)果的數(shù)據(jù)量很大,或者帶寬很窄,對用戶來說訪問Script Method依舊是個耗時的過程。因此如果我們能夠在客戶端緩存結(jié)果的話,用戶使用相同參數(shù)訪問方法時就可以立即得到數(shù)據(jù),即使在網(wǎng)絡(luò)中端的情況下。
說干就干。
首先,我們只能使用HTTP GET方法來訪問方法,因為我們需要讓瀏覽器為我們緩存結(jié)果。
[WebMethod] [ScriptMethod(UseHttpGet = true)] public DateTime GetServerTime() { ... }
我們使用ASP.NET中的傳統(tǒng)方法來啟用客戶端緩存功能:
WebMethod] [ScriptMethod(UseHttpGet = true)] public DateTime GetServerTime() { HttpCachePolicy cache = HttpContext.Current.Response.Cache; cache.SetCacheability(HttpCacheability.Private); cache.SetExpires(DateTime.Now.AddSeconds((double)10)); cache.SetMaxAge(new TimeSpan(0, 0, 10)); return DateTime.Now; }
我們將Cacheability設(shè)為Public(Private也是可以的,如果您希望Response只為同一個客戶端緩存,而不能在多個客戶端共享。自然這是為有中間結(jié)點的情況服務(wù)的,例如通過代理服務(wù)器請求資源),并且指定了一個10秒鐘的過期時間。我們同樣調(diào)用了SetMaxAge方法將max-age的值設(shè)為10秒鐘,因為ASP.NET AJAX在這之前已經(jīng)將它設(shè)為了零(TimeSpan.Zero)。讓我們來看一下效果……緩存失敗?我們隨意挑一個Response查看一下它的Header。
Cache-Control public, max-age=0 Date Fri, 29 Jun 2007 00:44:14 GMT Expires Fri, 29 Jun 2007 00:44:24 GMT
問題就在于Cache-Control中的max-age的值被設(shè)為了0。我們已經(jīng)將其設(shè)為了10秒但是它依舊是零的原因則在于HttpCachePolicy中SetMaxAge方法的實現(xiàn)上:
public void SetMaxAge(TimeSpan delta) { if (delta < TimeSpan.Zero) { throw new ArgumentOutOfRangeException("delta"); } if (s_oneYear < delta) { delta = s_oneYear; } if (!this._isMaxAgeSet || (delta < this._maxAge)) { this.Dirtied(); this._maxAge = delta; this._isMaxAgeSet = true; } }
一旦我們調(diào)用了SetMaxAge方法之后,_isMaxAgeSet標記就被設(shè)為了true,它組織_maxAge變量被設(shè)為比當前小的值。當我們在執(zhí)行Script Method時,_isMaxAgeSet標記已經(jīng)是true,并且_maxAge變量的值為Time.Zero,因此我們已經(jīng)不能將其改變成其它的值了(Omar大牛在之前的某篇文章中認為不能改變max-age是因為ASP.NET 2.0的Bug,其實并非這樣)。到了使用反射機制的時候了。我們要做的就是直接改變_maxAge變量的值。
[WebMethod] [ScriptMethod(UseHttpGet = true)] public DateTime GetServerTime() { HttpCachePolicy cache = HttpContext.Current.Response.Cache; cache.SetCacheability(HttpCacheability.Private); cache.SetExpires(DateTime.Now.AddSeconds((double)10)); FieldInfo maxAgeField = cache.GetType().GetField( "_maxAge", BindingFlags.Instance | BindingFlags.NonPublic); maxAgeField.SetValue(cache, new TimeSpan(0, 0, 10)); return DateTime.Now; }
我們檢驗一下緩存的效果:
似乎和之前沒有什么兩樣,但是HttpWatch能夠告訴我們個中區(qū)別:
瀏覽器就不會發(fā)送和接收任何數(shù)據(jù),此時那些Response被真正地緩存了,客戶端與服務(wù)器端的round-trip被節(jié)省了下來。請注意這里的緩存依舊是“參數(shù)相關(guān)”,也就是說我們?nèi)绻褂貌煌膮?shù),我們就會重新從服務(wù)器端獲得新的結(jié)果——因為我們請求時所使用的URL被改變了,不同的參數(shù)在請求時會使用不同的Query String。
緩存成功自然是Response中Header的功勞。請注意Cache-Control中max-age的值:
Cache-Control public, max-age=10 Date Fri, 29 Jun 2007 00:54:32 GMT Expires Fri, 29 Jun 2007 00:54:42 GMT
在客戶端進行緩存的優(yōu)勢就在于它能夠顯著提高Web應(yīng)用的性能。但是它也有一些缺點,其中的問題就在于使用了反射機制,而使用反射操作在很多環(huán)境下是一種被限制的行為。如果您希望希望使用反射機制,則必須選擇以下三種方法之一。很明顯,它們之間的任意一種都不太可能在購買的虛擬主機中使用。
為您的Web應(yīng)用開啟Full Trust。 使用自定義Trust Level,使它包含ReflectionPermission。 將使用反射的代碼放置在單獨的程序集中,并將其在GAC中注冊。在客戶端緩存的另一個缺點就是不同的客戶端在得到緩存效果之前都必須至少訪問服務(wù)器端一次。從這個角度來說,服務(wù)器端的緩存會工作地更有效,因為一旦結(jié)果被緩存了之后就可以被發(fā)送到來自任意客戶端的請求了(自然還是需要有相同的參數(shù)組合)。不過我們可以盡可能的緩解這方面的問題,例如我們可以使用最常用的做法來使用編程方式自行緩存數(shù)據(jù)。
總結(jié)本文談?wù)摿巳N提高訪問Script Method性能的方式,是時候來做一個總結(jié)了。
使用編程方式在服務(wù)器端緩存數(shù)據(jù):這是最靈活的做法,我們可以使用任意的策略來緩存數(shù)據(jù)。 通過設(shè)置CacheDuration屬性來緩存數(shù)據(jù):這是最容易的做法,方法在執(zhí)行一次之后就會被緩存,ASP.NET會負責將其自動返回給所有帶有相同參數(shù)的請求,無論它來自哪個客戶端,使用哪種HTTP方法來請求。不過在某些時候,這個緩存方式不是那么有效率,因為ASP.NET會緩存許多我們事實上不需要的重復數(shù)據(jù)。 在客戶端進行緩存:這是能夠限度提高性能的方法,一旦結(jié)果被緩存之后就能夠避免客戶端與服務(wù)器端的通信。不過不同的客戶端在得到緩存效果之前至少需要訪問一次服務(wù)器端的方法。