FreeRedis 是一款繼 CSRedisCore 以後重寫的 .NET redis 客戶端開源組件,以 MIT 協議開源託管於 github,目前支持 .NET 五、.NETCore 2.1+、.NETFramework 4.0+、Xamarin,有可能已經支持 AOT 編譯(目前未測試,但會往這個方向走)。git
github: https://github.com/2881099/FreeRedisgithub
Redis是一個開源的使用C語言編寫、開源、支持網絡、可基於內存亦可持久化的日誌型、高性能的Key-Value數據庫,並提供多種語言的API。它一般被稱爲 數據結構服務器 ,由於值(value)能夠是 字符串(string)、哈希(map)、 列表(list)、集合(sets)、有序集合(sorted sets)、地理位置(Geo)、消息列隊(Streams)等類型。redis
與其餘 key - value 緩存產品有如下三個特色:數據庫
優點:json
Redis 6.0 是一個可期的版本,增長了 RESP3.0 協議,ACL 權限控制,從原有的單線程改成多線程(性能提高2-3倍)等諸多更新。今天向你們介紹他的另外一個重要特性:客戶端緩存技術,講解如何落設計在 .NET 中。c#
爲何須要客戶端緩存?緩存
咱們都知道,使用 Redis 進行數據的緩存的主要目的是減小對 MySQL 等數據庫的訪問,提供更快的訪問速度,畢竟 《Redis in Action》中提到的, Redis 的性能大體是普通關係型數據庫的 10 ~ 100 倍。bash
因此,以下圖所示,Redis 用來存儲熱點數據,Redis 未命中,再去訪問數據庫,這樣能夠應付大多數狀況下的性能要求。服務器
可是,Redis 也有其性能上限,而且訪問 Redis 必然有必定的網絡 I/O 以及序列化反序列化損耗。因此,每每會引入進程緩存,將最熱的數據存儲在本地,進一步加快訪問速度。網絡
如上圖所示,Guava Cache 等進程緩存做爲一級緩存,Redis 做爲二級緩存:
只使用 Redis 分佈式緩存時,遇到數據更新時,應用程序更新完 MySQL 中的數據,能夠直接將 Redis 中對應緩存失效掉,保持數據的一致性。
而進程內緩存的數據一致性比分佈式的緩存面臨更大的挑戰。數據更新的時候,如何通知其餘進程也更新本身的緩存呢?
若是按照分佈式緩存的思路,咱們能夠設置極短的緩存失效時間,這樣沒必要實現複雜的通知機制。
可是不一樣進程內的數據依然會面臨不一致的問題,而且不一樣進程緩存失效時間不統一,同一個請求到了不一樣的進程,可能出現反覆幻讀的狀況。
如上當 key 失效的時候,Redis 6.0 提供了三種模式通知客戶端,普通模式、廣播模式、轉發模式。
一、普通模式
普通模式依賴 RESP3.0 協議,須要在鏈接成功時使用 hello 命令開啓 RESP3.0 模式。
hello 3
client tracking on
落地在 .NET 之中時,咱們必然是使用鏈接池技術,那麼每一個鏈接都必須使用以上的兩個命令,此時每一個鏈接是一個循環讀的操做,以下:
while (true) { var msg = await redisSocket.ReceiveAsync(); //等待 key 失效的通知 }
原本咱們能夠比較簡單的這樣執行命令:
await redisSocket.SendAsync("GET key1"); await redisSocket.ReceiveAsync(); //讀取響應的結果
能夠看出來,兩段代碼同時讀,會致使讀取的結果錯亂。如何解決還須要三思,而咱們 PASS 了這種模式。
二、廣播模式
廣播模式和普通模式差很少,都須要依賴 RESP3.0 協議。這種方式下 Redis 服務端再也不消耗過多內存存儲信息,而是發送更多的失效消息給客戶端。
與普通模式必須獲取一次鍵的規則不一樣,廣播模式下,只要鍵被修改或刪除,符合規則的客戶端都會收到失效消息,並且是能夠屢次獲取的
與普通模式相比,雖然少存儲了一些數據,可是因爲須要對前綴規則進行匹配,會消耗必定的 CPU 資源,因此注意別使用過長的前綴。
廣播模式和普通模式同樣,須要解決命令同時讀取的問題(請見上面的兩段代碼)。
三、轉發模式
Redis 爲了兼容 RESP2 協議提供了轉發(Redirect)模式,再也不使用 RESP3 原生支持 PUSH 消息,而是將消息經過 Pub/Sub 通知給另一個客戶端,具體流程以下圖所示。
public void Start() { //訂閱 __redis__:invalidate _sub = _cli.Subscribe("__redis__:invalidate", InValidate) as IPubSubSubscriber; //攔截緩存 _cli.Interceptors.Add(() => new MemoryCacheAop(this)); //當網絡斷開的時候,清空本地緩存 _cli.Unavailable += (_, e) => { lock (_dictLock) _dictSort.Clear(); _dict.Clear(); }; _cli.Connected += (_, e) => { //最關鍵的一個命令,不然 __redis__:invalidate 沒法收到訂閱消息 e.Client.ClientTracking(true, _sub.RedisSocket.ClientId, null, false, false, false, false); }; } void InValidate(string chan, object msg) { var keys = msg as object[]; foreach (var key in keys) //移除本地緩存 RemoveCache(string.Concat(key)); }
MemoryCacheAop 是 FreeRedis 已經實現好的攔截器,主要實現攔截命令執行,獲取本地內存。完整代碼:https://github.com/2881099/FreeRedis/blob/master/src/FreeRedis/ClientSideCaching.cs
static Lazy<RedisClient> _cliLazy = new Lazy<RedisClient>(() => { var r = new RedisClient("192.168.164.10:6379,database=1"); //redis 6.0 r.Serialize = obj => JsonConvert.SerializeObject(obj); r.Deserialize = (json, type) => JsonConvert.DeserializeObject(json, type); r.Notice += (s, e) => Console.WriteLine(e.Log); return r; }); static RedisClient cli => _cliLazy.Value; static void Main(string[] args) { cli.UseClientSideCaching(new ClientSideCachingOptions { //本地緩存的容量 Capacity = 3, //過濾哪些鍵能被本地緩存 KeyFilter = key => key.StartsWith("Interceptor"), //檢查長期未使用的緩存 CheckExpired = (key, dt) => DateTime.Now.Subtract(dt) > TimeSpan.FromSeconds(2) }); cli.Set("Interceptor01", "123123"); //redis-server var val1 = cli.Get("Interceptor01"); //redis-server var val2 = cli.Get("Interceptor01"); //本地 var val3 = cli.Get("Interceptor01"); //斷點等3秒,redis-server cli.Set("Interceptor01", "234567"); //redis-server var val4 = cli.Get("Interceptor01"); //redis-server var val5 = cli.Get("Interceptor01"); //本地 var val6 = cli.MGet("Interceptor01", "Interceptor02", "Interceptor03"); //redis-server var val7 = cli.MGet("Interceptor01", "Interceptor02", "Interceptor03"); //本地 var val8 = cli.MGet("Interceptor01", "Interceptor02", "Interceptor03"); //本地 cli.MSet("Interceptor01", "Interceptor01Value", "Interceptor02", "Interceptor02Value", "Interceptor03", "Interceptor03Value"); //redis-server var val9 = cli.MGet("Interceptor01", "Interceptor02", "Interceptor03"); //redis-server var val10 = cli.MGet("Interceptor01", "Interceptor02", "Interceptor03"); //本地 //如下 KeyFilter 返回 false,從而不使用本地緩存 cli.Set("123Interceptor01", "123123"); //redis-server var val11 = cli.Get("123Interceptor01"); //redis-server var val12 = cli.Get("123Interceptor01"); //redis-server var val23 = cli.Get("123Interceptor01"); //redis-server Console.ReadKey(); }
cli.Notice 事件在控制檯輸出內容:
Not connected 表明沒有通過 redis-server
192.168.164.10:6379 > CLIENT TRACKING ON REDIRECT 46 FreeRedis.RedisResult (0ms) 192.168.164.10:6379 > SET Interceptor01 123123 OK (24ms) 192.168.164.10:6379 > GET Interceptor01 123123 (2ms) Not connected > GET Interceptor01 123123 (0ms) 192.168.164.10:6379 > GET Interceptor01 123123 (0ms) 192.168.164.10:6379 > SET Interceptor01 234567 OK (0ms) 192.168.164.10:6379 > GET Interceptor01 234567 (0ms) Not connected > GET Interceptor01 234567 (0ms) 192.168.164.10:6379 > MGET Interceptor01 Interceptor02 Interceptor03 [234567, Interceptor02Value, Interceptor03Value] (0ms) Not connected > MGET Interceptor01 Interceptor02 Interceptor03 [234567, Interceptor02Value, Interceptor03Value] (0ms) Not connected > MGET Interceptor01 Interceptor02 Interceptor03 [234567, Interceptor02Value, Interceptor03Value] (0ms) 192.168.164.10:6379 > MSET Interceptor01 Interceptor01Value Interceptor02 Interceptor02Value Interceptor03 Interceptor03Value False (3ms) 192.168.164.10:6379 > MGET Interceptor01 Interceptor02 Interceptor03 [Interceptor01Value, Interceptor02Value, Interceptor03Value] (1ms) Not connected > MGET Interceptor01 Interceptor02 Interceptor03 [Interceptor01Value, Interceptor02Value, Interceptor03Value] (0ms) 192.168.164.10:6379 > SET 123Interceptor01 123123 OK (0ms) 192.168.164.10:6379 > GET 123Interceptor01 123123 (0ms) 192.168.164.10:6379 > GET 123Interceptor01 123123 (0ms) 192.168.164.10:6379 > GET 123Interceptor01 123123 (0ms)
FreeRedis 是一款繼 CSRedisCore 以後重寫的 .NET redis 客戶端開源組件,以 MIT 協議開源託管於 github,目前支持 .NET 五、.NETCore 2.1+、.NETFramework 4.0+、Xamarin,有可能已經支持 AOT 編譯(目前未測試,但會往這個方向走)。
github: https://github.com/2881099/FreeRedis
謝謝支持!!