.NetCore使用Redis,StackExchange.Redis队列,发布与订阅,分布式锁的简单使用

环境:之前一直是使用serverStack.Redis的客服端,
今天来使用一下StackExchange.Redis(个人感觉更加的人性化一些,也是免费的,性能也不会差太多),
版本为StackExchange.Redis V2.1.58 ,Core3.1

简单的说明(专业的术语参考资料网络和官网):官网地址:https://www.redis.net.cn/

Redis(Remote Dictionary Server ),即远程字典服务是一个开源的 ,由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队列值入队出队,截图效果:

优化之前入队1000条数据,测试结果将近50秒,这实在太慢,不可忍受!

 优化后的效果:为5.55s的样子(不太理想,说明有很大的改善空间)

 

2:redis发布与订阅截图效果:(一个发布者,四个订阅者) 订阅者都会收到相同的信息

 3:redis秒杀,截图如下:单个进程秒杀ok

开多个进程时,会有超卖的现象:(那我加lock锁呢?结果也是会有超卖的现象,此时下面的分布式锁可以解决)

 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 Task<ApiResultObject> EnqueueMsgAsync(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<ApiResultObject> 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 }
View Code

出队入队的后端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 Task<ApiResultObject> EnqueueMsgAsync(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<ApiResultObject> 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 }
View Code

入队以及秒杀分布式锁的客服端的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 }
View Code

完整的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;
 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         /// <summary>
 74         /// 入队right
 75         /// </summary>
 76         /// <param name="queueName"></param>
 77         /// <param name="redisValue"></param>
 78         /// <returns></returns>
 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         /// <summary>
 87         /// 入队left
 88         /// </summary>
 89         /// <param name="queueName"></param>
 90         /// <param name="redisvalue"></param>
 91         /// <returns></returns>
 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         /// <summary>
100         /// 入队left异步
101         /// </summary>
102         /// <param name="queueName"></param>
103         /// <param name="redisvalue"></param>
104         /// <returns></returns>
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         /// <summary>
113         /// 获取队列的长度
114         /// </summary>
115         /// <param name="queueName"></param>
116         /// <returns></returns>
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 }
View Code
  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, Action<RedisChannel, RedisValue> handler = 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 }
View Code

 

posted @ 2020-08-24 20:41  天天向上518  阅读(3835)  评论(9编辑  收藏  举报