緩存在一個大型一點的系統裏面是必然會涉及到的,合理的使用緩存可以給咱們的系統帶來更高的響應速度。因爲數據提供服務涉及到數據庫的相關操做,若是客戶端的併發數量超過必定的數量,那麼數據庫的請求處理則以爆發式增加,若是數據庫服務器沒法快速處理這些併發請求,那麼將會增長客戶端的請求時間,嚴重者可能致使數據庫服務或者應用服務直接癱瘓。緩存方案就是爲這個而誕生,隨着緩存的引入,能夠把數據庫的IO耗時操做,轉換爲內存數據的快速響應操做,或者把整個頁面緩存到緩存系統裏面。本篇隨筆主要介紹利用ABP框架的支持實現的服務端緩存處理和Winform客戶端緩存的處理。html
緩存的重要性不言而喻,我在博客園裏面也寫了不少緩存相關的文章,都是基於實際系統的總結處理。git
《Winform裏面的緩存使用》github
《使用ConcurrentDictionary替代Hashtable對多線程的對象緩存處理》redis
《在.NET項目中使用PostSharp,使用MemoryCache實現緩存的處理》數據庫
《.NET緩存框架CacheManager在混合式開發框架中的應用(1)-CacheManager的介紹和使用》json
《在.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裏面,有相關的緩存記錄以下所示。
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獲取的實體,咱們能夠用這個基類緩存它們,就不用再頻繁地從數據庫查詢。
不過這裏不對這個進行細講了。
除了在服務端進行緩存測試外,爲了提升客戶端的響應速度,咱們還能夠在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("隨訪狀態"); }
這樣就很是簡化了咱們對字典數據源的綁定操做了,很是方便易讀,下面是其中一個功能界面的下拉列表展現。
使用緩存接口,對於大量字典數據顯示的界面,界面顯示速度有了不錯的提高。