一. 整體介紹
1. 說明
CSRedis 是 redis.io 官方推薦庫,支持 redis-trib集群、哨兵、私有分區與連接池管理技術,簡易 RedisHelper 靜態類, 它主要又兩個程序集。
(1).CSRedisCore:主庫,實現對接redis各種功能
(2).Caching.CSRedis:分布式緩存 CSRedisCore 實現 Microsoft.Extensions.Caching
相關地址如下:
GitHub地址:https://github.com/2881099/csredis
Nuget地址:https://www.nuget.org/packages/CSRedisCore/
2. 主要特點
(1).調用方法的時候,可以使用CSRedisClient實例化的對象,也可以使用全局類RedisHelper(需要Initialization初始化一下)
注:無論是CSRedisClient實例化的對象還是RedisHelper調用的方法和Redis自身cli指令名字完全相同,這一點非常好!!
(2).官方推薦配置:CSRedisClient is singleton, RedisHelper static class is recommended (CSRedisClient推薦配置單例模式,RedisHelper推薦靜態)
(3).支持geo類型(>=3.2)、stream類型(>=5.0)
(4).支持主從、哨兵、cluster
二. 如何集成
1. 控制台中使用
前提:通過Nuget安裝程序集:CSRedisCore
(1).用法1:直接實例化CSRedisClient進行使用
(2).用法2:初始化幫助類RedisHelper幫助類進行使用
代碼分享:
{ //用法1-CSRedisClient實例化的對象(生產環境中把CSRedisClient寫成單例類) var rds = new CSRedis.CSRedisClient("119.45.174.xx:6379,password=123456,defaultDatabase=0"); rds.Set("name1", "ypf"); var result1 = rds.Get("name1"); Console.WriteLine($"name1={result1}"); //用法2-RedisHelper幫助類 RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.xx:6379,password=123456,defaultDatabase=0")); RedisHelper.Set("name2", "ypf2"); var result2 = RedisHelper.Get("name2"); Console.WriteLine($"name2={result2}"); }
ps:CSRedisClient鏈接字符串的說明
2. CoreMVC中使用
前提:通過Nuget安裝程序集:CSRedisCore,同樣有兩種用法
(1).在ConfigureSevice實例化CSRedisClient,並注冊成單例模式;然后實例化RedisHelper幫助類
(2).在action如果使用CSRedisClient,需要注入后再使用
(3).再action如果使用RedisHelper幫助類,可以直接使用,不需要注冊
代碼分享:
配置文件
"RedisStr": "119.45.174.xx:6379,password=123456,defaultDatabase=0"
ConfigureService:
public void ConfigureServices(IServiceCollection services) { //1. 集成Redis的兩種方式 { //用法1-CSRedisClient實例化的對象 var rds = new CSRedis.CSRedisClient(Configuration["RedisStr"]); services.AddSingleton(rds); //注冊成全局單例 //用法2-RedisHelper幫助類 RedisHelper.Initialization(rds); } services.AddControllersWithViews(); }
調用:
public class HomeController : Controller { private CSRedisClient _csredis; public HomeController(CSRedisClient csredis) { this._csredis = csredis; } public IActionResult Index() { #region 01-如何集成 //{ // //1. 用法1,需要注入 // _csredis.Set("name1", "ypf11"); // var result1 = _csredis.Get("name1"); // Console.WriteLine($"name1={result1}"); // //2. 用法2,直接使用RedisHelp類即可 // RedisHelper.Set("name2", "ypf22"); // var result2 = RedisHelper.Get("name2"); // Console.WriteLine($"name2={result2}"); //} #endregion return View(); } }
3. CoreMVC緩存集成
前提:通過Nuget安裝程序集【Caching.CSRedis】,注冊方式和CoreMVC的默認分布式緩存的方式有點區別,這里更加簡單粗暴,直接注冊IDistributedCache對象即可
(1).在ConfigureService中注冊IDistributedCache單例對象
(2).在控制器中注入進行使用即可,包含的方法默認修改的模式相同
代碼分享
ConfigureService

//2. 注冊基於Redis的分布式緩存 { var csredis = new CSRedis.CSRedisClient(Configuration["RedisStr"]); services.AddSingleton<IDistributedCache>(new Microsoft.Extensions.Caching.Redis.CSRedisCache(csredis)); //集群模式 // var csredis = new CSRedis.CSRedisClient(null, // "127.0.0.1:6371,pass=123,defaultDatabase=11,poolsize=10,ssl=false,writeBuffer=10240,prefix=key前輟", // "127.0.0.1:6372,pass=123,defaultDatabase=12,poolsize=11,ssl=false,writeBuffer=10240,prefix=key前輟", // "127.0.0.1:6373,pass=123,defaultDatabase=13,poolsize=12,ssl=false,writeBuffer=10240,prefix=key前輟", // "127.0.0.1:6374,pass=123,defaultDatabase=14,poolsize=13,ssl=false,writeBuffer=10240,prefix=key前輟"); // services.AddSingleton<IDistributedCache>(new Microsoft.Extensions.Caching.Redis.CSRedisCache(csredis)); }
調用
public class HomeController : Controller { private IDistributedCache _dCache; public HomeController(IDistributedCache dCache) { this._dCache = dCache; } public IActionResult Index() { #region 02-緩存集成 { _dCache.SetString("lmr", "1234567"); var data = _dCache.GetString("lmr"); } #endregion return View(); }
}
4.分享一種封裝模式(推薦)
(1). 配置文件中聲明緩存類型和鏈接字符串
(2). 新建CacheExtension類進行不同類型的緩存初始化(redis自身初始化和redis緩存依賴初始化)
(3). 在Program中進行UseCache
(4). 進行測試
代碼分享:
配置文件:
//緩存類型 //有兩種取值 (Redis:代表使用redis緩存, 並實例化redis相關對象. Memory:代表使用服務端緩存) "CacheType": "Redis", "RedisStr": "119.45.174.xxx:6379,password=123456,defaultDatabase=0"
策略類:
/// <summary> /// 緩存擴展 /// </summary> public static class CacheExtension { /// <summary> /// 使用緩存 /// </summary> /// <param name="hostBuilder">建造者</param> /// <returns></returns> public static IHostBuilder UseCache(this IHostBuilder hostBuilder) { hostBuilder.ConfigureServices((buidlerContext, services) => { var CacheType = buidlerContext.Configuration["CacheType"].ToString(); switch (CacheType) { case "Memory": services.AddDistributedMemoryCache(); break; case "Redis": { //初始化redis的兩種使用方式 var csredis = new CSRedisClient(buidlerContext.Configuration["RedisStr"].ToString()); services.AddSingleton(csredis); RedisHelper.Initialization(csredis); //初始化緩存基於redis services.AddSingleton<IDistributedCache>(new CSRedisCache(csredis)); }; break; default: throw new Exception("緩存類型無效"); } }); return hostBuilder; } }
Program:
調用:同上面的調用類似
三. 通用指令、數據類型測試
1. 通用指令
{ Console.WriteLine("通用指令測試"); //1.獲取所有key var d1 = RedisHelper.Keys("*"); //2. 獲取部分key var d2 = RedisHelper.Scan(0, "*", 2); //3. 獲取過期時間 var d3 = RedisHelper.Ttl("name1"); //4. 刪除key var d4 = RedisHelper.Del("name1"); //5. 判斷key是否存在 var d5 = RedisHelper.Exists("name1"); //6. 設置過期時間 var d6 = RedisHelper.Expire("name2", 60); Console.WriteLine("執行完成"); }
2. String類型
{ Console.WriteLine("String類型測試"); RedisHelper.Set("name1", "lmr2"); //默認是不過期的 RedisHelper.Set("name2", "lmr2", 30); //30s過期 RedisHelper.IncrBy("count1", 2); //每次自增2 RedisHelper.IncrBy("count2", -1); //每次自減1 Console.WriteLine("執行完成"); }
3. Hash類型
{ Console.WriteLine("Hash類型測試"); RedisHelper.HSet("myhash", "userName", "ypf"); RedisHelper.HSet("myhash", "userAge", 20); RedisHelper.HIncrBy("myhash", "count", 1); //每次自增1 //獲取key下的所有內容 var data1 = RedisHelper.HGetAll("myhash"); //指定key-filed var dataValue = RedisHelper.HGet<int>("myhash", "userAge"); Console.WriteLine("執行完成"); }
4. List類型
{ Console.WriteLine("List類型測試"); //左側插入 RedisHelper.LPush("myList", "001"); RedisHelper.LPush("myList", "002"); RedisHelper.LPush("myList", "003"); //右側插入 RedisHelper.RPush("myList", "004"); RedisHelper.RPush("myList", "005"); RedisHelper.RPush("myList", "006"); //左側刪除並返回該元素 var d1 = RedisHelper.LPop("myList"); //右側刪除並返回該元素 var d2 = RedisHelper.RPop("myList"); //獲取指定范圍的元素 var data1 = RedisHelper.LRange("myList", 0, 2); //左側開始,獲取前三個元素 var data2 = RedisHelper.LRange("myList", 0, -1); //左側開始,獲取所有 Console.WriteLine("執行完成"); }
5. Set類型
{ Console.WriteLine("Set類型測試"); //增加 RedisHelper.SAdd("school", "a"); RedisHelper.SAdd("school", "b"); RedisHelper.SAdd("school", "c"); RedisHelper.SAdd("school", "d"); RedisHelper.SAdd("school", "e"); //刪除 RedisHelper.SIsMember("school", "b"); //獲取 var data1 = RedisHelper.SMembers("school"); //隨機獲取(不刪除) var data2 = RedisHelper.SRandMember("school"); Console.WriteLine("執行完成"); }
6. SortedSet類型
{ Console.WriteLine("SortedSet類型測試"); //1. 增加 RedisHelper.ZAdd("mySort", (20, "s1"), (20, "s2"), (20, "s3"), (50, "s4"), (80, "s8")); //2. 自增/自減 RedisHelper.ZIncrBy("mySort", "s1", 1); RedisHelper.ZIncrBy("mySort", "s2", -2); RedisHelper.ZIncrBy("mySort", "s3", -3); RedisHelper.ZIncrBy("mySort", "s4", 10); //3. 獲取 var d1 = RedisHelper.ZRange("mySort", 0, 2); var d2 = RedisHelper.ZRange("mySort", 0, -1); //所有元素 //4.按照score排序獲取 var s1 = RedisHelper.ZRevRange("mySort", 0, -1); //從高到低排序 //5. 刪除 RedisHelper.ZRem("mySort", "s1"); Console.WriteLine("執行完成"); }
7. Geo類型
{ Console.WriteLine("Geo類型測試"); //1. 添加地點經緯度 RedisHelper.GeoAdd("myLocation", Convert.ToDecimal(116.20), Convert.ToDecimal(39.56), "北京"); RedisHelper.GeoAdd("myLocation", Convert.ToDecimal(120.51), Convert.ToDecimal(30.40), "上海"); //2. 求兩點之間的距離 var d1 = RedisHelper.GeoDist("myLocation", "北京", "上海", GeoUnit.km); Console.WriteLine("執行完成"); }
四. 集群測試
1. 主從
2. 哨兵
下面地址是哨兵的地址
var csredis = new CSRedis.CSRedisClient("mymaster,password=123,prefix=my_", new [] { "192.169.1.10:26379", "192.169.1.11:26379", "192.169.1.12:26379" });
3. Redis Cluster
(1).環境准備
6個redis實例,建立redis-cluster,地址分別為:192.168.137.202:6379(端口依次次6379-6384),主從關系和節點槽位分配,見下圖
(2).寫法1
寫任意個地址即可,其他節點在運行過程中自動增加,確保每個節點密碼一致。(原理:它會根據 redis-server 返回的 MOVED | ASK 錯誤記錄slot,自動增加節點 Nodes 屬性。)
如下測試:name1和name2位於不同redis服務器,寫入成功。
{ Console.WriteLine("集群測試"); RedisHelper.Initialization(new CSRedis.CSRedisClient("192.168.137.202:6379,password=123456,defaultDatabase=0")); RedisHelper.Set("name1", "ypf1"); //對應的槽位在6384端口上 RedisHelper.Set("name2", "ypf2"); //對應的槽位在6379端口上 var data1 = RedisHelper.Get<String>("name1"); var data2 = RedisHelper.Get<String>("name2"); Console.WriteLine($"data1={data1}"); Console.WriteLine($"data2={data2}"); Console.WriteLine("執行完畢"); }
注意:這個寫法與【分區模式】同時使用時,切記不可設置“prefix=key前輟”(或者全部設置成一樣),否則會導致 keySlot 計算結果與服務端不匹配,無法記錄 slotCache。
(3).寫法2
把所有地址都列進去。
如下測試:name1和name2位於不同redis服務器,寫入成功
{ Console.WriteLine("集群測試"); var csredis = new CSRedis.CSRedisClient(null, "192.168.137.202:6379,password=123456,defaultDatabase=0", "192.168.137.202:6380,password=123456,defaultDatabase=0", "192.168.137.202:6381,password=123456,defaultDatabase=0", "192.168.137.202:6382,password=123456,defaultDatabase=0", "192.168.137.202:6383,password=123456,defaultDatabase=0", "192.168.137.202:6384,password=123456,defaultDatabase=0"); RedisHelper.Initialization(csredis); RedisHelper.Set("name1", "ypf1"); //對應的槽位在6384端口上 RedisHelper.Set("name2", "ypf2"); //對應的槽位在6379端口上 var data1 = RedisHelper.Get<String>("name1"); var data2 = RedisHelper.Get<String>("name2"); Console.WriteLine($"data1={data1}"); Console.WriteLine($"data2={data2}"); Console.WriteLine("執行完畢"); }
特別注意:官方集群(redis-cluster)不支持多 keys 的命令、【管道】、Eval(腳本)等眾多殺手級功能。
五. Lua測試
lua相關介紹參考:https://www.cnblogs.com/yaopengfei/p/13941841.html
https://www.cnblogs.com/yaopengfei/p/13826478.html
1. 在客戶端測試
A. 把Test1.lua文件改為“始終復制”.
B. 方案1:將lua腳本通過 ScriptLoad 轉換成sha, 然后在通過 EvalSHA 調用.
C. 方案2:直接通過Eval調用lua腳本
PS:方案1中sha會存放到redis的緩存中,客戶端多次調用相比每次都發送lua腳本給redis來說節省帶寬。
代碼分享:
Lua腳本:

--[[ 一. 方法聲明 ]]-- --1. 方法冪等(防止網絡延遲多次下單) local function recordOrderSn() --(1).獲取相關參數 local requestId = ARGV[1]; --請求ID --(2).執行判斷業務 local requestIdNum = redis.call('INCR',requestId); --表示第一次請求 if (requestIdNum==1) then redis.call('expire',requestId,600) --10min過期 return 1; --成功 end; --第二次及第二次以后的請求 if (requestIdNum>1) then return 0; --失敗 end; end; --對應的是整個代碼塊的結束 --[[ 二. 方法調用 返回值1代表成功,返回:0代表失敗 ]]-- --1. 方法冪等 local status3 = recordOrderSn(); if status3 == 0 then return 0; --失敗 end return 1; --成功
調用:
{ Console.WriteLine("Lua客戶端測試"); RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=0")); FileStream fileStream1 = new FileStream(@"Luas/Test1.lua", FileMode.Open); string script1 = ""; string luaSha1 = ""; using (StreamReader reader = new StreamReader(fileStream1)) { script1 = reader.ReadToEnd(); //將lua腳本轉換成sha,同時redis會將其存到腳本緩存中 luaSha1 = RedisHelper.ScriptLoad(script1); } //方案1:調用lua對應sha值(這里testKey1沒有特別作用)----推薦 var d1 = RedisHelper.EvalSHA(luaSha1, "testkey1", "12345-1"); Console.WriteLine($"d1={d1}"); //方案2:直接調用lua腳本(每次都發腳本給redis,浪費帶寬,推薦上面的sha模式) var d2 = RedisHelper.Eval(script1, "testkey2", "12345-2"); Console.WriteLine($"d2={d2}"); //清空緩存中的所有腳本 //RedisHelper.ScriptFlush(); //殺死正在運行的腳本 //RedisHelper.ScriptKill(); Console.WriteLine("執行完成"); }
2. 在CoreMvc中測試
直接演示sha的那種模式,調用方式完全類似,但在web程序中,我們可以在項目啟動的時候就把所有的腳本都事先轉換成lua存放到服務器緩存中,后續直接調用即可.
后台任務代碼:
/// <summary> /// 后台任務,初始化lua文件到服務器緩存中 /// </summary> public class LuasLoadService : BackgroundService { private IMemoryCache _cache; public LuasLoadService(IMemoryCache cache) { _cache = cache; } protected override Task ExecuteAsync(CancellationToken stoppingToken) { FileStream fileStream1 = new FileStream(@"Luas/Test1.lua", FileMode.Open); using (StreamReader reader = new StreamReader(fileStream1)) { string line = reader.ReadToEnd(); string luaSha = RedisHelper.ScriptLoad(line); //保存到緩存中 _cache.Set<string>("Test1Lua", luaSha); } return Task.CompletedTask; } }
注入和調用:
{ services.AddHostedService<LuasLoadService>(); }
六. 其他
1.多個db的用法
(1).多個CSRedisClient實例
//1.多個CSRedisClient實例 var connectionString = "119.45.174.xx:6379,password=123456"; var redis = new CSRedisClient[14]; //生產中設置成Singleton for (var a = 0; a < redis.Length; a++) { redis[a] = new CSRedisClient(connectionString + ",defaultDatabase=" + a); }; redis[1].Set("cs1", "111"); redis[2].Set("cs1", "111");
(2).多個RedisHelper子類
public class MyHelper1 : RedisHelper<MyHelper1> { } public class MyHelper2 : RedisHelper<MyHelper2> { }
調用:
MyHelper1.Initialization(new CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=1")); MyHelper2.Initialization(new CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=2")); MyHelper1.Set("cs2", "222"); MyHelper2.Set("cs2", "222");
2. 發布訂閱
簡單了解即可(可參考:https://www.cnblogs.com/kellynic/p/9952386.html), 專業場景推薦使用RabbitMQ
(1). 普通的發布訂閱
利用Subscribe和Publish方法
{ Console.WriteLine("發布訂閱"); RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=0")); //程序1:使用代碼實現訂閱端 var sub = RedisHelper.Subscribe(("chan1", msg => Console.WriteLine(msg.Body))); //sub.Disponse(); //停止訂閱 //程序2:使用代碼實現發布端 RedisHelper.Publish("chan1", "111"); //下面了解即可 //{ //sub1, sub2 爭搶訂閱(只可一端收到消息) // var sub1 = RedisHelper.SubscribeList("list1", msg => Console.WriteLine($"sub1 -> list1 : {msg}")); // var sub2 = RedisHelper.SubscribeList("list1", msg => Console.WriteLine($"sub2 -> list1 : {msg}")); // //sub3, sub4, sub5 非爭搶訂閱(多端都可收到消息) // var sub3 = RedisHelper.SubscribeListBroadcast("list2", "sub3", msg => Console.WriteLine($"sub3 -> list2 : {msg}")); // var sub4 = RedisHelper.SubscribeListBroadcast("list2", "sub4", msg => Console.WriteLine($"sub4 -> list2 : {msg}")); // var sub5 = RedisHelper.SubscribeListBroadcast("list2", "sub5", msg => Console.WriteLine($"sub5 -> list2 : {msg}")); //} }
(2). 利用BLPOP+LPUSH實現
(詳細可參考上面文檔)
3. 緩存相關業務簡化用法
詳見代碼
{ Console.WriteLine("緩存相關業務簡化封裝"); RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=0")); List<string> strList = new List<string>() { "ypf1", "ypf2", "ypf3" }; //模擬db //常規寫法:先去緩存中找,有數據直接返回,沒有數據去查db,然后給緩存賦值,最后返回 //{ // var data = RedisHelper.Get<string>("userName"); // if (string.IsNullOrEmpty(data)) // { // //從db中查找 // data = strList.FirstOrDefault(); // RedisHelper.Set("userName", data, 100); //100s過期 // Console.WriteLine($"data={data}"); // } // else // { // //直接將緩存中的數據返回 // Console.WriteLine($"data={data}"); // } //} //封裝的寫法 { //下面兩行等價於上面的一坨代碼 var data = RedisHelper.CacheShell("userName", 100, () => strList.FirstOrDefault()); //100s過期 Console.WriteLine($"data={data}"); //另外還支持hash類型 //var t2 = RedisHelper.CacheShell("test1", "1", 100, () => strList.FirstOrDefault()); //var t3 = RedisHelper.CacheShell("test2", new[] { "1", "2" }, 10, notCacheFields => new[] { // ("1", strList.FirstOrDefault()), // ("2", strList.FirstOrDefault()) //}); } }
4. PipeLine管道
Redis 管道技術可以在服務端未響應時,客戶端可以繼續向服務端發送請求,並最終一次性讀取所有服務端的響應。
{ RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=0")); //一次性返回所有請求結果 var data = RedisHelper.StartPipe(p => p.Set("userName1", "ypf1").Get("userName1").Set("userName2", "ypf2").Get("userName2")); }
5. Benchmark性能測試
見官網測試記錄
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。