愛流浪的小風

技術隨手寫

使用Asp.Net MVC打造Web Api (24) - 使用Azure Cache將資料進行Cache

| Comments

在大型網站應用上,Cache的使用絕對是讓人又愛又恨,若是用的好,可以讓網站的Performance大大提升,但若不謹慎使用Cache的話,最後可能會發現自己的網站怎麼更新都是舊資料。因此Cache的使用絕對必須審慎拿捏,用在關鍵點,而且一但用了Cache一定要有對應的資料更新機制,這樣才能讓Cache發揮最大的功效。

新增Cache Role

在之前的文章中,我們已經新增了Web Role用來發行網站,而今天我們要在同樣的專案中新增一個Cache Role用來提供我們的Cache服務。

  1. 在Deploy專案的角色點擊滑鼠右鍵,新增背景工作角色專案

  2. 選擇新增快取角色服務

  3. 將網站發行至雲端,就可以看到快取服務的實體已經正常運作中,進入設定頁面可以看到有基本的設定內容

替網站加入Cache

接下來我們將實作一個模擬長時間執行的資料查詢,並且替這個服務加上Cache

  1. 使用Nuget加入Azure cache的Library

  2. 修改config,將[cache cluster role name]改為我們剛剛新增的Role名稱

    <dataCacheClients>
      <dataCacheClient name="default">
        <!--To use the in-role flavor of Windows Azure Caching, set identifier to be the cache cluster role name -->
        <!--To use the Windows Azure Caching Service, set identifier to be the endpoint of the cache cluster -->
        <autoDiscover isEnabled="true" identifier="ApiSample.Cache" />
    
        <!--<localCache isEnabled="true" sync="TimeoutBased" objectCount="100000" ttlValue="300" />-->
    
        <!--Use this section to specify security settings for connecting to your cache. This section is not required if your cache is hosted on a role that is a part of your cloud service. -->
        <!--<securityProperties mode="Message" sslEnabled="false">
          <messageSecurity authorizationInfo="[Authentication Key]" />
        </securityProperties>-->
      </dataCacheClient>
    </dataCacheClients>    
    
  3. 撰寫一個模擬長時間查詢的服務

    public interface ILongTimeService
    {
        string GetLongTimeData();
    }
    
    public class LongTimeService : ILongTimeService
    {
        public string GetLongTimeData()
        {
            System.Threading.Thread.Sleep(10 * 1000);
    
            return DateTime.Now.ToString();
        }
    }
    
  4. 增加一個Controller提供資料,並且加上Cache,同時我們加上StopWatch觀察查詢資料需要的時間

    public class SampleController : Controller
    {            
        public SampleController(ISampleService sampleService, ILongTimeService longTimeService)
        {
            this.SampleService = sampleService;
            this.LongTimeService = longTimeService;
        }  
    
        public ActionResult GetLongTimeData()
        {
            //// Calculate execute time
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
    
            //// Emulate long time processing data
            DataCacheFactory cacheFactory = new DataCacheFactory();
            DataCache cache = cacheFactory.GetDefaultCache();
            object result = cache.Get("longTimeData");
            if (result == null)
            {
                result = this.LongTimeService.GetLongTimeData();
                cache.Add("longTimeData", result);
            }
    
            stopWatch.Stop();
            TimeSpan ts = stopWatch.Elapsed;
            string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                ts.Hours, ts.Minutes, ts.Seconds,
                ts.Milliseconds / 10);
    
            return Json(
                new
                {
                    Result = result,
                    ExecuteTime = elapsedTime
                }, JsonRequestBehavior.AllowGet);
        }
    
    }
    

    其實cacheFactory.GetDefaultCache();代表取得default這組cache,也可以使用cacheFactory.GetCache("cache name");取得其它Cache資料。

  5. 部署程式到雲端,準備開始測試,由於是長時間查詢,所以第一次的執行時間應該會比較久,我們連結至/Sample/GetLongTimeData測試

  6. 再次執行查詢,由於第一次查詢之後結果應該會儲存在Cache之中,所以查詢時間應該較短!

測試成功!我們的Cache服務生效了,大家還可以依據自己的需求進一步的調整Cache的時間長短。

延伸閱讀:

使用AOP實作CacheInterceptor

除了直接在程式碼直接撰寫Cache的讀取程式碼之外,其實我們也可以將Cache抽出作為Interceptor,這麼一來我們只要在需要的Class上加上Attribute就可以完成Cache的新增,並且讓Cache統一並分離於商業邏輯之外,也增加了重複使用的容易性!

  1. 在Extensions專案新增CacheInterceptor

    public class CacheInterceptor : IInterceptor
    {
        private string section;
    
        public CacheInterceptor()
            : this(string.Empty)
        {
        }
    
        public CacheInterceptor(string section)
        {
            this.section = section;
        }
    
        public void Intercept(IInvocation invocation)
        {
            DataCacheFactory cacheFactory = new DataCacheFactory();
    
            //// Get cache by section
            DataCache cache;
            if (string.IsNullOrWhiteSpace(this.section))
            {
                cache = cacheFactory.GetDefaultCache();
            }
            else
            {
                cache = cacheFactory.GetCache(this.section);
            }
    
            //// Get cache or set by proceed method
            var typeName = invocation.TargetType.FullName;
            var methodName = invocation.Method.Name;
            var cacheKey = string.Format("{0}-{1}", typeName, methodName);
            var result = cache.Get(cacheKey);
            if (result == null)
            {
                invocation.Proceed();
                result = invocation.ReturnValue;
    
                cache.Add(cacheKey, result);
            }
            else
            {
                invocation.ReturnValue = result;
            }
        }
    }
    
  2. 修改BL的ServiceModule,讓LongTimeService使用CacheInterceptor

    public class ServiceModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            var service = Assembly.Load("ApiSample.BL.Services");
    
            builder.RegisterAssemblyTypes(service)
                   .AsImplementedInterfaces()
                   .EnableInterfaceInterceptors();
    
            builder.RegisterType<ProductService>()
                   .As<IProductService>()
                   .EnableInterfaceInterceptors()
                   .InterceptedBy(typeof(LogInterceptor), typeof(AuthInterceptor));
    
            builder.RegisterType<LongTimeService>()
                   .As<ILongTimeService>()
                   .EnableInterfaceInterceptors()
                   .InterceptedBy(typeof(CacheInterceptor));
        }
    }
    
  3. 修改Controller,將原本手動新增的Cache程式碼移除

    public ActionResult GetLongTimeData()
    {
        //// Calculate execute time
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();
    
        var result = this.LongTimeService.GetLongTimeData();
    
        stopWatch.Stop();
        TimeSpan ts = stopWatch.Elapsed;
        string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
            ts.Hours, ts.Minutes, ts.Seconds,
            ts.Milliseconds / 10);
    
        return Json(
            new
            {
                Result = result,
                ExecuteTime = elapsedTime
            }, JsonRequestBehavior.AllowGet);
    }
    
  4. 發行上雲端,測試一切正常,如此一來不但讓我們的程式碼更加的乾淨,也讓要增加Cache變成一件輕鬆的事情囉!

本日小結

透過Cache的幫助,我們可以讓需要花費大量資源才能產生資料的服務成本降低,也讓網站整體的效能變好,但還是必須謹慎的使用以避免Cache過於繁雜而難於管理,Azure提供的分散式Cache服務也讓Cloud Service可以共用Cache服務,更甚至透過多個Cache Instance來實現高可用性,相當的方便,大家可以多加利用!關於今天的內容,歡迎大家一起討論喔^_^

Comments

comments powered by Disqus