如果你還沒有 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 主要考慮轉換方便。
入門的知識就介紹到這裡,更多的 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
提供的介面太少了:
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 介面提供的方法實在太少了,有四個非同步方法四個同步方法,這裡只介紹非同步方法。
方法 | 說明 |
---|---|
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);