Redis 入門與 ASP.NET Core 快取

痴者工良發表於2020-10-23

如果你還沒有 redis 叢集,可以參考筆者的另一篇文章:搭建分散式 Redis Cluster 叢集與 Redis 入門

本文將使用 StackExchange.Redis 庫來連線和操作 Redis 。

StackExchange.Redis 的使用,本文只是參照文件,換種方式表示,如果英文基礎好,建議閱讀文件: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 ,使用最新版本。

Redis 預設埠為 6379,如果要連線本地 Redis 服務:

ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379");
// ”{ip}:{port}“

如果使用 redis 叢集,則使用 , 分隔地址:

ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("server1:port1,server2:port2,server3:port3");

可能要注意區分叢集模式,多 redis 例項的地址,適合主從模式的叢集或者 redis culster 叢集,哨兵模式筆者還沒有測試過。

能用 redis 幹啥

redis 具有很多應用場景,一般使用到的場景有:

  • 儲存資料(當資料庫使用)
  • 利用 pub/sub 做訊息佇列

接下來將介紹這兩種場景的使用方法。

Redis 資料庫儲存

訪問 redis 資料庫:

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

IDatabase 中包含 string 型別的資料操作,其 API 使用 String 開頭,辨識度高。

設定一個字串資料:

            db.StringSet("A", "這是一條字串資料的值");
            var value = db.StringGet("A");

如果字串使用 byte[] (二進位制)儲存,也可以設定值:

            byte[] str=... ...
            db.StringSet("A", str;

Redis 裡面,還有其它很多型別,這裡我們只介紹字串,因為 API 其實就那麼些,用到的時候再學也可以的。先學字串的使用,其它就是觸類旁通了。

訂閱釋出

訂閱某個 Topic,當其改變狀態時,訂閱者可以收到通知,做分散式訊息佇列也行。類似 MQTT 協議這樣。

獲取訂閱器:

ISubscriber sub = redis.GetSubscriber();

選擇訂閱的 Topic,並設定回撥函式:

sub.Subscribe("Message", (channel, message) => {
    Console.WriteLine((string)message);
});

當某一方訂閱了 Message ,在另一個地方,有別的客戶端(也可以是自己)推送 Topic :

sub.Publish("Message","你有一條新的訊息,請注意查收");

Topic 推送後,訂閱方可以收到推送的訊息。

測試程式碼

            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隱式轉換

入門的知識就介紹到這裡,更多的 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.CachingMemoryCache`。

在記憶體中快取、儲存資料

在 ASP.NET Core 的記憶體快取之外,我們來討論一下,編寫程式碼時,自己設定的記憶體快取是否合理。

我們都知道,使用記憶體快取是為了提高程式碼效能而用的

這裡筆者個人認為可以從兩個層次來對這種快取歸類討論。

第一種,對於要多次使用、而每次使用都需要計算、源資料相同則結果相同的,可以使用記憶體快取。例如反射就比較消耗時間(速度慢),可以使用記憶體快取起來,下次直接取得資訊而不需要重新計算。

下面筆者說一下理由。

記憶體快取用在反射快取這類快取上,快取的資料來源是可確定的、可計算總量的,而且這部分記憶體不需要頻繁增加或者減少,不僅提高了效能,對 GC 來說也可以一定程度上減少回收壓力,更重要的是開發者可以降低快取的複雜程度。

這種快取主要為了避免重複計算,或者重複匯入(例如載入程式集、從檔案載入資料)等。如果資料最近出現過,而且後面一段時間不會變化,使用記憶體來快取也很實在,例如 MVC 的檢視、每15分鐘重新整理一次的排行榜等。

第二種是使用記憶體儲存資料,很多人單純是因為記憶體儲存資料特別快,把記憶體當作資料庫來玩,因此很容易導致記憶體洩露。最常見的就是使用靜態字典、靜態列表等,然後編寫方法增刪查改資料,這一類在壓力測試下或者請求量大一些、變動比較頻繁的時候,記憶體堆積特別厲害。

需要頻繁變化或需要實時變化的資料,儲存在記憶體中確實速度非常快,如何確定資料失效、去除無用資料等需要有很深的考慮。

另外,在記憶體中如使用字典大量儲存資料,資料量很多的情況下,每次索引資料的時間都會變長,如果使用了 Linq 或者 for 或者 foreach 等檢索資料,也很容易出現耗時長的時間複雜度。這種情況下,你是相信自己的程式碼,還是相信 Mysql、SqlServer 等資料庫? Hash 演算法和紅黑樹都瞭解了嘛?

如果實在有需求需要使用記憶體快取資料,並且可能動態增加或移除資料的話,可以使用 WeakReference 弱引用,即在引用物件的同時仍然允許 GC 回收該物件。缺點是資料可能丟失,不適合需要持久化的資料。

但無論情況,我們可以確定:

  • 快取都是副本
  • 快取丟失不影響程式的使用
  • 快取不能無限增長
  • 快取避免複雜結構
  • ... ...

IMemoryCache

IMemoryCache 提供的介面太少了:

        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/aspnet/core/performance/caching/distributed?view=aspnetcore-3.1#distributed-memory-cache

分散式快取

ASP.NET Core 分散式快取,則使用了 IDistributedCache 這個統一的介面。如果你在 Nuget 搜尋 IDistributedCache ,會發現相關的庫非常多。

分散式快取的使用,除了最常見的 Redis,SQLServer 也行,只要實現了 IDistributedCache 就ok。

IDistributedCache

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 是 ASP.NET Core 官方推薦的 Redis 框架,並且官方對其做了封裝,可以到 Nuget 搜尋 Microsoft.Extensions.Caching.StackExchangeRedis

RedisCache 繼承了 IDistributedCache 介面。

Startup.ConfigureServices 中配置服務註冊:

            services.AddStackExchangeRedisCache(options =>
            {
                options.Configuration = "ip:埠,ip1:埠,ip2:埠";	// redis 叢集或單機
                options.InstanceName = "mvc";						// 例項 名稱
            });

依賴注入:

        private readonly IDistributedCache _cache;

示例:

        public async Task<string> Test(string key,string value)
        {
            await _cache.SetStringAsync(key, value);
            return await _cache.GetStringAsync(key);
        }

設定快取時間:

            var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
            await _cache.SetStringAsync(key, value, options);

相關文章