1. 程式人生 > >Redis 入門與 ASP.NET Core 快取

Redis 入門與 ASP.NET Core 快取

[TOC] 如果你還沒有 redis 叢集,可以參考筆者的另一篇文章:[搭建分散式 Redis Cluster 叢集與 Redis 入門](https://www.cnblogs.com/whuanle/p/13837153.html) 本文將使用 [StackExchange.Redis](https://stackexchange.github.io/StackExchange.Redis/) 庫來連線和操作 Redis 。 `StackExchange.Redis` 的使用,本文只是參照文件,換種方式表示,如果英文基礎好,建議閱讀文件:[https://stackexchange.github.io/StackExchange.Redis/Basics](https://stackexchange.github.io/StackExchange.Redis/Basics) 本文內容介紹 `StackExchange.Redis` 的使用基礎,然後介紹 ASP.NET Core 中的快取、如何使用 Redis。 ## 基礎 ### Redis 庫 C# 下 Redis-Client 開源的庫很多,有 BeetleX.Redis、csredis、Nhiredis、redis-sharp、redisboost、Rediska、ServiceStack.Redis、Sider、StackExchange.Redis、TeamDev Redis Client。 這裡我們使用 StackExchange.Redis,另外 csredis 現在葉老闆(Freesql作者)貢獻了大量維護,並且葉老闆新開了一個叫 FreeRedis 的框架,目前正在開發中,有興趣可以參與開發或提出建議。 ### 連線 Redis 建立一個 .NET Core 專案,Nuget 庫新增引用 [StackExchange.Redis](https://stackexchange.github.io/StackExchange.Redis/) ,使用最新版本。 Redis 預設埠為 6379,如果要連線本地 Redis 服務: ```csharp ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost"); ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379"); // ”{ip}:{port}“ ``` 如果使用 redis 叢集,則使用 `,` 分隔地址: ```csharp ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("server1:port1,server2:port2,server3:port3"); ``` 可能要注意區分叢集模式,多 redis 例項的地址,適合主從模式的叢集或者 redis culster 叢集,哨兵模式筆者還沒有測試過。 ### 能用 redis 幹啥 redis 具有很多應用場景,一般使用到的場景有: * 儲存資料(當資料庫使用) * 利用 pub/sub 做訊息佇列 接下來將介紹這兩種場景的使用方法。 ### Redis 資料庫儲存 訪問 redis 資料庫: ```csharp IDatabase db = redis.GetDatabase(); ``` Redis 預設有 16 個數據庫,可以 `GetDatabase(int db )` 獲取指定的資料庫。 使用了 redis cluster 叢集的 redis 節點,只有一個數據庫,不能自由選擇。這裡我們只需要使用 `redis.GetDatabase()` 即可 。 Redis 使用比較簡單的,大多時候,只要有相應的應用場景,我們查詢文件很快就可以掌握,所以這裡只介紹字串的使用。 #### 字串 redis 的字串參考:[https://www.cnblogs.com/whuanle/p/13837153.html#字串string](https://www.cnblogs.com/whuanle/p/13837153.html#字串string) IDatabase 中包含 string 型別的資料操作,其 API 使用 `String` 開頭,辨識度高。 設定一個字串資料: ```csharp db.StringSet("A", "這是一條字串資料的值"); var value = db.StringGet("A"); ``` 如果字串使用 byte[] (二進位制)儲存,也可以設定值: ```csharp byte[] str=... ... db.StringSet("A", str; ``` Redis 裡面,還有其它很多型別,這裡我們只介紹字串,因為 API 其實就那麼些,用到的時候再學也可以的。先學字串的使用,其它就是觸類旁通了。 ### 訂閱釋出 訂閱某個 Topic,當其改變狀態時,訂閱者可以收到通知,做分散式訊息佇列也行。類似 MQTT 協議這樣。 獲取訂閱器: ```csharp ISubscriber sub = redis.GetSubscriber(); ``` 選擇訂閱的 Topic,並設定回撥函式: ```csharp sub.Subscribe("Message", (channel, message) => { Console.WriteLine((string)message); }); ``` 當某一方訂閱了 `Message` ,在另一個地方,有別的客戶端(也可以是自己)推送 Topic : ```csharp sub.Publish("Message","你有一條新的訊息,請注意查收"); ``` Topic 推送後,訂閱方可以收到推送的訊息。 測試程式碼 ```csharp ISubscriber sub = redis.GetSubscriber(); sub.Subscribe("Message", (channel, message) => { Console.WriteLine((string)message); }); Thread.Sleep(1000); sub.Publish("Message","你有一條新的訊息,請注意查收"); ``` channel :Topic 的名稱,即上面的 Message。 message:推送的訊息內容。 ### RedisValue 使用 API 設定值的時候,都會有這個引數。因為 Redis 中的值只能是 “字串”,因此 C# 中也要遵守這種規則,但是 C# 是強型別語言,而且有那麼多值型別,只使用 string ,編寫程式碼時會有諸多不便。 因此,就建立了 RedisValue 這個型別,裡面有大量的隱式轉換過載,所以我們可以使用 C# 的簡單型別儲存資料以及獲取資料,避免手工轉換。 當然這個說法不是很準確,使用 RedisValue 主要考慮轉換方便。 ![RedisValue隱式轉換](https://img2020.cnblogs.com/blog/1315495/202010/1315495-20201019212458231-1406173484.png) 入門的知識就介紹到這裡,更多的 Redis 知識可以檢視官方文件。下面開始介紹 AS.NET Core 使用分散式快取。 ## ASP.NET Core 快取與分散式快取 ASP.NET Core 裡面有很多定義的標準介面,例如日誌、快取等,這些介面為開發者設定了統一的定義和功能,上層服務不需要變更程式碼就能切換類庫,底層使用哪種庫對上層沒有影響。 ASP.NET Core 中的快取,可以使用多種方式完成,例如 Redis,記憶體,關係型資料庫,檔案快取等。而且根據拓展性,可以分為本機快取,分散式快取。 本機快取常見的是記憶體快取,記憶體快取可以儲存任何物件。 分散式快取最常見的是 Redis,分散式快取介面僅限 `byte[]`(指引數,繼續看到後面的小節就明白了) 。 記憶體快取和分散式快取都使用鍵值對來儲存快取項。 ### 記憶體中的快取 #### ASP.NET Core 的記憶體快取 ASP.NET Core 記憶體快取是指一般是單機(本機)使用的,一般這種記憶體快取框架是 `System.Runtime` 或 Microsoft 包提供的,因為不需要考慮分散式或者複雜的結構,所以一般不需要第三方庫。這裡的記憶體快取並不只是指資料在記憶體中,所以需要區分 Redis 這類專業的快取框架。且這裡快取只是作為提高效能而用。 這種快取主要有兩種功能比較豐富的實現 System.Runtime.Caching` 和 `MemoryCache`。 #### 在記憶體中快取、儲存資料 在 ASP.NET Core 的記憶體快取之外,我們來討論一下,編寫程式碼時,自己設定的記憶體快取是否合理。 我們都知道,**使用記憶體快取是為了提高程式碼效能而用的**。 這裡筆者個人認為可以從兩個層次來對這種快取歸類討論。 第一種,對於要多次使用、而每次使用都需要計算、源資料相同則結果相同的,可以使用記憶體快取。例如反射就比較消耗時間(速度慢),可以使用記憶體快取起來,下次直接取得資訊而不需要重新計算。 下面筆者說一下理由。 記憶體快取用在反射快取這類快取上,快取的資料來源是可確定的、可計算總量的,而且這部分記憶體不需要頻繁增加或者減少,不僅提高了效能,對 GC 來說也可以一定程度上減少回收壓力,更重要的是開發者可以降低快取的複雜程度。 這種快取主要為了避免重複計算,或者重複匯入(例如載入程式集、從檔案載入資料)等。如果資料最近出現過,而且後面一段時間不會變化,使用記憶體來快取也很實在,例如 MVC 的檢視、每15分鐘重新整理一次的排行榜等。 第二種是使用記憶體儲存資料,很多人單純是因為記憶體儲存資料特別快,把記憶體當作資料庫來玩,因此**很容易導致記憶體洩露**。最常見的就是使用靜態字典、靜態列表等,然後編寫方法增刪查改資料,這一類在壓力測試下或者請求量大一些、變動比較頻繁的時候,記憶體堆積特別厲害。 需要頻繁變化或需要實時變化的資料,儲存在記憶體中確實速度非常快,如何確定資料失效、去除無用資料等需要有很深的考慮。 另外,在記憶體中如使用字典大量儲存資料,資料量很多的情況下,每次索引資料的時間都會變長,如果使用了 Linq 或者 for 或者 foreach 等檢索資料,也很容易出現耗時長的時間複雜度。這種情況下,你是相信自己的程式碼,還是相信 Mysql、SqlServer 等資料庫? Hash 演算法和紅黑樹都瞭解了嘛? 如果實在有需求需要使用記憶體快取資料,並且可能動態增加或移除資料的話,可以使用 WeakReference 弱引用,即在引用物件的同時仍然允許 GC 回收該物件。缺點是資料可能丟失,不適合需要持久化的資料。 但無論情況,我們可以確定: - 快取都是副本 - 快取丟失不影響程式的使用 * 快取不能無限增長 * 快取避免複雜結構 * ... ... #### IMemoryCache `IMemoryCache` 提供的介面太少了: ```csharp ICacheEntry CreateEntry(object key); void Remove(object key); bool TryGetValue(object key, out object value); ``` 適合單一的鍵值快取。 此介面在 `Microsoft.Extensions.Caching.Memory` 中有實現,例如 MemoryCache 。適合 ASP.NET Core 中使用。 #### MemoryCache 這裡的 MemoryCache 並不是指 IMemoryCache 的實現,而是指 `System.Runtime.Caching.MemoryCache`,需要安裝 Nuget 包。 可以實現對例項物件的快取,請檢視檢視官方文件:[https://docs.microsoft.com/zh-cn/dotnet/api/system.runtime.caching.memorycache?view=dotnet-plat-ext-3.1](https://docs.microsoft.com/zh-cn/dotnet/api/system.runtime.caching.memorycache?view=dotnet-plat-ext-3.1) 另外記憶體快取還有一個分散式記憶體快取,但不是真正的分散式,資訊可以參考:[https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed?view=aspnetcore-3.1#distributed-memory-cache](https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed?view=aspnetcore-3.1#distributed-memory-cache) ### 分散式快取 ASP.NET Core 分散式快取,則使用了 **IDistributedCache** 這個統一的介面。如果你在 Nuget 搜尋 **IDistributedCache** ,會發現相關的庫非常多。 分散式快取的使用,除了最常見的 Redis,SQLServer 也行,只要實現了 **IDistributedCache** 就ok。 ![IDistributedCache](https://img2020.cnblogs.com/blog/1315495/202010/1315495-20201022214446474-1758912220.png) #### IDistributedCache IDistributedCache 介面提供的方法實在太少了,有四個非同步方法四個同步方法,這裡只介紹非同步方法。 | 方法 | 說明 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | GetAsync(String, CancellationToken) | 獲取一個鍵的值 | | RefreshAsync(String, CancellationToken) | 基於快取中某個值的鍵重新整理該值,並重置其可調到期超時(如果有) | | RemoveAsync(String, CancellationToken) | 刪除一個鍵 | | SetAsync(String, Byte[], DistributedCacheEntryOptions, CancellationToken) | 設定一個鍵的值 | 侷限還是很大的,只能使用字串。估計大家可能沒怎麼使用? ASP.NET Core 官方支援的分散式快取,目前主要有 NCache、Redis、SqlServer。本節只討論 Redis。 #### Redis 快取 [StackExchange.Redis](https://stackexchange.github.io/StackExchange.Redis/) 是 ASP.NET Core 官方推薦的 Redis 框架,並且官方對其做了封裝,可以到 Nuget 搜尋 `Microsoft.Extensions.Caching.StackExchangeRedis` 。 RedisCache 繼承了 IDistributedCache 介面。 Startup.ConfigureServices 中配置服務註冊: ```csharp services.AddStackExchangeRedisCache(options => { options.Configuration = "ip:埠,ip1:埠,ip2:埠"; // redis 叢集或單機 options.InstanceName = "mvc"; // 例項 名稱 }); ``` 依賴注入: ```csharp private readonly IDistributedCache _cache; ``` 示例: ```csharp public async Task Test(string key,string value) { await _cache.SetStringAsync(key, value); return await _cache.GetStringAsync(key); } ``` 設定快取時間: ```csharp var options = new DistributedCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromSeconds(20)); await _cache.SetStringAsync(key, value, option