• .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
  • 相关阅读:
    tomcat配置和原理(转)
    maven install、maven clean、project clean
    spring注解的(List&Map)特殊注入功能
    React父子组件传值
    使用Ant Design的Upload上传删除预览照片,以及上传图片状态一直处于uploading的解决方法。
    使用react-redux
    URLSearchParams生成和解析URL或者参数字符串
    Ant Design的Table组件去除“取消排序”选项
    React Hooks的useState和useEffect
    webpack配置alias简化相对路径
  • 原文地址:https://www.cnblogs.com/Fengge518/p/13556182.html
Copyright © 2020-2023  润新知