在我們的很多框架或者項(xiàng)目應(yīng)用中闺骚,緩存在一定程度上可以提高程序的響應(yīng)速度颠通,以及減輕服務(wù)器的承載壓力险污,因此在一些地方我們都考慮引入緩存模塊,這篇隨筆介紹使用開源緩存框架CacheManager來實(shí)現(xiàn)數(shù)據(jù)的緩存憎瘸,在微信開發(fā)框架中入篮,我們有一些常用的處理也需要應(yīng)用到緩存,因此本隨筆以微信框架為例介紹緩存的實(shí)際使用幌甘,實(shí)際上潮售,在我們很多框架中,如混合式開發(fā)框架锅风、Web開發(fā)框架酥诽、Bootstrap開發(fā)框架中,這個(gè)模塊都是通用的皱埠。
1肮帐、框架的緩存設(shè)計(jì)
在我們的微信開發(fā)框架中,緩存作為數(shù)據(jù)庫和對(duì)外接口之間的一個(gè)分層,提供數(shù)據(jù)的緩存響應(yīng)處理训枢,如下結(jié)構(gòu)所示是Web API層對(duì)緩存的架構(gòu)設(shè)計(jì)托修。
在緩存的處理中,我側(cè)重于使用CacheManager恒界,這個(gè)緩存框架是一個(gè)集大成者睦刃,關(guān)于CacheManager 的介紹,我們可以回顧下我之前的隨筆《.NET緩存框架CacheManager在混合式開發(fā)框架中的應(yīng)用(1)-CacheManager的介紹和使用》十酣。
CacheManager是一個(gè)以C#語言開發(fā)的開源.Net緩存框架抽象層涩拙。它不是具體的緩存實(shí)現(xiàn),但它支持多種緩存提供者(如Redis耸采、Memcached等)并提供很多高級(jí)特性兴泥。CacheManager 主要的目的使開發(fā)者更容易處理各種復(fù)雜的緩存場(chǎng)景,使用CacheManager可以實(shí)現(xiàn)多層的緩存洋幻,讓進(jìn)程內(nèi)緩存在分布式緩存之前郁轻,且僅需幾行代碼來處理。CacheManager 不僅僅是一個(gè)接口去統(tǒng)一不同緩存提供者的編程模型文留,它使我們?cè)谝粋€(gè)項(xiàng)目里面改變緩存策略變得非常容易,同時(shí)也提供更多的特性:如緩存同步竭沫、并發(fā)更新燥翅、序列號(hào)、事件處理蜕提、性能計(jì)算等等森书,開發(fā)人員可以在需要的時(shí)候選擇這些特性。
CacheManager的GitHub源碼地址為:https://github.com/MichaCo/CacheManager谎势,如果需要具體的Demo及說明凛膏,可以訪問其官網(wǎng):http://cachemanager.michaco.net
2、在微信框架中整合CacheManager 緩存框架
在使用CacheManager 緩存的時(shí)候脏榆,我們可以直接使用相關(guān)對(duì)象進(jìn)行處理猖毫,首先需要定義一個(gè)類來進(jìn)行初始化緩存的設(shè)置,然后進(jìn)行調(diào)用须喂,調(diào)用的時(shí)候可以使用IOC的方式構(gòu)建對(duì)象吁断,如下代碼所示創(chuàng)建一個(gè)自定義的緩存管理類
/// <summary>
/// 基于CacheManager的接口處理
/// </summary>
public class CacheManager : ICacheManager
{
/// <summary>
/// ICacheManager對(duì)象
/// </summary>
public ICacheManager<object> Manager { get; set; }
/// <summary>
/// 默認(rèn)構(gòu)造函數(shù)
/// </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的配置文件中配置緩存的相關(guān)信息,如下文件所示坞生。
如果直接使用Autofac的構(gòu)造類來處理仔役,那么調(diào)用緩存處理的代碼如下所示。
//通過AutoFac工廠獲取對(duì)應(yīng)的接口實(shí)現(xiàn)
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;
}
}
如果為了使用方便是己,我們還可以對(duì)這個(gè)輔助類進(jìn)行進(jìn)一步的封裝又兵,以便對(duì)它進(jìn)行統(tǒng)一的調(diào)用處理即可。
/// <summary>
/// 基于.NET CacheManager的緩存管理卒废,文檔參考:http://cachemanager.michaco.net/documentation
/// </summary>
public class CacheManagerHelper
{
/// <summary>
/// 鎖定處理變量
/// </summary>
private static readonly object locker = new object();
/// <summary>
/// 創(chuàng)建一個(gè)緩存的鍵值沛厨,并指定響應(yīng)的時(shí)間范圍乘盼,如果失效,則自動(dòng)獲取對(duì)應(yīng)的值
/// </summary>
/// <typeparam name="T">對(duì)象類型</typeparam>
/// <param name="key">對(duì)象的鍵</param>
/// <param name="cachePopulate">獲取緩存值的操作</param>
/// <param name="expiration">失效的時(shí)間范圍</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工廠獲取對(duì)應(yīng)的接口實(shí)現(xiàn)
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只有在空值的情況下執(zhí)行加入并返回true,Put總會(huì)替換并返回True
//如果按下面的方式加入,那么會(huì)留下歷史丟棄的鍵值: 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配置參數(shù)錯(cuò)誤页屠,請(qǐng)檢查autofac.config是否存在ICacheManager的定義");
}
}
}
不過由于官方已經(jīng)提供了一個(gè)類似上面的代碼邏輯的TryGetOrAdd方法粹胯,這個(gè)方法的定義如下所示。
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
True
if the operation succeeds, False
in case there are too many retries or the valueFactory returns null.
我們根據(jù)這個(gè)參數(shù)的定義辰企,可以進(jìn)一步簡(jiǎn)化上面的輔助類代碼风纠。
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;
整個(gè)類的代碼如下所示
/// <summary>
/// 基于.NET CacheManager的緩存管理,文檔參考:http://cachemanager.michaco.net/documentation
/// </summary>
public class CacheManagerHelper
{
/// <summary>
/// 創(chuàng)建一個(gè)緩存的鍵值牢贸,并指定響應(yīng)的時(shí)間范圍竹观,如果失效,則自動(dòng)獲取對(duì)應(yīng)的值
/// </summary>
/// <typeparam name="T">對(duì)象類型</typeparam>
/// <param name="key">對(duì)象的鍵</param>
/// <param name="cachePopulate">獲取緩存值的操作</param>
/// <param name="expiration">失效的時(shí)間范圍</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工廠獲取對(duì)應(yīng)的接口實(shí)現(xiàn)
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配置參數(shù)錯(cuò)誤潜索,請(qǐng)檢查autofac.config是否存在ICacheManager的定義");
}
}
}
這樣代碼就簡(jiǎn)化了不少臭增,而且不用自己控制讀取的線程鎖了,下面代碼是使用輔助類實(shí)現(xiàn)緩存的添加及獲取處理竹习。
/// <summary>
/// 為避免頻繁的對(duì)數(shù)據(jù)庫檢索誊抛,提高獲取賬號(hào)信息的速度
/// 我們把賬號(hào)信息根據(jù)ID緩存起來,方便快速使用整陌,提高效率拗窃。
/// </summary>
public static AccountInfo GetAccountByID(string accountId)
{
AccountInfo accountInfo = null;
#region 使用.NET CacheManager緩存
//正常情況下access_token有效期為7200秒,這里使用緩存設(shè)置短于這個(gè)時(shí)間即可
var key = "GetAccountByID_" + accountId;
accountInfo = CacheManagerHelper.GetCacheItem<AccountInfo>(key, () =>
{
return BLLFactory<Account>.Instance.FindByID(accountId);
}, TimeSpan.FromMinutes(TimeOut_Minutes));
return accountInfo;
}
通過這樣的輔助類封裝,我們可以在需要緩存的函數(shù)里面泌辫,統(tǒng)一使用輔助類對(duì)數(shù)據(jù)進(jìn)行緩存或者讀取緩存的操作随夸。
我們也可以直接使用Autofac構(gòu)建的緩存管理進(jìn)行操作,如在小程序里面震放,我們對(duì)用戶敏感數(shù)據(jù)的解密處理函數(shù)宾毒,如下所示。
/// <summary>
/// 根據(jù)微信小程序平臺(tái)提供的解密算法解密數(shù)據(jù)
/// </summary>
[HttpGet]
public SmallAppUserInfo Decrypt(string encryptedData, string iv, string thirdkey)
{
SmallAppUserInfo userInfo = null;
//通過AutoFac工廠獲取對(duì)應(yīng)的接口實(shí)現(xiàn)
var cache = AutoFactory.Instatnce.Container.Resolve<ICacheManager>();
if (cache != null)
{
//從緩存里面澜搅,獲取對(duì)應(yīng)的SessionKey
var sessionkey = cache.Manager.Get(thirdkey);
if (sessionkey != null)
{
//對(duì)用戶身份加密數(shù)據(jù)進(jìn)行解析伍俘,獲取包含openid等屬性的完整對(duì)象
IBasicApi api = new BasicApi();
userInfo = api.Decrypt(encryptedData, iv, sessionkey.ToString());
}
}
return userInfo;
}