在.NET項目中使用PostSharp果漾,使用CacheManager實現(xiàn)多種緩存框架的處理

在前面幾篇隨筆中球切,介紹了PostSharp的使用,以及整合MemoryCache,《在.NET項目中使用PostSharp绒障,實現(xiàn)AOP面向切面編程處理》吨凑、《在.NET項目中使用PostSharp,使用MemoryCache實現(xiàn)緩存的處理》參數(shù)了對PostSharp的使用户辱,并介紹了MemoryCache的緩存使用鸵钝,但是緩存框架的世界里面,有很多成熟的緩存框架庐镐,如MemoryCache恩商、Redis、Memcached必逆、Couchbase怠堪、System.Web.Caching等韧献,這時候我們?nèi)绻幸粋€大內(nèi)總管或者一個吸星大法的武功,把它們?nèi)诤掀饋硌薪校敲淳驼娴氖欠浅M昝赖囊患虑榇敢ぃ@個就是我們CacheManager緩存框架了,這樣的靈活性緩存框架并結(jié)合了PostSharp橫切面對常規(guī)代碼的簡化功能嚷炉,簡直就是好鞍配好馬渊啰、寶劍贈英雄,整合起來處理緩存真的是如虎添翼申屹。

1绘证、CacheManager緩存框架的回顧

關(guān)于這個緩存框架,我在隨筆《.NET緩存框架CacheManager在混合式開發(fā)框架中的應(yīng)用(1)-CacheManager的介紹和使用》中進行了介紹哗讥,讀者可以從中了解一下CacheManager緩存框架究竟是一個什么樣的東西嚷那。
CacheManager是一個以C#語言開發(fā)的開源.Net緩存框架抽象層。它不是具體的緩存實現(xiàn)杆煞,但它支持多種緩存提供者(如Redis魏宽、Memcached等)并提供很多高級特性。CacheManager 主要的目的使開發(fā)者更容易處理各種復(fù)雜的緩存場景决乎,使用CacheManager可以實現(xiàn)多層的緩存队询,讓進程內(nèi)緩存在分布式緩存之前,且僅需幾行代碼來處理构诚。CacheManager 不僅僅是一個接口去統(tǒng)一不同緩存提供者的編程模型装盯,它使我們在一個項目里面改變緩存策略變得非常容易拆挥,同時也提供更多的特性:如緩存同步掏颊、并發(fā)更新普碎、序列號、事件處理丑蛤、性能計算等等叠聋,開發(fā)人員可以在需要的時候選擇這些特性。
CacheManager緩存框架支持Winform和Web等應(yīng)用開發(fā)盏阶,以及支持多種流行的緩存實現(xiàn)晒奕,如MemoryCache闻书、Redis名斟、Memcached、Couchbase魄眉、System.Web.Caching等砰盐。
縱觀整個緩存框架,它的特定很明顯坑律,在支持多種緩存實現(xiàn)外岩梳,本身主要是以內(nèi)存緩存(進程內(nèi))為主囊骤,其他分布式緩存為輔的多層緩存架構(gòu)方式,以達到快速命中和處理的機制冀值,它們內(nèi)部有相關(guān)的消息處理也物,使得即使是分布式緩存,也能夠及時實現(xiàn)并發(fā)同步的緩存處理列疗。
CacheManager緩存框架在配置方面滑蚯,支持代碼方式的配置、XML配置抵栈,以及JSON格式的配置處理告材,非常方便。
CacheManager緩存框架默認對緩存數(shù)據(jù)的序列化是采用二進制方式古劲,同時也支持多種自定義序列化的方式斥赋,如基于JOSN.NET的JSON序列化或者自定義序列化方式。
CacheManager緩存框架可以對緩存記錄的增加产艾、刪除疤剑、更新等相關(guān)事件進行記錄。
CacheManager緩存框架的緩存數(shù)據(jù)是強類型的闷堡,可以支持各種常規(guī)類型的處理骚露,如Int、String缚窿、List類型等各種基礎(chǔ)類型棘幸,以及可序列號的各種對象及列表對象。
CacheManager緩存框架支持多層的緩存實現(xiàn)倦零,內(nèi)部良好的機制可以高效误续、及時的同步好各層緩存的數(shù)據(jù)。
CacheManager緩存框架支持對各種操作的日志記錄扫茅。
CacheManager緩存框架在分布式緩存實現(xiàn)中支持對更新的鎖定和事務(wù)處理蹋嵌,讓緩存保持更好的同步處理,內(nèi)部機制實現(xiàn)版本沖突處理葫隙。
CacheManager緩存框架支持兩種緩存過期的處理栽烂,如絕對時間的過期處理,以及固定時段的過期處理恋脚,是我們處理緩存過期更加方便腺办。
....
很多特性基本上覆蓋了緩存的常規(guī)特性,而且提供的接口基本上也是我們所經(jīng)常用的Add糟描、Put怀喉、Update、Remove等接口船响,使用起來也非常方便躬拢。
CacheManager的GitHub源碼地址為:https://github.com/MichaCo/CacheManager躲履,如果需要具體的Demo及說明,可以訪問其官網(wǎng):http://cachemanager.net/聊闯。

一般來說工猜,對于單機版本的應(yīng)用場景,基本上是無需引入這種緩存框架的菱蔬,因為客戶端的并發(fā)量很少域慷,而且數(shù)據(jù)請求也是寥寥可數(shù)的,性能方便不會有任何問題汗销。
如果對于分布式的應(yīng)用系統(tǒng)犹褒,如我在很多隨筆中介紹到我的《混合式開發(fā)框架》、《Web開發(fā)框架》弛针,由于數(shù)據(jù)請求是并發(fā)量隨著用戶增長而增長的叠骑,特別對于一些互聯(lián)網(wǎng)的應(yīng)用系統(tǒng),極端情況下某個時間點一下可能就會達到了整個應(yīng)用并發(fā)的峰值削茁。那么這種分布式的系統(tǒng)架構(gòu)宙枷,引入數(shù)據(jù)緩存來降低IO的并發(fā)數(shù),把耗時請求轉(zhuǎn)換為內(nèi)存的高速請求茧跋,可以極大程度的降低系統(tǒng)宕機的風(fēng)險慰丛。
我們以基于常規(guī)的Web API層來構(gòu)建應(yīng)用框架為例,整個數(shù)據(jù)緩存層瘾杭,應(yīng)該是在Web API層之下诅病、業(yè)務(wù)實現(xiàn)層之上的一個層,如下所示粥烁。


2贤笆、整合PostSharp和CacheManager實現(xiàn)多種緩存框架的處理

由于MemoryCache是在單個機器上進行緩存的處理,而且無法進行序列號讨阻,電腦宕機后就會全部丟掉緩存內(nèi)容芥永,由于這個缺點,我們對《在.NET項目中使用PostSharp钝吮,使用MemoryCache實現(xiàn)緩存的處理》基礎(chǔ)上進行進一步的調(diào)整埋涧,整合CacheManager進行使,從而可以利用緩存彈性化處理以及可序列號的特點奇瘦。
我們在正常情況下棘催,還是需要使用Redis這個強大的分布式緩存的,關(guān)于Redis的安裝和使用链患,請參考我的隨筆《基于C#的MongoDB數(shù)據(jù)庫開發(fā)應(yīng)用(4)--Redis的安裝及使用》巧鸭。
我們首先定義一個CacheAttribute的Aspect類瓶您,用來對緩存的切面處理麻捻。

/// <summary>
/// 方法實現(xiàn)緩存的標識
/// </summary>
[Serializable]
public class CacheAttribute : MethodInterceptionAspect
{
    /// <summary>
    /// 緩存的失效時間設(shè)置纲仍,默認采用30分鐘
    /// </summary>
    public int ExpirationPeriod = 30;

    /// <summary>
    /// PostSharp的調(diào)用處理,實現(xiàn)數(shù)據(jù)的緩存處理
    /// </summary>
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        //默認30分鐘失效贸毕,如果設(shè)置過期時間郑叠,那么采用設(shè)置值
        TimeSpan timeSpan = new TimeSpan(0, 0, ExpirationPeriod, 0);

        var cache = MethodResultCache.GetCache(args.Method, timeSpan);
        var arguments = args.Arguments.ToList();//args.Arguments.Union(new[] {WindowsIdentity.GetCurrent().Name}).ToList();
        var result = cache.GetCachedResult(arguments);
        if (result != null)
        {
            args.ReturnValue = result;
            return;
        }
        else
        {
            base.OnInvoke(args);
            //調(diào)用后更新緩存
            cache.CacheCallResult(args.ReturnValue, arguments);
        }
    }
}

然后就是進一步處理完善類 MethodResultCache來對緩存數(shù)據(jù)進行處理了。該類負責(zé)構(gòu)造一個CacheManager管理類來對緩存進行處理明棍,如下代碼所示乡革。



初始化緩存管理器的代碼如下所示,這里利用了MemoryCache作為快速的內(nèi)存緩存(主緩存)摊腋,以及Redis作為序列化存儲的緩存容器(從緩存)沸版,它們有內(nèi)在機制進行同步處理。

/// <summary>
/// 初始化緩存管理器
/// </summary>
private void InitCacheManager()
{
    _cache = 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)
        ;
    });
}

對緩存結(jié)果進行處理的函數(shù)如下所示兴蒸。

/// <summary>
/// 緩存結(jié)果內(nèi)容
/// </summary>
/// <param name="result">待加入緩存的結(jié)果</param>
/// <param name="arguments">方法的參數(shù)集合</param>
public void CacheCallResult(object result, IEnumerable<object> arguments)
{
    var key = GetCacheKey(arguments);
    _cache.Remove(key);

    var item = new CacheItem<object>(key, result, ExpirationMode.Sliding, _expirationPeriod);
    _cache.Add(item);
}

首先就是獲取方法參數(shù)的鍵视粮,然后移除對應(yīng)的緩存,加入新的緩存橙凳,并設(shè)定緩存的失效時間段即可蕾殴。

清空緩存的時候,直接調(diào)用管理類的Clear方法即可達到目的岛啸。

/// <summary>
/// 清空方法的緩存
/// </summary>
public void ClearCachedResults()
{
    _cache.Clear();
} 

這樣钓觉,我們處理好后,在一個業(yè)務(wù)調(diào)用類里面進行設(shè)置緩存標志即可坚踩,如下代碼所示荡灾。

/// <summary>
/// 獲取用戶全部簡單對象信息,并放到緩存里面
/// </summary>
/// <returns></returns>
[Cache(ExpirationPeriod = 1)]
public static List<SimpleUserInfo> GetSimpleUsers(int userid)
{
    Thread.Sleep(500);
    //return CallerFactory<IUserService>.Instance.GetSimpleUsers(); 

    //模擬從數(shù)據(jù)庫獲取數(shù)據(jù)
    List<SimpleUserInfo> list = new List<SimpleUserInfo>();
    for (int i = 0; i < 10; i++)
    {
        var info = new SimpleUserInfo();
        info.ID = i;
        info.Name = string.Concat("Name:", i);
        info.FullName = string.Concat("姓名:", i);
        list.Add(info);
    }
    return list;
}

為了測試緩存的處理瞬铸,以及對Redis的支持情況卧晓,我編寫了一個簡單的案例,功能如下所示赴捞。



測試代碼如下所示逼裆。

//測試緩存
private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(" 測試緩存: ");

    //測試反復(fù)調(diào)用獲取數(shù)值的耗時
    DateTime start = DateTime.Now;
    var list = CacheService.GetSimpleUsers(1);
    int end = (int)DateTime.Now.Subtract(start).TotalMilliseconds;

    Console.WriteLine(" first: " + end);
    Console.WriteLine(" List: " + list.Count);

    //Second test
    //檢查不同的方法參數(shù),對緩存值的影響
    start = DateTime.Now;
    list = CacheService.GetSimpleUsers(2);
    end = (int)DateTime.Now.Subtract(start).TotalMilliseconds;
    Console.WriteLine(" Second: " + end);
    Console.WriteLine(" List2: " + list.Count);
}
   
//更新緩存
private void button2_Click(object sender, EventArgs e)
{
    Console.WriteLine(" 更新緩存: ");

    //首先獲取對應(yīng)鍵的緩存值
    //然后對緩存進行修改
    //最后重新加入緩存
    var key = "CacheManagerAndPostSharp.CacheService.GetSimpleUsers";
    var item = MethodResultCache.GetCache(key);
    var argument = new List<object>(){1};
    var result = item.GetCachedResult(argument);
    Console.WriteLine("OldResult:" + result.ToJson());

    List<SimpleUserInfo> newList = result as List<SimpleUserInfo>;
    if(newList != null)
    {
        newList.Add(new SimpleUserInfo() { ID = new Random().Next(), Name = RandomChinese.GetRandomChars(2) });
    }
    item.CacheCallResult(newList, argument);
}

//清空緩存
private void button3_Click(object sender, EventArgs e)
{
    Console.WriteLine(" 清空緩存: ");

    //首先獲取對應(yīng)鍵的緩存值
    var key = "CacheManagerAndPostSharp.CacheService.GetSimpleUsers";
    var item = MethodResultCache.GetCache(key);
    var argument = new List<object>(){1};

    //然后清空方法的所有緩存
    item.ClearCachedResults();

    //最后重新檢驗緩存值為空
    var result = item.GetCachedResult(argument);
    Console.WriteLine("Result:" + result !=null ? result.ToJson() : "null");
}

測試運行結(jié)果如下所示赦政。

 測試緩存: 
 first: 870
 List: 10
 Second: 502
 List2: 10

 更新緩存: 
OldResult:[
{"ID":0,"HandNo":null,"Name":"Name:0","Password":null,"FullName":"姓名:0","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":1,"HandNo":null,"Name":"Name:1","Password":null,"FullName":"姓名:1","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":2,"HandNo":null,"Name":"Name:2","Password":null,"FullName":"姓名:2","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":3,"HandNo":null,"Name":"Name:3","Password":null,"FullName":"姓名:3","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":4,"HandNo":null,"Name":"Name:4","Password":null,"FullName":"姓名:4","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":5,"HandNo":null,"Name":"Name:5","Password":null,"FullName":"姓名:5","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":6,"HandNo":null,"Name":"Name:6","Password":null,"FullName":"姓名:6","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":7,"HandNo":null,"Name":"Name:7","Password":null,"FullName":"姓名:7","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":8,"HandNo":null,"Name":"Name:8","Password":null,"FullName":"姓名:8","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":9,"HandNo":null,"Name":"Name:9","Password":null,"FullName":"姓名:9","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null}]

 測試緩存: 
 first: 0
 List: 11
 Second: 0
 List2: 10
 
 清空緩存: 
null

同時我們看到在Redis里面胜宇,有相關(guān)的記錄如下所示。



結(jié)合PostSharp和CacheManager恢着,使得我們在使用緩存方面更具有彈性化桐愉,可以根據(jù)情況通過配置實現(xiàn)使用不同的緩存處理,但是在代碼中使用緩存就是只需要聲明一下即可掰派,非常方便簡潔了从诲。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市靡羡,隨后出現(xiàn)的幾起案子系洛,更是在濱河造成了極大的恐慌俊性,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件描扯,死亡現(xiàn)場離奇詭異定页,居然都是意外死亡,警方通過查閱死者的電腦和手機绽诚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門典徊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恩够,你說我怎么就攤上這事卒落。” “怎么了蜂桶?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵导绷,是天一觀的道長。 經(jīng)常有香客問我屎飘,道長妥曲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任钦购,我火速辦了婚禮檐盟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘押桃。我一直安慰自己葵萎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布唱凯。 她就那樣靜靜地躺著羡忘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪磕昼。 梳的紋絲不亂的頭發(fā)上卷雕,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音票从,去河邊找鬼漫雕。 笑死,一個胖子當(dāng)著我的面吹牛峰鄙,可吹牛的內(nèi)容都是我干的浸间。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼吟榴,長吁一口氣:“原來是場噩夢啊……” “哼魁蒜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤兜看,失蹤者是張志新(化名)和其女友劉穎锥咸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铣减,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡她君,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年脚作,在試婚紗的時候發(fā)現(xiàn)自己被綠了葫哗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡球涛,死狀恐怖劣针,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情亿扁,我是刑警寧澤捺典,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站从祝,受9級特大地震影響襟己,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牍陌,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一擎浴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧毒涧,春花似錦贮预、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捡偏,卻和暖如春唤冈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背银伟。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工务傲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枣申。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓售葡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親忠藤。 傳聞我的和親對象是個殘疾皇子挟伙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容