环境:之前一直是使用serverStack.Redis的客服端,
今天来使用一下StackExchange.Redis(个人感觉更加的人性化一些,也是免费的,性能也不会差太多),
版本为StackExchange.Redis V2.1.58 ,Core3.1
简单的说明(专业的术语参考资料网络和官网):官网地址:https://www.redis.net.cn/
Redis是一个开源的 ,由C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis 是一个高性能的key-value数据库。Redis的出现,很大程度补偿了memcached这类key/value存储的不足,
提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。
优点:
1 Redis读写性能优异,从内存当中进行IO读写速度快,支持超过100K+每秒的读写频率。
2 Redis支持Strings,
Lists, Hashes, Sets,Ordered Sets等数据类型操作。
3 Redis支持数据持久化,支持AOF和RDB两种持久化方式
4 Redis支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
5 Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
6 Redis是单线程多CPU,不需要考虑加锁释放锁,也就没有死锁的问题,效率高。
1:redis队列值入队出队,截图效果:
优化之前将近50秒了,这实在太慢了
优化后的效果:为5.55s的样子
2:redis发布与订阅截图效果:(一个发布者,四个订阅者)
3:redis秒杀,截图如下:单个进程秒杀ok
开多个进程时,会有超卖的现象:
4:redis值分布式锁,截图:
但是这样会比较耗资源
分布式锁ok库存为零就不在请求直接抛异常即可
上面通过测试的截图,简单的介绍了,Redis的队列(入队和出队),Redis发布与订阅,Redis分布式锁的使用,现在直接上代码 :
出队入队的WebApi Core3.1
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 6 namespace WebApp.Controllers 7 { 8 using Microsoft.AspNetCore.Mvc; 9 using RedisPublishAndSubHelper; 10 [Route("api/[Controller]")] 11 [ApiController] 12 public class RedisTestController 13 { 14 [HttpGet("EnqueueMsg")] 15 public async TaskEnqueueMsgAsync(string rediskey, string redisValue) 16 { 17 ApiResultObject obj = new ApiResultObject(); 18 try 19 { 20 long enqueueLong = default; 21 for (int i = 0; i < 1000; i++) 22 { 23 enqueueLong = await MyRedisSubPublishHelper.EnqueueListLeftPushAsync(rediskey, redisValue + i); 24 } 25 obj.Code = ResultCode.Success; 26 obj.Data = "入队的数据长度:" + enqueueLong; 27 obj.Msg = "入队成功!"; 28 } 29 catch (Exception ex) 30 { 31 32 obj.Msg = $"入队异常,原因:{ex.Message}"; 33 } 34 return obj; 35 } 36 [HttpGet("DequeueMsg")] 37 public async Task DequeueMsgAsync(string rediskey) 38 { 39 ApiResultObject obj = new ApiResultObject(); 40 try 41 { 42 string dequeueMsg = await MyRedisSubPublishHelper.DequeueListPopRightAsync(rediskey); 43 obj.Code = ResultCode.Success; 44 obj.Data = $"出队的数据是:{dequeueMsg}"; 45 obj.Msg = "入队成功!"; 46 } 47 catch (Exception ex) 48 { 49 obj.Msg = $"入队异常,原因:{ex.Message}"; 50 } 51 return obj; 52 } 53 } 54 }
出队入队的后端code WebApi:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 6 namespace WebApp.Controllers 7 { 8 using Microsoft.AspNetCore.Mvc; 9 using RedisPublishAndSubHelper; 10 [Route("api/[Controller]")] 11 [ApiController] 12 public class RedisTestController 13 { 14 [HttpGet("EnqueueMsg")] 15 public async TaskEnqueueMsgAsync(string rediskey, string redisValue) 16 { 17 ApiResultObject obj = new ApiResultObject(); 18 try 19 { 20 long enqueueLong = default; 21 for (int i = 0; i < 1000; i++) 22 { 23 enqueueLong = await MyRedisSubPublishHelper.EnqueueListLeftPushAsync(rediskey, redisValue + i); 24 } 25 obj.Code = ResultCode.Success; 26 obj.Data = "入队的数据长度:" + enqueueLong; 27 obj.Msg = "入队成功!"; 28 } 29 catch (Exception ex) 30 { 31 32 obj.Msg = $"入队异常,原因:{ex.Message}"; 33 } 34 return obj; 35 } 36 [HttpGet("DequeueMsg")] 37 public async Task DequeueMsgAsync(string rediskey) 38 { 39 ApiResultObject obj = new ApiResultObject(); 40 try 41 { 42 string dequeueMsg = await MyRedisSubPublishHelper.DequeueListPopRightAsync(rediskey); 43 obj.Code = ResultCode.Success; 44 obj.Data = $"出队的数据是:{dequeueMsg}"; 45 obj.Msg = "入队成功!"; 46 } 47 catch (Exception ex) 48 { 49 obj.Msg = $"入队异常,原因:{ex.Message}"; 50 } 51 return obj; 52 } 53 } 54 }
入队以及秒杀分布式锁的客服端的Code:
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 namespace RedisPublishService 6 { 7 using RedisPublishAndSubHelper; 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 #region 入队的code 13 { 14 int Index = 100000; 15 while (Index > 0) 16 { 17 //string msg = Console.ReadLine(); 18 new MyRedisSubPublishHelper().PublishMessage("nihaofengge", $"你好风哥:Guid值是:{DateTime.Now}{Guid.NewGuid().ToString()}"); 19 Console.WriteLine("发布成功!"); 20 Index -= 1; 21 } 22 Console.ReadKey(); 23 } 24 #endregion 25 26 #region 秒杀的code 27 { 28 try 29 { 30 Console.WriteLine("秒杀开始。。。。。"); 31 for (int i = 0; i < 200; i++) 32 { 33 Task.Run(() => 34 { 35 MyRedisSubPublishHelper.LockByRedis("mstest"); 36 string productCount = MyRedisHelper.StringGet("productcount"); 37 int pcount = int.Parse(productCount); 38 if (pcount > 0) 39 { 40 long dlong = MyRedisHelper.StringDec("productcount"); 41 Console.WriteLine($"秒杀成功,商品库存:{dlong}"); 42 pcount -= 1; 43 System.Threading.Thread.Sleep(30); 44 } 45 else 46 { 47 Console.WriteLine($"秒杀失败,商品库存为零了!"); 48 throw new Exception("产品秒杀数量为零!");//加载这里会比较保险 49 } 50 MyRedisSubPublishHelper.UnLockByRedis("mstest"); 51 }).Wait(); 52 } 53 } 54 catch (Exception ex) 55 { 56 Console.WriteLine($"产品已经秒杀完毕,原因:{ex.Message}"); 57 } 58 Console.ReadKey(); 59 } 60 #endregion 61 } 62 } 63 }
完整的code RedisHelper帮助类(测试并使用了部分方法的封装),
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 6 namespace RedisPublishAndSubHelper 7 { 8 using StackExchange.Redis; 9 using StackExchange; 10 using System.Threading; 11 12 public class MyRedisHelper 13 { 14 private static readonly string connectionRedisStr = string.Empty;// "47.107.87.32:6379,connectTimeout=1000,connectRetry=3,syncTimeout=10000"; 15 static MyRedisHelper() 16 { 17 //在这里来初始化一些配置信息 18 connectionRedisStr = "12.23.45.12:6379,connectTimeout=1000,connectRetry=3,syncTimeout=10000"; 19 } 20 21 #region Redis string简单的常见同步方法操作 22 public static bool StringSet(string key, string stringValue, double senconds = 60) 23 { 24 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 25 { 26 IDatabase db = conn.GetDatabase(); 27 return db.StringSet(key, stringValue, TimeSpan.FromSeconds(senconds)); 28 } 29 } 30 public static string StringGet(string key) 31 { 32 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 33 { 34 IDatabase db = conn.GetDatabase(); 35 return db.StringGet(key); 36 } 37 } 38 39 public static long StringInc(string key) 40 { 41 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 42 { 43 IDatabase db = conn.GetDatabase(); 44 return db.StringIncrement(key); 45 } 46 } 47 48 public static long StringDec(string key) 49 { 50 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 51 { 52 IDatabase db = conn.GetDatabase(); 53 return db.StringDecrement(key); 54 } 55 } 56 public static bool KeyExists(string key) 57 { 58 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 59 { 60 IDatabase db = conn.GetDatabase(); 61 return db.KeyExists(key); 62 } 63 } 64 #endregion 65 66 #region List Hash, Set,Zset 大同小异的使用,比较简单,后续有时间再补上 67 68 #endregion 69 70 #region 入队出队 71 72 #region 入队 73 ///74 /// 入队right 75 /// 76 /// 77 /// 78 /// 79 public static long EnqueueListRightPush(RedisKey queueName, RedisValue redisValue) 80 { 81 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 82 { 83 return conn.GetDatabase().ListRightPush(queueName, redisValue); 84 } 85 } 86 /// 87 /// 入队left 88 /// 89 /// 90 /// 91 /// 92 public static long EnqueueListLeftPush(RedisKey queueName, RedisValue redisvalue) 93 { 94 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 95 { 96 return conn.GetDatabase().ListLeftPush(queueName, redisvalue); 97 } 98 } 99 /// 100 /// 入队left异步 101 /// 102 /// 103 /// 104 /// 105 public static async Task<long> EnqueueListLeftPushAsync(RedisKey queueName, RedisValue redisvalue) 106 { 107 using (var conn = await ConnectionMultiplexer.ConnectAsync(connectionRedisStr)) 108 { 109 return await conn.GetDatabase().ListLeftPushAsync(queueName, redisvalue); 110 } 111 } 112 /// 113 /// 获取队列的长度 114 /// 115 /// 116 /// 117 public static long EnqueueListLength(RedisKey queueName) 118 { 119 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 120 { 121 return conn.GetDatabase().ListLength(queueName); 122 } 123 } 124 125 #endregion 126 127 #region 出队 128 public static string DequeueListPopLeft(RedisKey queueName) 129 { 130 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 131 { 132 IDatabase database = conn.GetDatabase(); 133 int count = database.ListRange(queueName).Length; 134 if (count <= 0) 135 { 136 throw new Exception($"队列{queueName}数据为零"); 137 } 138 string redisValue = database.ListLeftPop(queueName); 139 if (!string.IsNullOrEmpty(redisValue)) 140 return redisValue; 141 else 142 return string.Empty; 143 } 144 } 145 public static string DequeueListPopRight(RedisKey queueName) 146 { 147 using (var conn = ConnectionMultiplexer.Connect(connectionRedisStr)) 148 { 149 IDatabase database = conn.GetDatabase(); 150 int count = database.ListRange(queueName).Length; 151 if (count <= 0) 152 { 153 throw new Exception($"队列{queueName}数据为零"); 154 } 155 string redisValue = conn.GetDatabase().ListRightPop(queueName); 156 if (!string.IsNullOrEmpty(redisValue)) 157 return redisValue; 158 else 159 return string.Empty; 160 } 161 } 162 public static async Task<string> DequeueListPopRightAsync(RedisKey queueName) 163 { 164 using (var conn = await ConnectionMultiplexer.ConnectAsync(connectionRedisStr)) 165 { 166 IDatabase database = conn.GetDatabase(); 167 int count = (await database.ListRangeAsync(queueName)).Length; 168 if (count <= 0) 169 { 170 throw new Exception($"队列{queueName}数据为零"); 171 } 172 string redisValue = await conn.GetDatabase().ListRightPopAsync(queueName); 173 if (!string.IsNullOrEmpty(redisValue)) 174 return redisValue; 175 else 176 return string.Empty; 177 } 178 } 179 #endregion 180 181 #endregion 182 183 #region 分布式锁 184 public static void LockByRedis(string key, int expireTimeSeconds = 10) 185 { 186 try 187 { 188 IDatabase database1 = ConnectionMultiplexer.Connect(connectionRedisStr).GetDatabase(); 189 while (true) 190 { 191 expireTimeSeconds = expireTimeSeconds > 20 ? 10 : expireTimeSeconds; 192 bool lockflag = database1.LockTake(key, Thread.CurrentThread.ManagedThreadId, TimeSpan.FromSeconds(expireTimeSeconds)); 193 if (lockflag) 194 { 195 break; 196 } 197 } 198 } 199 catch (Exception ex) 200 { 201 throw new Exception($"Redis加锁异常:原因{ex.Message}"); 202 } 203 } 204 205 public static bool UnLockByRedis(string key) 206 { 207 ConnectionMultiplexer conn = ConnectionMultiplexer.Connect(connectionRedisStr); 208 try 209 { 210 IDatabase database1 = conn.GetDatabase(); 211 return database1.LockRelease(key, Thread.CurrentThread.ManagedThreadId); 212 } 213 catch (Exception ex) 214 { 215 throw new Exception($"Redis加锁异常:原因{ex.Message}"); 216 } 217 finally 218 { 219 if (conn != null) 220 { 221 conn.Close(); 222 conn.Dispose(); 223 } 224 } 225 } 226 #endregion 227 228 } 229 }
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace RedisPublishAndSubHelper 6 { 7 using StackExchange.Redis; 8 using System.Net.Http; 9 using System.Threading; 10 using System.Threading.Channels; 11 using System.Threading.Tasks; 12 13 public class MyRedisSubPublishHelper 14 { 15 private static readonly string redisConnectionStr = "12.32.12.54:6379,connectTimeout=10000,connectRetry=3,syncTimeout=10000"; 16 private static readonly ConnectionMultiplexer connectionMultiplexer = null; 17 static MyRedisSubPublishHelper() 18 { 19 connectionMultiplexer = ConnectionMultiplexer.Connect(redisConnectionStr); 20 } 21 22 23 #region 发布订阅 24 public void SubScriper(string topticName, Actionhandler = null) 25 { 26 ISubscriber subscriber = connectionMultiplexer.GetSubscriber(); 27 ChannelMessageQueue channelMessageQueue = subscriber.Subscribe(topticName); 28 channelMessageQueue.OnMessage(channelMessage => 29 { 30 if (handler != null) 31 { 32 string redisChannel = channelMessage.Channel; 33 string msg = channelMessage.Message; 34 handler.Invoke(redisChannel, msg); 35 } 36 else 37 { 38 string msg = channelMessage.Message; 39 Console.WriteLine($"订阅到消息: { msg},Channel={channelMessage.Channel}"); 40 } 41 }); 42 } 43 public void PublishMessage(string topticName, string message) 44 { 45 ISubscriber subscriber = connectionMultiplexer.GetSubscriber(); 46 long publishLong = subscriber.Publish(topticName, message); 47 Console.WriteLine($"发布消息成功:{publishLong}"); 48 } 49 #endregion 50 51 #region 入队出队 52 public static async Task<long> EnqueueListLeftPushAsync(RedisKey queueName, RedisValue redisvalue) 53 { 54 return await connectionMultiplexer.GetDatabase().ListLeftPushAsync(queueName, redisvalue); 55 } 56 57 public static async Task<string> DequeueListPopRightAsync(RedisKey queueName) 58 { 59 IDatabase database = connectionMultiplexer.GetDatabase(); 60 int count = (await database.ListRangeAsync(queueName)).Length; 61 if (count <= 0) 62 { 63 throw new Exception($"队列{queueName}数据为零"); 64 } 65 string redisValue = await database.ListRightPopAsync(queueName); 66 if (!string.IsNullOrEmpty(redisValue)) 67 return redisValue; 68 else 69 return string.Empty; 70 } 71 #endregion 72 73 #region 分布式锁 74 public static void LockByRedis(string key, int expireTimeSeconds = 10) 75 { 76 try 77 { 78 IDatabase database = connectionMultiplexer.GetDatabase(); 79 while (true) 80 { 81 expireTimeSeconds = expireTimeSeconds > 20 ? 10 : expireTimeSeconds; 82 bool lockflag = database.LockTake(key, Thread.CurrentThread.ManagedThreadId, TimeSpan.FromSeconds(expireTimeSeconds)); 83 if (lockflag) 84 { 85 break; 86 } 87 } 88 } 89 catch (Exception ex) 90 { 91 throw new Exception($"Redis加锁异常:原因{ex.Message}"); 92 } 93 } 94 95 public static bool UnLockByRedis(string key) 96 { 97 try 98 { 99 IDatabase database = connectionMultiplexer.GetDatabase(); 100 return database.LockRelease(key, Thread.CurrentThread.ManagedThreadId); 101 } 102 catch (Exception ex) 103 { 104 throw new Exception($"Redis加锁异常:原因{ex.Message}"); 105 } 106 } 107 #endregion 108 } 109 }