1. 程式人生 > >ABP開發框架前後端開發系列---(15)ABP框架的服務端和客戶端快取的使用

ABP開發框架前後端開發系列---(15)ABP框架的服務端和客戶端快取的使用

快取在一個大型一點的系統裡面是必然會涉及到的,合理的使用快取能夠給我們的系統帶來更高的響應速度。由於資料提供服務涉及到資料庫的相關操作,如果客戶端的併發數量超過一定的數量,那麼資料庫的請求處理則以爆發式增長,如果資料庫伺服器無法快速處理這些併發請求,那麼將會增加客戶端的請求時間,嚴重者可能導致資料庫服務或者應用服務直接癱瘓。快取方案就是為這個而誕生,隨著快取的引入,可以把資料庫的IO耗時操作,轉換為記憶體資料的快速響應操作,或者把整個頁面快取到快取系統裡面。本篇隨筆主要介紹利用ABP框架的支援實現的服務端快取處理和Winform客戶端快取的處理。

1、快取文章回顧

快取的重要性不言而喻,我在部落格園裡面也寫了很多快取相關的文章,都是基於實際系統的總結處理。

《Winform裡面的快取使用》

《使用ConcurrentDictionary替代Hashtable對多執行緒的物件快取處理》

《在.NET專案中使用PostSharp,使用MemoryCache實現快取的處理》

《.NET快取框架CacheManager在混合式開發框架中的應用(1)-CacheManager的介紹和使用》

《在.NET專案中使用PostSharp,使用CacheManager實現多種快取框架的處理》

《在Winform開發框架中下拉列表繫結字典以及使用快取提高介面顯示速度》

《C#開發微信門戶及應用(48) - 在微信框架中整合CacheManager 快取框架》

上面這些都是和快取相關的內容,一般來說,快取有很多方式的實現,如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等,為了方便我們一般使用.net的記憶體快取處理,如果我們需要序列化快取內容,那麼可以採用MemoryCache或者Redis快取等。後來我們通過綜合考慮,基於配置方式選擇不同快取方式,在後端一般可以使用CacheManager 的快取處理。

如下面是基於常規架構的快取處理分層,如果是基於Web API的服務端,那麼快取一般可以在Web API層或者它的下面一層。

如果是基於可序列化的快取處理,它在IIS或者其他Web 容器重新啟動後,快取不會丟失,如在Redis裡面,有相關的快取記錄如下所示。

 

2、ABP服務端快取處理 

ABP提供了快取的抽象,它內部使用了這個快取抽象。雖然預設的實現使用了MemoryCache,通過配置也可以使用Redis等快取,快取的主要介面ICacheManager。

我們可以在應用服務層的建構函式裡面,注入該介面,然後使用該介面獲得一個快取物件。

官方簡單的應用服務層程式碼如下所示。

public class TestAppService : ApplicationService
{
    private readonly ICacheManager _cacheManager;

    public TestAppService(ICacheManager cacheManager)
    {
        _cacheManager = cacheManager;
    }

實際上,我們應用服務層應該會更加複雜一些,如下是我們ABP快速開發框架的應用服務層的程式碼

    [AbpAuthorize]
    public class DictDataAppService : MyAsyncServiceBase<DictData, DictDataDto, string, DictDataPagedDto, CreateDictDataDto, DictDataDto>, IDictDataAppService
    {
        /// <summary>
        /// 快取管理介面
        /// </summary>
        private readonly ICacheManager _cacheManager;
        private readonly IRepository<DictData, string> _repository;

        public DictDataAppService(IRepository<DictData, string> repository, ICacheManager cacheManager) : base(repository)
        {
            _repository = repository;
            _cacheManager = cacheManager;//依賴注入快取
        }

對於字典模組,我們一般獲取介面如下所示。

        /// <summary>
        /// 根據字典型別ID獲取所有該型別的字典列表集合(Key為名稱,Value為值)
        /// </summary>
        /// <param name="dictTypeId">字典型別ID</param>
        /// <returns></returns>
        public async Task<Dictionary<string, string>> GetDictByTypeID(string dictTypeId)
        {
            IList<DictData> list = await Repository.GetAllListAsync(s => s.DictType_ID == dictTypeId);

            Dictionary<string, string> dict = new Dictionary<string, string>();
            foreach (DictData info in list)
            {
                if (!dict.ContainsKey(info.Name))
                {
                    dict.Add(info.Name, info.Value);
                }
            }
            return dict;
        }

如果我們需要把它構建一個快取介面,那麼處理方式就是對它進行一個簡單包裝即可,如下程式碼所示。

        /// <summary>
        /// 根據字典型別ID獲取所有該型別的字典列表集合(使用快取)
        /// </summary>
        /// <param name="dictTypeId">字典型別ID</param>
        /// <returns></returns>
        public async Task<Dictionary<string, string>> GetDictByTypeIDCached(string dictTypeId)
        {
            //系統快取預設為60分鐘,可以在模組中配置具體的時間,配置後則是具體配置時間
            return await _cacheManager.GetCache("DictDataAppService")
                    .GetAsync(dictTypeId, () => GetDictByTypeID(dictTypeId));
        }

預設快取超時是60分鐘,它可以改。如果你超過60分鐘沒有使用快取中的項,會從快取中自動移除。你可以配置指定的快取或是全部的快取。

我們可以在應用服務層模組類ApplicationModule類裡面進行修改,實現對快取的過期設定。

            //系統快取預設為60分鐘,可以在模組中配置具體的時間,配置後則是具體配置時間
            //所有快取設定為2小時
            Configuration.Caching.ConfigureAll(cache =>
            {
                cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
            });

            //特殊指定為5分鐘
            Configuration.Caching.Configure("DictDataAppService", cache =>
            {
                cache.DefaultSlidingExpireTime = TimeSpan.FromMinutes(5);
            });

Redis 快取整合

預設快取管理使用的是記憶體快取。所以,如果你有多個併發的Web伺服器使用同個應用,可能會成為一個問題,在這種情況下,你需要一個分佈/集中快取服務,你就可以簡單的使用Redis做為你的快取伺服器。

Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,和Memcached類似,它支援儲存的value型別相對更多,包括string(字串)、list(連結串列)、set(集合)、zset(sorted set --有序集合)和hash(雜湊型別)。在此基礎上,redis支援各種不同方式的排序。與memcached一樣,為了保證效率,資料都是快取在記憶體中。區別的是redis會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了master-slave(主從)同步。

Redis的程式碼遵循ANSI-C編寫,可以在所有POSIX系統(如Linux, *BSD, Mac OS X, Solaris等)上安裝執行。而且Redis並不依賴任何非標準庫,也沒有編譯引數必需新增。

下載地址:https://github.com/MSOpenTech/redis/releases下載,安裝為Windows服務即可。

安裝後作為Windows服務執行,安裝後可以在系統的服務裡面看到Redis的服務在運行了,如下圖所示。

安裝好Redis後,還有一個Redis伴侶Redis Desktop Manager需要安裝,這樣可以實時檢視Redis快取裡面有哪些資料,具體地址如下:http://redisdesktop.com/download

下載屬於自己平臺的版本即可

下載安裝後,開啟執行介面,如果我們往裡面新增鍵值的資料,那麼可以看到裡面的資料了。

我們來看看如何在ABP框架中使用Redis快取

我們現在應用服務層模組裡面配置好使用Redis,如下程式碼所示

    [DependsOn(
        ................
        typeof(AbpRedisCacheModule) //Redis快取加入
    )]
    public class ApplicationModule : AbpModule
    {
        public override void PreInitialize()
        {
            ............

            //使用Redis快取
            int DatabaseId = -1;
            int.TryParse(AppSettingConfig.GetAppSetting("RedisCache", "DatabaseId"), out DatabaseId);
            string connectionString = AppSettingConfig.GetAppSetting("RedisCache", "ConnectionString");
            Configuration.Caching.UseRedis(options =>
            {
                options.ConnectionString = connectionString;
                options.DatabaseId = DatabaseId;
            });

            //系統快取預設為60分鐘,可以在模組中配置具體的時間,配置後則是具體配置時間
            //所有快取設定為2小時
            //Configuration.Caching.ConfigureAll(cache =>
            //{
            //    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
            //});
            //特殊指定為5分鐘
            Configuration.Caching.Configure("DictDataAppService", cache =>
            {
                cache.DefaultSlidingExpireTime = TimeSpan.FromMinutes(5);
            });
        }

Host專案配置檔案,Appsetting.json配置檔案如下所示,增加RedisCache的配置節點。

使用快取處理的應用服務層介面實現如下所示

        /// <summary>
        /// 根據字典型別ID獲取所有該型別的字典列表集合(使用快取)
        /// </summary>
        /// <param name="dictTypeId">字典型別ID</param>
        /// <returns></returns>
        public async Task<Dictionary<string, string>> GetDictByTypeIDCached(string dictTypeId)
        {
            //系統快取預設為60分鐘,可以在模組中配置具體的時間,配置後則是具體配置時間
            return await _cacheManager.GetCache("DictDataAppService").GetAsync(dictTypeId, () => GetDictByTypeID(dictTypeId));
        }

在測試介面頁面中進行測試

檢視快取管理裡面的內容,可以發現已經具有值了,如下所示。

這樣我們就可以很容易的從記憶體快取切換到Redis的快取了。 

 

實體快取

雖然ABP快取系統出於普通的目的,但有一個EntityCache基類,可幫你快取實體。如果我們通過它們的Id獲取的實體,我們可以用這個基類快取它們,就不用再頻繁地從資料庫查詢。 

不過這裡不對這個進行細講了。

 

3、Winform客戶端的快取處理

除了在服務端進行快取測試外,為了提高客戶端的響應速度,我們還可以在Winform客戶端中使用記憶體快取進行快取一些不常變化的內容的,這樣可以避免頻繁的請求網路介面,獲取介面資料。

ABP基礎模組裡面也提供了一個簡單的快取類,我們可以使用它進行快取處理。

我曾經在之前一篇隨筆《在Winform開發框架中下拉列表繫結字典以及使用快取提高介面顯示速度》對字典模組中使用快取進行了說明,這個我們也可以調整為ABP快速開發框架中Winform客戶端的字典處理方式。

ABP中有兩種cache的實現方式:MemroyCache 和 RedisCache. 如下圖,兩者都繼承至ICache介面。ABP核心模組封裝了MemroyCache 來實現ABP中的預設快取功能。 Abp.RedisCache這個模組封裝RedisCache來實現快取。

我們可以在Winform客戶端中使用AbpMemoryCache是實現記憶體快取的處理。

例如我們在介面模組中使用一個字典輔助類來封裝對字典模組的呼叫,同時可以使用快取方式進行獲取。

使用快取處理的邏輯,如下所示

主要就是判斷鍵值是否存在,否則就設定記憶體快取即可。

然後在編寫一個字典控制元件的擴充套件函式,如下所示。

        /// <summary>
        /// 繫結下拉列表控制元件為指定的資料字典列表
        /// </summary>
        /// <param name="control">下拉列表控制元件</param>
        /// <param name="dictTypeName">資料字典型別名稱</param>
        /// <param name="defaultValue">控制元件預設值</param>
        /// <param name="emptyFlag">是否新增空行</param>
        public static void BindDictItems(this ComboBoxEdit control, string dictTypeName, string defaultValue, bool isCache = true, bool emptyFlag = true)
        {
            var dict = GetDictByDictType(dictTypeName, isCache);

            List<CListItem> itemList = new List<CListItem>();
            foreach (string key in dict.Keys)
            {
                itemList.Add(new CListItem(key, dict[key]));
            }

            control.BindDictItems(itemList, defaultValue, emptyFlag);
        }

繫結字典控制元件使用的時候,就非常簡單了,如下程式碼是實際專案中對字典列表繫結的操作,字典資料在字典模組裡面統一定義的。

        /// <summary>
        /// 初始化資料字典
        /// </summary>
        private void InitDictItem()
        {
            txtInDiagnosis.BindDictItems("入院診斷");
            txtLeaveDiagnosis.BindDictItems("最後診斷");

            //初始化程式碼
            this.txtFollowType.BindDictItems("隨訪方式");
            this.txtFollowStatus.BindDictItems("隨訪狀態");
        }

這樣就非常簡化了我們對字典資料來源的繫結操作了,非常方便易讀,下面是其中一個功能介面的下拉列表展示。

 使用快取介面,對於大量字典資料顯示的介面,介面顯示速度有了不錯的提升。