因MemoryCache鬧了個笑話

前言

是這麼一回事:

我正在苦思一個業務邏輯,捋著我還剩不多的秀髮,一時陷入冥想中……

突然聊天圖標一頓猛閃,打開一看,有同事語音;

大概意思是:同事把項目中Redis部分快取換成MemoryCache/Memcached,還強調MemoryCache/Memcached的效率是Redis的2~5倍;

當時我想到的是Memcached,聽到的似乎也是,心想:怎麼可能,就算有性能差,也不至於那麼多;

因為當時同事程式碼還沒提交,然後就陷入討論ing,最後還是沒聊通,我就跑到同事那當面溝通(要去打架嗎,不不不,文明人);

溝通中…..,好幾分鐘過去了,突然同事說:他用的是微軟的MemoryCache

雖然從讀音上我還沒區分出來,但一聽微軟,我就感覺我成笑話啦;

然後趕緊讓同事打開程式碼,我擦,真成笑話啦,還理直氣壯的溝通了好幾十分鐘。

為什麼會那麼「理直氣壯」?

  • Memcached(聽錯的)性能高於Redis的2~5倍產生重大懷疑,經驗告訴我不可能,除非Redis用法有問題;
  • 同事把Redis換成Memcached(聽錯的),那肯定不行的,前期的技術選型,Memcached不太符合項目應用場景;

最後因為MemoryCache成就了一場笑話,那MemoryCacheMemcached有什麼區別呢?

  • MemoryCache不是分散式快取,是基於程式存儲在記憶體中的;是微軟封裝好的記憶體快取庫,合理利用CPU,性能好;由於基於程式分配記憶體,使用時避免了網路通訊的消耗。
  • Memcached是分散式快取,是存儲在公共機器上的,供不同程式使用的,存在一定的網路傳輸消耗。

這樣比較感覺有點勉強,雖然Memcached是分散式的,但也是基於記憶體的,在數據存儲記憶體的邏輯還是不同的,不過這裡不打算講解源碼,我要說應用,哈哈哈。

附加-為什麼Redis讓同事感覺性能不好

真實場景是這樣的,客戶端開啟多執行緒頻繁讀取Redis數據,當訪問比較多時,導致Redis讀取數據超過20毫秒,對於Web項目來說其實這還好,20毫秒的響應用戶根本無法感知。但對於一個高性能要求的服務程式來說,對通訊要求就比較高,所以簡單分析了一下拖慢的原因,大概以下兩點:

  • 客戶端增多,導致一個常用Key對應的數據變大(其實不大,只是相對大,稍微影響了讀取速度,毫秒級別);

    解決方案:同事使用MemoryCache多做一層快取,將這個常用Key直接存在記憶體中,提高讀取性能;

  • 使用類似於Keys * 的命令頻繁獲取數據,導致有些命令執行在20毫秒左右(慢日誌中可以看到);

    解決方案:改用Scan類似命令獲取數據;

  • Redis自身的持久化耗時;

    解決方案:適當調整Redis持久化策略,讓持久化頻率沒那麼高;

正文

回歸正題,既然說到MemoryCache,就來簡單聊聊,主要分享在項目實戰中如何使用;

主要依賴包:Microsoft.Extensions.Caching.Memory

MemoryCache的使用很簡單,就是在調用方法設置和獲取值就對啦;來直接看Demo吧;

1. 控制台Demo

其實有很多程式是基於後台服務運行的,並不都是Web,所以寫了一個控制台的Demo,方便小夥伴參考;

1.1 引入相關包,項目中使用了Autofac作為依賴注入和其切面編程,則需要引入相關的依賴包,項目結構和包引入如下圖:

image-20210303122048613

1.2 編寫示例程式碼及註冊相關服務
  • IUserService就是簡單介面;

    image-20210303124658054

    介面和方法上標註的Intercept和MyCache特性不是必須的,接下來會說;

  • UserService對介面的實現;

    image-20210303124839953

  • MyCacheAttribute自定義特性,用於標識,裡面沒有任何邏輯處理;

    image-20210303124900862

  • MyInterceptor自定義攔截器,面向切面的邏輯程式碼在這裡處理;

    image-20210303124958451

    程式碼完了,就開始使用Autofac註冊服務,進行使用啦,如下:

    image-20210303125134012

    註:Autofac不是必須的,根據自己需要進行選擇使用,這裡是為了要使用Autofac的切面編程功能。

1.3 兩種方式進行快取處理

通常在非Web程式中,有以下兩種方式進行快取處理:

  • 程式碼嵌入到業務邏輯,在真實業務邏輯處進行快取獲取或設置;

    image-20210303125430137

    這樣很大一個缺點是每一個快取的數據都需要手動到指定業務邏輯中添加快取處理,程式碼後期不好維護,快取功能的開啟和關閉也不好控制,需要修改程式碼進行滿足。

  • 面向切面編程,無需嵌入多餘程式碼到業務中

    通過面向切面的思想,以動態代理的原理攔截方法,在方法前後進行處理,如下:

    image-20210303130048764

    快取邏輯直接放在攔截器中處理即可,如下:

    image-20210303130259027

    註冊服務時,開啟Autofac的面向切面功能即可

    image-20210303130351440

    運行看效果,第二次都是從快取中獲取數據,美美噠:

    image-20210303130505631

註:推薦使用面向切面的形式進行處理,這樣快取功能可插拔,程式碼維護性也好。

2.WebApiDemo(項目名稱為:MemoryCacheWebApiDemo)

在WebApi中使用就比較簡單啦,關於MemoryCache的依賴包已經集成在框架中,如果需要使用,直接註冊服務就可以用啦;通常在WebApi中進行快取處理的方式有三種:

  • 中間件形式:通過自定義中間件進行快取邏輯操作;
  • 過濾器形式:使用MVC相關過濾器進行快取邏輯操作;
  • 業務層面向切面形式:面向切面的方式,在業務層做快取,集成Autofac即可,和控制台Demo的面向切面一樣;

這裡就用過濾器的形式進行Demo演示,走起來~~~

2.1 編寫過濾器

快取如果能在最前面處理,就優先在最前面,所以使用的ResourceFilter過濾器;關於過濾器之前寫過一篇文章很詳細(跟我一起學.NetCore之MVC過濾器,這篇看完走路可以仰著頭走),這裡就不贅述了。

image-20210303131403185

然後在Startup.cs中註冊服務和將自定義過濾器註冊即可,如下:

image-20210303131617185

然後啟動運行,多次請求調試看看效果(小夥伴自己在過濾器中調試即可);

在WebApi中也可以使用業務層面向切面的方式進行相關業務處理,集成Autofac即可;小夥伴可以參照這篇文章(跟我一起學.NetCore之Asp.NetCore中集成Autofac擴展)。 關於AOP面向切面編程這塊,後續單獨整理一篇分享;

源碼地址://github.com/zyq025/DotNetCoreStudyDemo

總結

在一些開發項目中,可能會使用Dictionary進行數據快取,如果是這樣,可以嘗試使用MemoryCache,性能合理利用CPU,而且還是執行緒安全的;另外在高並發場景,可以用其作為多級快取,因為MemoryCache還能設置過期時間,搭配Redis配合使用,效果杠杠的。

一個被程式搞丑的帥小伙,關注”Code綜藝圈”,跟我一起學~~~