在我們的很多框架或者項目應用中,緩存在一定程度上可以提高程式的響應速度,以及減輕伺服器的承載壓力,因此在一些地方我們都考慮引入緩存模塊,這篇隨筆介紹使用開源緩存框架CacheManager來實現數據的緩存,在微信開發框架中,我們有一些常用的處理也需要應用到緩存,因此本隨筆以微信框架為例介紹緩存的實際... ...
在我們的很多框架或者項目應用中,緩存在一定程度上可以提高程式的響應速度,以及減輕伺服器的承載壓力,因此在一些地方我們都考慮引入緩存模塊,這篇隨筆介紹使用開源緩存框架CacheManager來實現數據的緩存,在微信開發框架中,我們有一些常用的處理也需要應用到緩存,因此本隨筆以微信框架為例介紹緩存的實際使用,實際上,在我們很多框架中,如混合式開發框架、Web開發框架、Bootstrap開發框架中,這個模塊都是通用的。
1、框架的緩存設計
在我們的微信開發框架中,緩存作為資料庫和對外介面之間的一個分層,提供數據的緩存響應處理,如下結構所示是Web API層對緩存的架構設計。
在緩存的處理中,我側重於使用CacheManager,這個緩存框架是一個集大成者,關於CacheManager 的介紹,我們可以回顧下我之前的隨筆《.NET緩存框架CacheManager在混合式開發框架中的應用(1)-CacheManager的介紹和使用》。
CacheManager是一個以C#語言開發的開源.Net緩存框架抽象層。它不是具體的緩存實現,但它支持多種緩存提供者(如Redis、Memcached等)並提供很多高級特性。
CacheManager 主要的目的使開發者更容易處理各種複雜的緩存場景,使用CacheManager可以實現多層的緩存,讓進程內緩存在分散式緩存之前,且僅需幾行代碼來處理。
CacheManager 不僅僅是一個介面去統一不同緩存提供者的編程模型,它使我們在一個項目裡面改變緩存策略變得非常容易,同時也提供更多的特性:如緩存同步、併發更新、序列號、事件處理、性能計算等等,開發人員可以在需要的時候選擇這些特性。
CacheManager的GitHub源碼地址為:https://github.com/MichaCo/CacheManager,如果需要具體的Demo及說明,可以訪問其官網:http://cachemanager.michaco.net
2、在微信框架中整合CacheManager 緩存框架
在使用CacheManager 緩存的時候,我們可以直接使用相關對象進行處理,首先需要定義一個類來進行初始化緩存的設置,然後進行調用,調用的時候可以使用IOC的方式構建對象,如下代碼所示創建一個自定義的緩存管理類
/// <summary> /// 基於CacheManager的介面處理 /// </summary> public class CacheManager : ICacheManager { /// <summary> /// ICacheManager對象 /// </summary> public ICacheManager<object> Manager { get; set; } /// <summary> /// 預設構造函數 /// </summary> public CacheManager() { // 初始化緩存管理器 Manager = CacheFactory.Build("getStartedCache", settings => { settings .WithSystemRuntimeCacheHandle("handleName") .And .WithRedisConfiguration("redis", config => { config.WithAllowAdmin() .WithDatabase(0) .WithEndpoint("localhost", 6379); }) .WithMaxRetries(100) .WithRetryTimeout(50) .WithRedisBackplane("redis") .WithRedisCacheHandle("redis", true) ; }); } } }
然後在Autofac的配置文件中配置緩存的相關信息,如下文件所示。
如果直接使用Autofac的構造類來處理,那麼調用緩存處理的代碼如下所示。
//通過AutoFac工廠獲取對應的介面實現 var cache = AutoFactory.Instatnce.Container.Resolve<ICacheManager>(); if (cache != null) { accountInfo = cache.Manager.Get(key) as AccountInfo; if (accountInfo == null) { var value = BLLFactory<Account>.Instance.FindByID(accountId); var item = new CacheItem<object>(key, value, ExpirationMode.Absolute, TimeSpan.FromMinutes(TimeOut_Minutes)); cache.Manager.Put(item); accountInfo = cache.Manager.Get(key) as AccountInfo; } }
如果為了使用方便,我們還可以對這個輔助類進行進一步的封裝,以便對它進行統一的調用處理即可。
/// <summary> /// 基於.NET CacheManager的緩存管理,文檔參考:http://cachemanager.michaco.net/documentation /// </summary> public class CacheManagerHelper { /// <summary> /// 鎖定處理變數 /// </summary> private static readonly object locker = new object(); /// <summary> /// 創建一個緩存的鍵值,並指定響應的時間範圍,如果失效,則自動獲取對應的值 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <param name="key">對象的鍵</param> /// <param name="cachePopulate">獲取緩存值的操作</param> /// <param name="expiration">失效的時間範圍</param> /// <param name="mode">失效類型</param> /// <returns></returns> public static T GetCacheItem<T>(string key, Func<T> cachePopulate, TimeSpan expiration, string region = "_", ExpirationMode mode = ExpirationMode.Sliding) where T :class { CacheItem<object> outItem = null; //通過AutoFac工廠獲取對應的介面實現 var cache = AutoFactory.Instatnce.Container.Resolve<ICacheManager>(); if (cache != null) { if (cache.Manager.Get(key, region) == null) { lock (locker) { if (cache.Manager.Get(key, region) == null) { //Add、Put差異,Add只有在空值的情況下執行加入並返回true,Put總會替換並返回True //如果按下麵的方式加入,那麼會留下歷史丟棄的鍵值: cache.Manager.Put(key, value); var value = cachePopulate(); var item = new CacheItem<object>(key, region, value, mode, expiration); cache.Manager.Put(item); } } } return cache.Manager.Get(key, region) as T; } else { throw new ArgumentNullException("AutoFac配置參數錯誤,請檢查autofac.config是否存在ICacheManager的定義"); } } }
不過由於官方已經提供了一個類似上面的代碼邏輯的TryGetOrAdd方法,這個方法的定義如下所示。
TryGetOrAdd(String, String, Func<String, String, TCacheValue>, out TCacheValue)
Tries to either retrieve an existing item or add the item to the cache if it does not exist. The valueFactory will be evaluated only if the item does not exist.
Declaration
bool TryGetOrAdd(string key, string region, Func<string, string, TCacheValue> valueFactory, out TCacheValue value)
Parameters
Type | Name | Description |
---|---|---|
String | key |
The cache key. |
String | region |
The cache region. |
Func<String, String, TCacheValue> | valueFactory |
The method which creates the value which should be added. |
TCacheValue | value |
The cache value. |
Returns
Type | Description |
---|---|
Boolean |
|
我們根據這個參數的定義,可以進一步簡化上面的輔助類代碼。
cache.Manager.TryGetOrAdd(key, region, (_key, _region) =>{ var value = cachePopulate(); var item = new CacheItem<object>(key, region, value, mode, expiration); return item; }, out outItem); return outItem as T;
整個類的代碼如下所示
/// <summary> /// 基於.NET CacheManager的緩存管理,文檔參考:http://cachemanager.michaco.net/documentation /// </summary> public class CacheManagerHelper { /// <summary> /// 創建一個緩存的鍵值,並指定響應的時間範圍,如果失效,則自動獲取對應的值 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <param name="key">對象的鍵</param> /// <param name="cachePopulate">獲取緩存值的操作</param> /// <param name="expiration">失效的時間範圍</param> /// <param name="mode">失效類型</param> /// <returns></returns> public static T GetCacheItem<T>(string key, Func<T> cachePopulate, TimeSpan expiration, string region = "_", ExpirationMode mode = ExpirationMode.Sliding) where T :class { CacheItem<object> outItem = null; //通過AutoFac工廠獲取對應的介面實現 var cache = AutoFactory.Instatnce.Container.Resolve<ICacheManager>(); if (cache != null) { cache.Manager.TryGetOrAdd(key, region, (_key, _region) =>{ var value = cachePopulate(); var item = new CacheItem<object>(key, region, value, mode, expiration); return item; }, out outItem); return outItem as T; } else { throw new ArgumentNullException("AutoFac配置參數錯誤,請檢查autofac.config是否存在ICacheManager的定義"); } } }
這樣代碼就簡化了不少,而且不用自己控制讀取的線程鎖了,下麵代碼是使用輔助類實現緩存的添加及獲取處理。
/// <summary> /// 為避免頻繁的對資料庫檢索,提高獲取賬號信息的速度 /// 我們把賬號信息根據ID緩存起來,方便快速使用,提高效率。 /// </summary> public static AccountInfo GetAccountByID(string accountId) { AccountInfo accountInfo = null; #region 使用.NET CacheManager緩存 //正常情況下access_token有效期為7200秒,這裡使用緩存設置短於這個時間即可 var key = "GetAccountByID_" + accountId; accountInfo = CacheManagerHelper.GetCacheItem<AccountInfo>(key, () => { return BLLFactory<Account>.Instance.FindByID(accountId); }, TimeSpan.FromMinutes(TimeOut_Minutes)); return accountInfo; }
通過這樣的輔助類封裝,我們可以在需要緩存的函數裡面,統一使用輔助類對數據進行緩存或者讀取緩存的操作。
我們也可以直接使用Autofac構建的緩存管理進行操作,如在小程式裡面,我們對用戶敏感數據的解密處理函數,如下所示。
/// <summary> /// 根據微信小程式平臺提供的解密演算法解密數據 /// </summary> [HttpGet] public SmallAppUserInfo Decrypt(string encryptedData, string iv, string thirdkey) { SmallAppUserInfo userInfo = null; //通過AutoFac工廠獲取對應的介面實現 var cache = AutoFactory.Instatnce.Container.Resolve<ICacheManager>(); if (cache != null) { //從緩存裡面,獲取對應的SessionKey var sessionkey = cache.Manager.Get(thirdkey); if (sessionkey != null) { //對用戶身份加密數據進行解析,獲取包含openid等屬性的完整對象 IBasicApi api = new BasicApi(); userInfo = api.Decrypt(encryptedData, iv, sessionkey.ToString()); } } return userInfo; }