如果你還沒有 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);