.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 }