隨筆 - 30, 文章 - 0, 評論 - 9, 引用 - 0
          數(shù)據(jù)加載中……

          在PetShop 4.0中ASP.NET緩存的實現(xiàn)

          PetShop作為一個B2C的寵物網(wǎng)上商店,需要充分考慮訪客的用戶體驗,如果因為數(shù)據(jù)量大而導(dǎo)致Web服務(wù)器的響應(yīng)不及時,頁面和查詢數(shù)據(jù)遲遲得不到結(jié)果,會因此而破壞客戶訪問網(wǎng)站的心情,在耗盡耐心的等待后,可能會失去這一部分客戶。無疑,這是非常糟糕的結(jié)果。因而在對其進行體系架構(gòu)設(shè)計時,整個系統(tǒng)的性能就顯得殊為重要。然而,我們不能因噎廢食,因為專注于性能而忽略數(shù)據(jù)的正確性。在PetShop 3.0版本以及之前的版本,因為ASP.NET緩存的局限性,這一問題并沒有得到很好的解決。PetShop 4.0則引入了SqlCacheDependency特性,使得系統(tǒng)對緩存的處理較之以前大為改觀。

          4.3.1  CacheDependency接口

          PetShop 4.0引入了SqlCacheDependency特性,對Category、Product和Item數(shù)據(jù)表對應(yīng)的緩存實施了SQL Cache Invalidation技術(shù)。當(dāng)對應(yīng)的數(shù)據(jù)表數(shù)據(jù)發(fā)生更改后,該技術(shù)能夠?qū)⑾嚓P(guān)項從緩存中移除。實現(xiàn)這一技術(shù)的核心是SqlCacheDependency類,它繼承了CacheDependency類。然而為了保證整個架構(gòu)的可擴展性,我們也允許設(shè)計者建立自定義的CacheDependency類,用以擴展緩存依賴。這就有必要為CacheDependency建立抽象接口,并在web.config文件中進行配置。

          在PetShop 4.0的命名空間PetShop.ICacheDependency中,定義了名為IPetShopCacheDependency接口,它僅包含了一個接口方法:
          public interface IPetShopCacheDependency
          {      
              AggregateCacheDependency GetDependency();
          }

          AggregateCacheDependency是.Net Framework 2.0新增的一個類,它負(fù)責(zé)監(jiān)視依賴項對象的集合。當(dāng)這個集合中的任意一個依賴項對象發(fā)生改變時,該依賴項對象對應(yīng)的緩存對象都將被自動移除。
          AggregateCacheDependency類起到了組合CacheDependency對象的作用,它可以將多個CacheDependency對象甚至于不同類型的CacheDependency對象與緩存項建立關(guān)聯(lián)。由于PetShop需要為Category、Product和Item數(shù)據(jù)表建立依賴項,因而IPetShopCacheDependency的接口方法GetDependency()其目的就是返回建立了這些依賴項的AggregateCacheDependency對象。

          4.3.2  CacheDependency實現(xiàn)

          CacheDependency的實現(xiàn)正是為Category、Product和Item數(shù)據(jù)表建立了對應(yīng)的SqlCacheDependency類型的依賴項,如代碼所示:
          public abstract class TableDependency : IPetShopCacheDependency
          {
              // This is the separator that's used in web.config
              protected char[] configurationSeparator = new char[] { ',' };

              protected AggregateCacheDependency dependency = new AggregateCacheDependency();
              protected TableDependency(string configKey)
              {
                  string dbName = ConfigurationManager.AppSettings["CacheDatabaseName"];
                  string tableConfig = ConfigurationManager.AppSettings[configKey];
                  string[] tables = tableConfig.Split(configurationSeparator);

                  foreach (string tableName in tables)
                      dependency.Add(new SqlCacheDependency(dbName, tableName));
              }
              public AggregateCacheDependency GetDependency()
             {
                  return dependency;
              }
          }

          需要建立依賴項的數(shù)據(jù)庫與數(shù)據(jù)表都配置在web.config文件中,其設(shè)置如下:
          <add key="CacheDatabaseName" value="MSPetShop4"/>
          <add key="CategoryTableDependency" value="Category"/>
          <add key="ProductTableDependency" value="Product,Category"/>
          <add key="ItemTableDependency" value="Product,Category,Item"/>

          根據(jù)各個數(shù)據(jù)表間的依賴關(guān)系,因而不同的數(shù)據(jù)表需要建立的依賴項也是不相同的,從配置文件中的value值可以看出。然而不管建立依賴項的多寡,其創(chuàng)建的行為邏輯都是相似的,因而在設(shè)計時,抽象了一個共同的類TableDependency,并通過建立帶參數(shù)的構(gòu)造函數(shù),完成對依賴項的建立。由于接口方法GetDependency()的實現(xiàn)中,返回的對象dependency是在受保護的構(gòu)造函數(shù)創(chuàng)建的,因此這里的實現(xiàn)方式也可以看作是Template Method模式的靈活運用。例如TableDependency的子類Product,就是利用父類的構(gòu)造函數(shù)建立了Product、Category數(shù)據(jù)表的SqlCacheDependency依賴:
          public class Product : TableDependency
          {
              public Product() : base("ProductTableDependency") { }
          }

          如果需要自定義CacheDependency,那么創(chuàng)建依賴項的方式又有不同。然而不管是創(chuàng)建SqlCacheDependency對象,還是自定義的CacheDependency對象,都是將這些依賴項添加到AggregateCacheDependency類中,因而我們也可以為自定義CacheDependency建立專門的類,只要實現(xiàn)IPetShopCacheDependency接口即可。

          4.3.3  CacheDependency工廠

          繼承了抽象類TableDependency的Product、Category和Item類均需要在調(diào)用時創(chuàng)建各自的對象。由于它們的父類TableDependency實現(xiàn)了接口IPetShopCacheDependency,因而它們也間接實現(xiàn)了IPetShopCacheDependency接口,這為實現(xiàn)工廠模式提供了前提。

          在PetShop 4.0中,依然利用了配置文件和反射技術(shù)來實現(xiàn)工廠模式。命名空間PetShop.CacheDependencyFactory中,類DependencyAccess即為創(chuàng)建IPetShopCacheDependency對象的工廠類:
          public static class DependencyAccess
          {       
              public static IPetShopCacheDependency CreateCategoryDependency()
              {
                  return LoadInstance("Category");
              }
              public static IPetShopCacheDependency CreateProductDependency()
              {
                  return LoadInstance("Product");
              }
              public static IPetShopCacheDependency CreateItemDependency()
              {
                  return LoadInstance("Item");
              }
              private static IPetShopCacheDependency LoadInstance(string className)
              {
                  string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
                  string fullyQualifiedClass = path + "." + className;
                  return (IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);
              }
          }
          整個工廠模式的實現(xiàn)如圖4-3所示:

          4-3.gif
           圖4-3 CacheDependency工廠

          雖然DependencyAccess類創(chuàng)建了實現(xiàn)了IPetShopCacheDependency接口的類Category、Product、Item,然而我們之所以引入IPetShopCacheDependency接口,其目的就在于獲得創(chuàng)建了依賴項的AggregateCacheDependency類型的對象。我們可以調(diào)用對象的接口方法GetDependency(),如下所示:
          AggregateCacheDependency dependency = DependencyAccess.CreateCategoryDependency().GetDependency();

          為了方便調(diào)用者,似乎我們可以對DependencyAccess類進行改進,將原有的CreateCategoryDependency()方法,修改為創(chuàng)建AggregateCacheDependency類型對象的方法。

          然而這樣的做法擾亂了作為工廠類的DependencyAccess的本身職責(zé),且創(chuàng)建IPetShopCacheDependency接口對象的行為仍然有可能被調(diào)用者調(diào)用,所以保留原有的DependencyAccess類仍然是有必要的。

          在PetShop 4.0的設(shè)計中,是通過引入Facade模式以方便調(diào)用者更加簡單地獲得AggregateCacheDependency類型對象。

          4.3.4  引入Facade模式

          利用Facade模式可以將一些復(fù)雜的邏輯進行包裝,以方便調(diào)用者對這些復(fù)雜邏輯的調(diào)用。就好像提供一個統(tǒng)一的門面一般,將內(nèi)部的子系統(tǒng)封裝起來,統(tǒng)一為一個高層次的接口。一個典型的Facade模式示意圖如下所示:

          4-4.gif
          圖4-4 Facade模式

          Facade模式的目的并非要引入一個新的功能,而是在現(xiàn)有功能的基礎(chǔ)上提供一個更高層次的抽象,使得調(diào)用者可以直接調(diào)用,而不用關(guān)心內(nèi)部的實現(xiàn)方式。以CacheDependency工廠為例,我們需要為調(diào)用者提供獲得AggregateCacheDependency對象的簡便方法,因而創(chuàng)建了DependencyFacade類:
          public static class DependencyFacade
          {
              private static readonly string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
              public static AggregateCacheDependency GetCategoryDependency()
              {
                  if (!string.IsNullOrEmpty(path))
                      return DependencyAccess.CreateCategoryDependency().GetDependency();
                  else
                      return null;
              }
              public static AggregateCacheDependency GetProductDependency()
              {
                  if (!string.IsNullOrEmpty(path))
                      return DependencyAccess.CreateProductDependency().GetDependency();
                  else
                      return null;
                  }
              public static AggregateCacheDependency GetItemDependency()
              {
                  if (!string.IsNullOrEmpty(path))
                      return DependencyAccess.CreateItemDependency().GetDependency();
                  else
                      return null;
              }
          }

          DependencyFacade類封裝了獲取AggregateCacheDependency類型對象的邏輯,如此一來,調(diào)用者可以調(diào)用相關(guān)方法獲得創(chuàng)建相關(guān)依賴項的AggregateCacheDependency類型對象:
          AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();

          比起直接調(diào)用DependencyAccess類的GetDependency()方法而言,除了方法更簡單之外,同時它還對CacheDependencyAssembly配置節(jié)進行了判斷,如果其值為空,則返回null對象。

          在PetShop.Web的App_Code文件夾下,靜態(tài)類WebUtility的GetCategoryName()和GetProductName()方法調(diào)用了DependencyFacade類。例如GetCategoryName()方法:
          public static string GetCategoryName(string categoryId)
          {
               Category category = new Category();
               if (!enableCaching)
                      return category.GetCategory(categoryId).Name;

               string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);

               // 檢查緩存中是否存在該數(shù)據(jù)項;
               string data = (string)HttpRuntime.Cache[cacheKey];
               if (data == null)
               {
                     // 通過web.config的配置獲取duration值;
                     int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);
                     // 如果緩存中不存在該數(shù)據(jù)項,則通過業(yè)務(wù)邏輯層訪問數(shù)據(jù)庫獲取;
                     data = category.GetCategory(categoryId).Name;
                     // 通過Facade類創(chuàng)建AggregateCacheDependency對象;
                     AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();
                     // 將數(shù)據(jù)項以及AggregateCacheDependency 對象存儲到緩存中;
                     HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
                }
                return data;
          }

          GetCategoryName()方法首先會檢查緩存中是否已經(jīng)存在CategoryName數(shù)據(jù)項,如果已經(jīng)存在,就通過緩存直接獲取數(shù)據(jù);否則將通過業(yè)務(wù)邏輯層調(diào)用數(shù)據(jù)訪問層訪問數(shù)據(jù)庫獲得CategoryName,在獲得了CategoryName后,會將新獲取的數(shù)據(jù)連同DependencyFacade類創(chuàng)建的AggregateCacheDependency對象添加到緩存中。

          WebUtility靜態(tài)類被表示層的許多頁面所調(diào)用,例如Product頁面:
          public partial class Products : System.Web.UI.Page
          {
              protected void Page_Load(object sender, EventArgs e)
              {
                  Page.Title = WebUtility.GetCategoryName(Request.QueryString["categoryId"]);
              }
          }

          顯示頁面title的邏輯是放在Page_Load事件方法中,因而每次打開該頁面都要執(zhí)行獲取CategoryName的方法。如果沒有采用緩存機制,當(dāng)Category數(shù)據(jù)較多時,頁面的顯示就會非常緩慢。

          4.3.5  引入Proxy模式

          業(yè)務(wù)邏輯層BLL中與Product、Category、Item有關(guān)的業(yè)務(wù)方法,其實現(xiàn)邏輯是調(diào)用數(shù)據(jù)訪問層(DAL)對象訪問數(shù)據(jù)庫,以獲取相關(guān)數(shù)據(jù)。為了改善系統(tǒng)性能,我們就需要為這些實現(xiàn)方法增加緩存機制的邏輯。當(dāng)我們操作增加了緩存機制的業(yè)務(wù)對象時,對于調(diào)用者而言,應(yīng)與BLL業(yè)務(wù)對象的調(diào)用保持一致。也即是說,我們需要引入一個新的對象去控制原來的BLL業(yè)務(wù)對象,這個新的對象就是Proxy模式中的代理對象。

          以PetShop.BLL.Product業(yè)務(wù)對象為例,PetShop為其建立了代理對象ProductDataProxy,并在GetProductByCategory()等方法中,引入了緩存機制,例如:
          public static class ProductDataProxy
          {

              private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings["ProductCacheDuration"]);
              private static readonly bool enableCaching = bool.Parse(ConfigurationManager.AppSettings["EnableCaching"]);
                 
              public static IList
          GetProductsByCategory(string category)
              {
                  Product product = new Product();

                  if (!enableCaching)
                      return product.GetProductsByCategory(category);

                  string key = "product_by_category_" + category;
                  IList data = (IList )HttpRuntime.Cache[key];

                  // Check if the data exists in the data cache
                  if (data == null)
                  {
                      data = product.GetProductsByCategory(category);

                      // Create a AggregateCacheDependency object from the factory
                      AggregateCacheDependency cd = DependencyFacade.GetProductDependency();

                      // Store the output in the data cache, and Add the necessary AggregateCacheDependency object
                      HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(productTimeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
                  }
                  return data;
              }
          }

          與業(yè)務(wù)邏輯層Product對象的GetProductsByCategory()方法相比,增加了緩存機制。當(dāng)緩存內(nèi)不存在相關(guān)數(shù)據(jù)項時,則直接調(diào)用業(yè)務(wù)邏輯層Product的GetProductsByCategory()方法來獲取數(shù)據(jù),并將其與對應(yīng)的AggregateCacheDependency對象一起存儲在緩存中。

          引入Proxy模式,實現(xiàn)了在緩存級別上對業(yè)務(wù)對象的封裝,增強了對業(yè)務(wù)對象的控制。由于暴露在對象外的方法是一致的,因而對于調(diào)用方而言,調(diào)用代理對象與真實對象并沒有實質(zhì)的區(qū)別。

          從職責(zé)分離與分層設(shè)計的角度分析,我更希望這些Proxy對象是被定義在業(yè)務(wù)邏輯層中,而不像在PetShop的設(shè)計那樣,被劃分到表示層UI中。此外,如果需要考慮程序的可擴展性與可替換性,我們還可以為真實對象與代理對象建立統(tǒng)一的接口或抽象類。然而,單以PetShop的表示層調(diào)用來看,采用靜態(tài)類與靜態(tài)方法的方式,或許更為合理。我們需要謹(jǐn)記,“過度設(shè)計”是軟件設(shè)計的警戒線。

          如果需要對UI層采用緩存機制,將應(yīng)用程序數(shù)據(jù)存放到緩存中,就可以調(diào)用這些代理對象。以ProductsControl用戶控件為例,調(diào)用方式如下:
          productsList.DataSource = ProductDataProxy.GetProductsByCategory(categoryKey);

          productsList對象屬于自定義的CustomList類型,這是一個派生自System.Web.UI.WebControls.DataList控件的類,它的DataSource屬性可以接受IList集合對象。
          不過在PetShop 4.0的設(shè)計中,對于類似于ProductsControl類型的控件而言,采用的緩存機制是頁輸出緩存。我們可以從ProductsControl.ascx頁面的Source代碼中發(fā)現(xiàn)端倪:
          <%@ OutputCache Duration="100000" VaryByParam="page;categoryId" %>

          與ASP.NET 1.x的頁輸出緩存不同的是,在ASP.NET 2.0中,為ASP.NET用戶控件新引入了CachePolicy屬性,該屬性的類型為ControlCachePolicy類,它以編程方式實現(xiàn)了對ASP.NET用戶控件的輸出緩存設(shè)置。我們可以通過設(shè)置ControlCachePolicy類的Dependency屬性,來設(shè)置與該用戶控件相關(guān)的依賴項,例如在ProductsControl用戶控件中,進行如下的設(shè)置:
          protected void Page_Load(object sender, EventArgs e)
          {
              this.CachePolicy.Dependency = DependencyFacade.GetProductDependency();
          }

          采用頁輸出緩存,并且利用ControlCachePolicy設(shè)置輸出緩存,能夠?qū)I(yè)務(wù)數(shù)據(jù)與整個頁面放入到緩存中。這種方式比起應(yīng)用程序緩存而言,在性能上有很大的提高。同時,它又通過引入的SqlCacheDependency特性有效地避免了“數(shù)據(jù)過期”的缺點,因而在PetShop 4.0中被廣泛采用。相反,之前為Product、Category、Item業(yè)務(wù)對象建立的代理對象則被“投閑散置”,僅僅作為一種設(shè)計方法的展示而“幸存”與整個系統(tǒng)的源代碼中。

          posted on 2007-12-26 12:07 風(fēng)雨兼程 閱讀(1292) 評論(1)  編輯  收藏 所屬分類: Petshop4.0 案例分析

          評論

          # re: 在PetShop 4.0中ASP.NET緩存的實現(xiàn)   回復(fù)  更多評論   

          寫的好!!!

          http://www.langligelang.com 啷哩咯啷
          2008-10-31 22:57 | mydip
          主站蜘蛛池模板: 东港市| 宜都市| 鞍山市| 通海县| 永泰县| 房山区| 乡城县| 临汾市| 隆回县| 金堂县| 噶尔县| 靖州| 密山市| 南靖县| 娄底市| 洛川县| 西吉县| 万全县| 贵溪市| 台前县| 株洲县| 民权县| 林甸县| 朝阳市| 东丽区| 吴堡县| 芦溪县| 利辛县| 迁安市| 南充市| 丰县| 马龙县| 唐海县| 康马县| 绥滨县| 靖远县| 新巴尔虎左旗| 天峻县| 阿拉善左旗| 浪卡子县| 和田县|