.net core ocelot+consul+jwt 身份验证,服务治理与发现,网关配置(三)增加 redis

用户登录需要存储会话 比如说20分钟会话结束,需要重新登录, 比如说分布式集群电商在秒杀的时候 出现并发请求,如果不加分布式锁会出现超卖现象 ,都会用到redis

reids , 可以提升查询效率,原理是单线程IO多路复用

第三篇 将会使用redis ,实现 保存会话和 演示分布式锁的应用

首先理解   高并发 多线程    是同瞬间 进行多个读写操作 这里 出现会出现争抢 造成 读的数据不正确  ,写的数据也不正确, 造成数据不正确,就是说线程没有顺序 ,无序的争抢造成的

此时需要加锁,简单的 是Lock , 在项目部署的时候 分布式 的情况  部署有一个redis 的情况 可以有 阻塞锁(悲观锁) 非阻塞锁(乐观锁) ,如果一个redis宕机了 导致不能使用了 狠可怕所以要高可用集群的redis,多台redis 集群的情况下 采用 redlock解决 

这里多说一个句 事物 ,事物 具有 原子性 持久性 隔离性 一致性 ,在多个表 更新时候 ,一个表 更新 同时需要 另一个表 也更新对 ,也就是 多个表 数据 进行修改的时候 需要做到 数据一致性

要么都成功更新 要么都失败 不能有一个表 数据更新成功 一个表数据更新失败的情况 这个是一致性,持久性就是将数据存到数据库 ,原子性 就是 把具体的 业务 分到最小 最后 就是 一堆 增删改查操作,一个增 就是一个原子 操作,隔离性 就是 现在 有人修改 这个表 其他人就不能修改,这是把其他人先暂时 隔离了 ,这里 就是用到 锁 阻塞锁 ,其他人只能等待

下面演示 单体项目 高并发 同时出现 读写操作

实现 类库 为ServiceStack.Redis

nuget 下载

项目

 并行开启20个线程 就是同时搞了20个线程

 1 // See https://aka.ms/new-console-template for more information
 2 using HighMoreThread;
 3 {
 4     Parallel.For(0, 20, body =>
 5     {
 6         // Console.WriteLine("开始抢了");
 7         Task.Run(() =>
 8         {
 9             SingleXingMu.Show();
10         });
11         //  Console.WriteLine("结束了");
12         Console.ReadKey();
13     });
14 } 

有5个东西 public static int total = 5; 

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 namespace HighMoreThread
 7 {
 8     //单体项目
 9     public class SingleXingMu
10     {
11         //总数
12         public static int total = 5;
13         private readonly static object lockdata = new object();
14         public static void Show()
15         {
16             lock (lockdata)
17             {
18                 if (total > 0)
19                 {
20                     total = total - 1;
21                     Console.WriteLine($"抢到了当前线程为 {Thread.CurrentThread.ManagedThreadId.ToString()} ,total值为{total}");
22                 }
23                 else
24                 {
25                     Console.WriteLine($"没抢到,当前线程为 {Thread.CurrentThread.ManagedThreadId.ToString()} ,total值为{total}");
26                 }
27             }
28         }
29     }
30 }

先把 lock注释掉看不加锁的情况 发现值不对 此时单体项目 考虑加锁

 加上锁了 发现值对了

 原理就是 

 那么分布式是不是 可行呢?

 开启了三个都各自为正 并不可行 原因是 加的lock 是每个进程下加的 并没有共享,就是 每个项目都有自己的锁,不统一 如果统一就可以实现

此时就上redis了 下面演示 一个redis的情况

现在将total存到redis中 演示阻塞锁

 1 using ServiceStack.Redis;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 namespace HighMoreThread
 8 {
 9     /// <summary>
10     /// 阻塞锁
11     /// </summary>
12     public class BlockingLock
13     {
14         //  static int total = 5;
15         public static void Show(string key, TimeSpan timeSpan)
16         {
17             using var client = new RedisClient("192.168.0.100", 6379);
18             //阻塞锁 AcquireLock
19             using (var datalock = client.AcquireLock(key, timeSpan))
20             {
21                 int total = client.Get<int>("total");
22                 if (total > 0)
23                 {
24                     client.Set<int>("total", total - 1);
25                     Console.WriteLine($"抢到了当前线程为 {Thread.CurrentThread.ManagedThreadId.ToString()} ,total值为{total}");
26                 }
27                 else
28                 {
29                     Console.WriteLine($"没抢到,当前线程为 {Thread.CurrentThread.ManagedThreadId.ToString()} ,total值为{total}");
30                 }
31             }
32         }
33     }
34 }

 

 1 // See https://aka.ms/new-console-template for more information
 2 
 3 using ServiceStack.Redis;
 4 using HighMoreThread;
 5 {
 6     //using var client = new RedisClient("192.168.0.100", 6379);
 7     //client.Set<int>("total",5,TimeSpan.FromDays(1));
 8 
 9     Parallel.For(0, 20, body =>
10     {
11         // Console.WriteLine("开始抢了");
12         Task.Run(() =>
13         {
14             //单体项目
15             //SingleXingMu.Show();
16 
17             //分布式 一个redis 阻塞锁 lockdata 过期时间 TimeSpan.FromSeconds(100)
18             BlockingLock.Show("lockdata", TimeSpan.FromSeconds(100));
19 
20         });
21         //  Console.WriteLine("结束了");
22         Console.ReadKey();
23     });
24 } 

 

 

 看到数据正确

还有是 非阻塞 

 

 1 // See https://aka.ms/new-console-template for more information
 2 
 3 using ServiceStack.Redis;
 4 using HighMoreThread;
 5 {
 6     //using var client = new RedisClient("192.168.0.100", 6379);
 7     //client.Set<int>("total",5,TimeSpan.FromDays(1));
 8 
 9     Parallel.For(0, 20, body =>
10     {
11         // Console.WriteLine("开始抢了");
12         Task.Run(() =>
13         {
14             //单体项目
15             //SingleXingMu.Show();
16 
17             //分布式 一个redis 阻塞锁 lockdata 过期时间 TimeSpan.FromSeconds(100)
18             //BlockingLock.Show("lockdata", TimeSpan.FromSeconds(100));
19             //非阻塞锁
20             ImmediatelyLock.Show("lockdata", TimeSpan.FromSeconds(100));
21 
22 
23         });
24         //  Console.WriteLine("结束了");
25         Console.ReadKey();
26     });
27 } 

 

 

 1 using ServiceStack.Redis;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading;
 7 using System.Threading.Tasks;
 8 
 9 namespace HighMoreThread
10 {
11     public class ImmediatelyLock
12     {
13         static int total = 5;
14         public static void Show(string key, TimeSpan timeSpan)
15         {
16             using var client = new RedisClient("192.168.0.100", 6379);
17             {
18                 //非阻塞锁 
19                 bool isLocked = client.Add<string>("DataLock:" + key, key, timeSpan);
20                 if (isLocked)
21                 {
22                     try
23                     {
24                         int total = client.Get<int>("total");
25                         if (total > 0)
26                         {
27                             client.Set<int>("total", total - 1);
28                             total = total - 1;
29                             Console.WriteLine($"抢到了当前线程为 {Thread.CurrentThread.ManagedThreadId.ToString()} ,total值为{total}");
30                         }
31                         else
32                         {
33                             Console.WriteLine($"没抢到,当前线程为 {Thread.CurrentThread.ManagedThreadId.ToString()} ,total值为{total}");
34                         }
35 
36                     }
37                     catch (Exception)
38                     {
39 
40                         throw;
41                     }
42                     finally
43                     {
44                         client.Remove("DataLock:" + key);
45                     }
46                 }
47             }
48         }
49     }
50 }

 

多个集群 redis  锁

 1 #region LICENSE
 2 /*
 3  *   Copyright 2014 Angelo Simone Scotto <scotto.a@gmail.com>
 4  * 
 5  *   Licensed under the Apache License, Version 2.0 (the "License");
 6  *   you may not use this file except in compliance with the License.
 7  *   You may obtain a copy of the License at
 8  * 
 9  *       http://www.apache.org/licenses/LICENSE-2.0
10  * 
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  * 
17  * */
18 #endregion
19 
20 using StackExchange.Redis;
21 using System;
22 using System.Collections.Generic;
23 using System.Linq;
24 using System.Text;
25 using System.Threading.Tasks;
26 
27 namespace Redlock.CSharp
28 {
29     public class Lock
30     {
31 
32         public Lock(RedisKey resource, RedisValue val, TimeSpan validity)
33         {
34             this.resource = resource;
35             this.val = val;
36             this.validity_time = validity;
37         }
38 
39         private RedisKey resource;
40 
41         private RedisValue val;
42 
43         private TimeSpan validity_time;
44 
45         public RedisKey Resource { get { return resource; } }
46 
47         public RedisValue Value { get { return val; } }
48 
49         public TimeSpan Validity { get { return validity_time; } }
50     }
51 }

 

  1 #region LICENSE
  2 /*
  3  *   Copyright 2014 Angelo Simone Scotto <scotto.a@gmail.com>
  4  * 
  5  *   Licensed under the Apache License, Version 2.0 (the "License");
  6  *   you may not use this file except in compliance with the License.
  7  *   You may obtain a copy of the License at
  8  * 
  9  *       http://www.apache.org/licenses/LICENSE-2.0
 10  * 
 11  *   Unless required by applicable law or agreed to in writing, software
 12  *   distributed under the License is distributed on an "AS IS" BASIS,
 13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  *   See the License for the specific language governing permissions and
 15  *   limitations under the License.
 16  * 
 17  * */
 18 #endregion
 19 
 20 using StackExchange.Redis;
 21 using System;
 22 using System.Collections.Generic;
 23 using System.Linq;
 24 using System.Text;
 25 using System.Threading;
 26 using System.Threading.Tasks;
 27 
 28 namespace Redlock.CSharp
 29 {
 30     public class Redlock
 31     {
 32 
 33         // 连接多个节点的redis服务
 34         public Redlock(params IConnectionMultiplexer[] list)
 35         {
 36             foreach (var item in list)
 37                 this.redisMasterDictionary.Add(item.GetEndPoints().First().ToString(), item);
 38         }
 39         //默认重试次数
 40         const int DefaultRetryCount = 3;
 41         //默认每次重试等待时间200毫秒
 42         readonly TimeSpan DefaultRetryDelay = new TimeSpan(0, 0, 0, 0, 200);
 43         //时钟驱动因子
 44         const double ClockDriveFactor = 0.01;
 45         // 节点的大多数数量
 46         protected int Quorum { get { return (redisMasterDictionary.Count / 2) + 1; } }
 47 
 48         /// <summary>
 49         ///释放锁的脚本//   lua  提高性能//一次性执行 
 50         /// </summary>
 51         const String UnlockScript = @"
 52             if redis.call(""get"",KEYS[1]) == ARGV[1] then
 53                 return redis.call(""del"",KEYS[1])
 54             else
 55                 return 0
 56             end";
 57 
 58         //客户端抢锁的唯一标识,全局唯一
 59         protected static byte[] CreateUniqueLockId()
 60         {
 61             return Guid.NewGuid().ToByteArray();
 62         }
 63 
 64 
 65         protected Dictionary<String, IConnectionMultiplexer> redisMasterDictionary = new Dictionary<string, IConnectionMultiplexer>();
 66 
 67         //构造连接resis实例
 68         protected bool LockInstance(string redisServer, string resource, byte[] val, TimeSpan ttl)
 69         {
 70 
 71             bool succeeded;
 72             try
 73             {
 74                 // 去操作一个节点 
 75                 var redis = this.redisMasterDictionary[redisServer];
 76                 // 设置过期时间,交给redis,value=guid 
 77                 succeeded = redis.GetDatabase().StringSet(resource, val, ttl, When.NotExists);
 78             }
 79             catch (Exception)
 80             {
 81                 succeeded = false;
 82             }
 83             return succeeded;
 84         }
 85 
 86         //释放锁方法执行释放锁的lua脚本,在每一个服务器上都执行
 87         protected void UnlockInstance(string redisServer, string resource, byte[] val)
 88         {
 89             RedisKey[] key = { resource };
 90             RedisValue[] values = { val };
 91             var redis = redisMasterDictionary[redisServer];
 92             redis.GetDatabase().ScriptEvaluate(
 93                 UnlockScript,
 94                 key,
 95                 values
 96                 );
 97         }
 98         //抢锁,如果成功返回true,否则返回失败
 99         public bool Lock(RedisKey resource, TimeSpan ttl, out Lock lockObject)
100         {
101             //生成抢锁前的唯一字符串
102             var val = CreateUniqueLockId();
103             Lock innerLock = null;
104             //补充重试多次抢锁
105             bool successfull = retry(DefaultRetryCount, DefaultRetryDelay, () =>
106             {
107                 try
108                 {
109                     int n = 0;
110                     var startTime = DateTime.Now;
111 
112                     // 抢锁 调用了我们开始配置的多个节点reids,一个一个操作
113                     // 给三个节点同时发送抢锁的key
114                     for_each_redis_registered(
115                         redis =>
116                         {
117                             // 累加当前我这个客户端,有几个节点拿到锁。。。
118                             if (LockInstance(redis, resource, val, ttl)) n += 1;
119                         }
120                     );
121 
122                     /*
123                      * Add 2 milliseconds to the drift to account for Redis expires
124                      * precision, which is 1 millisecond, plus 1 millisecond min drift 
125                      * for small TTLs.        
126                      */
127                     var drift = Convert.ToInt32((ttl.TotalMilliseconds * ClockDriveFactor) + 2);
128                     // 计算抢到锁花费的时间和设置的过过期时间
129 
130                     // 业务,是设置占有5s,,你在抢锁的过程中,超过了5s 
131                     var validity_time = ttl - (DateTime.Now - startTime) - new TimeSpan(0, 0, 0, 0, drift);
132                     //如果大部分节点抢到了锁,而且抢锁花费的时间小于设置的锁过期时间
133                     if (n >= Quorum && validity_time.TotalMilliseconds > 0)
134                     {
135                         //生成锁,返回true
136                         innerLock = new Lock(resource, val, validity_time);
137                         return true;
138                     }
139                     else
140                     {
141                         // 抢锁失败(只有少部分节点给你写成功,要么抢锁的时间超过了设置的锁过期时间)
142                         for_each_redis_registered(
143                             redis =>
144                             {
145                                 // 从所以节点执行移除锁的lua脚本
146                                 UnlockInstance(redis, resource, val);
147                             }
148                         );
149                         return false;
150                     }
151                 }
152                 catch (Exception)
153                 { return false; }
154             });
155 
156             lockObject = innerLock;
157             return successfull;
158         }
159 
160         protected void for_each_redis_registered(Action<IConnectionMultiplexer> action)
161         {
162             foreach (var item in redisMasterDictionary)
163             {
164                 action(item.Value);
165             }
166         }
167 
168         protected void for_each_redis_registered(Action<String> action)
169         {
170             foreach (var item in redisMasterDictionary)
171             {
172                 action(item.Key);
173             }
174         }
175 
176         //补偿重试的源代码
177         protected bool retry(int retryCount, TimeSpan retryDelay, Func<bool> action)
178         {
179             int maxRetryDelay = (int)retryDelay.TotalMilliseconds;
180             Random rnd = new Random();
181             int currentRetry = 0;
182             while (currentRetry++ < retryCount)
183             {
184                 if (action()) return true;
185                 // 微循环, 隔一段时间执行一次,隔一段时间执行一次
186                 Thread.Sleep(rnd.Next(maxRetryDelay));
187             }
188             return false;
189         }
190 
191         public void Unlock(Lock lockObject)
192         {
193             for_each_redis_registered(redis =>
194             {
195                 UnlockInstance(redis, lockObject.Resource, lockObject.Value);
196             });
197         }
198 
199         public override string ToString()
200         {
201             StringBuilder sb = new StringBuilder();
202             sb.AppendLine(this.GetType().FullName);
203 
204             sb.AppendLine("Registered Connections:");
205             foreach (var item in redisMasterDictionary)
206             {
207                 sb.AppendLine(item.Value.GetEndPoints().First().ToString());
208             }
209 
210             return sb.ToString();
211         }
212     }
213 }

 

 1 using Redlock.CSharp;
 2 using RedLockNet.SERedis;
 3 using ServiceStack.Redis;
 4 using StackExchange.Redis;
 5 using System;
 6 using System.Collections.Generic;
 7 using System.Linq;
 8 using System.Text;
 9 using System.Threading.Tasks;
10 
11 namespace HighMoreThread
12 {
13     public class MoreRadisLock
14     {
15         //docker run -d  -p 6379:6379 --name myredis redis 
16         //docker run -d  -p 6380:6379  --name myredis1 redis 
17         //docker run -d  -p 6381:6379  --name myredis2  redis 
18         //docker run -d  -p 6382:6379  --name myredis3 redis 
19         public static void Show( string key, TimeSpan timeout)
20         {
21             var dlm = new Redlock.CSharp.Redlock(
22                     ConnectionMultiplexer.Connect("192.168.1.211:6380"),
23                     ConnectionMultiplexer.Connect("192.168.1.211:6381"),
24                     ConnectionMultiplexer.Connect("192.168.1.211:6382")
25                       );
26             Lock lockObject;
27             // true ,拿到锁,false 拿不到  、、阻塞锁(内部还是补偿重试)
28             var isLocked = dlm.Lock(key, timeout, out lockObject);
29             if (isLocked)
30             {
31                 try
32                 {
33                     using (var client = new RedisClient("127.0.0.1", 6379))
34                     {
35                         //库存数量
36                         var inventory = client.Get<int>("inventoryNum");
37                         if (inventory > 0)
38                         {
39                             client.Set<int>("inventoryNum", inventory - 1);
40                             //订单数量
41                             var orderNum = client.Incr("orderNum");
42                             Console.WriteLine($"抢购成功*****线程id:{Thread.CurrentThread.ManagedThreadId.ToString("00")},库存:{inventory},订单数量:{orderNum}");
43                         }
44                         else
45                         {
46                             Console.WriteLine($"抢购失败:原因,没有库存");
47                         }
48                     }
49                 }
50                 catch
51                 {
52                     throw;
53                 }
54                 finally
55                 {
56                     // 删锁 
57                     dlm.Unlock(lockObject);
58                 }
59             }
60             else
61             {
62                 Console.WriteLine($"抢购失败:原因:没有拿到锁");
63             }
64         }
65     }
66 }

调用

 1 // See https://aka.ms/new-console-template for more information
 2 
 3 using ServiceStack.Redis;
 4 using HighMoreThread;
 5 {
 6     //using var client = new RedisClient("192.168.0.100", 6379);
 7     //client.Set<int>("total",5,TimeSpan.FromDays(1));
 8 
 9     Parallel.For(0, 20, body =>
10     {
11         // Console.WriteLine("开始抢了");
12         Task.Run(() =>
13         {
14         //单体项目
15         //SingleXingMu.Show();
16 
17         //分布式 一个redis 阻塞锁 lockdata 过期时间 TimeSpan.FromSeconds(100)
18         //BlockingLock.Show("lockdata", TimeSpan.FromSeconds(100));
19         //非阻塞锁
20         //ImmediatelyLock.Show("lockdata", TimeSpan.FromSeconds(100));
21         //集群redis 情况
22         MoreRadisLock.Show("akey", TimeSpan.FromSeconds(100));
23 
24         });
25         //  Console.WriteLine("结束了");
26         Console.ReadKey();
27     });
28 } 

 

posted on 2023-05-17 22:17  是水饺不是水饺  阅读(90)  评论(0)    收藏  举报

导航