Redis内置分布式共享锁AcquireLock原理
最近公司公司站点被攻击,所以需求对于重量级的操作根据用户加锁
对于单站点的网站来说,.Net自带的Lock已经够用,但是对于分布式的网站,Lock却没有办法实现应用于不同服务器
所谓条条大路通罗马,即便是分布式,加锁的方式也是挺多,可以选择数据库,也可以用分布式锁,我这边用到的就是Redis自带的AcquireLock
之前并没有怎么接触Redis,所以先对这个锁进行了测试,线上代码
public void Lock(Action a, string key, int TimeOut = 120) { IDisposable sr=null; try { sr = RedisManager.GetClient().AcquireLock(RedisManager.GetLockKey(key), TimeSpan.FromSeconds(TimeOut)); a(); sr.Dispose(); } catch(Exception ex) { Console.WriteLine(ex.Message); } finally { if(sr!=null) { sr.Dispose(); } } }
class Program { static int Index = 0; static void Main(string[] args) { //Console.WriteLine("holle Word!"); //Console.WriteLine(JsonConvert.SerializeObject(VerifyDataModel.GetVerifyDataModels())); //Console.WriteLine(JsonConvert.SerializeObject(VerifyDataModel.GetVerifyDataModels())); //RedisManager.Set("11", "nimeid"); //Console.WriteLine(RedisManager.Get("11")); //Console.WriteLine(RedisManager.Get("11")); //Console.WriteLine("22222"); Console.WriteLine("分布式锁!"); for(int i=0;i<100;i++) { new Task(p=> { IndexAdd((int)p); },i).Start(); } Console.Read(); } static void IndexAddPrecss(int i) { Thread.Sleep(1000); Index++; // Console.WriteLine(DateTime.Now); Console.WriteLine("我是第" + i + "个测试"); } static void IndexAdd(int i) { LockHelper.Lock(()=> { IndexAddPrecss(i); }, "test11211"); } }
代码很简单 ,刚开始调用也没有问题
但是......我把超时时间设置短一点之后却出现

What ?????
怎么会有些成功有些失败呢?
后面我加了时间测试一下 结果就有点明显了

C#在做Task应该又有默认线程池启用的,不晓得做了什么限制,导致只有四个线程同时启用,其他线程要等四个线程结束后才启用,于是用了四个线程测试:
(Lock超时时间设置1S,线程执行2S)
由这个结果可以得知,当四个线程同时争锁的时候,一个成功,其他等待,当等待时间超过超时时间时,Redis会自动抛出异常
-----------------------------------------引入正题--------------------------------
为此我查看了redis封装的源代码,我直接把注释写在源代码上
public RedisLock(RedisClient redisClient, string key, TimeSpan? timeOut) { Func<bool> action = null; this.redisClient = redisClient; this.key = key; if (action == null) { action = delegate { long num; Func<IRedisClient, bool> command = null; TimeSpan? nullable1 = timeOut; TimeSpan span = nullable1.HasValue ? nullable1.GetValueOrDefault() : new TimeSpan(0x16d, 0, 0, 0); DateTime dateTime = DateTime.UtcNow.Add(span); string lockString = (dateTime.ToUnixTimeMs() + 1L).ToString(); //验证key存不存在,不存在新增,直接返回true if (redisClient.SetValueIfNotExists(key, lockString)) { return true; } redisClient.Watch(new string[] { key }); //验证key取回的值能否long格式,不能则返回false,出现这种情况最有可能key跟其他业务的key重叠了 if (!long.TryParse(redisClient.Get<string>(key), out num)) { redisClient.UnWatch(); return false; } //验证key的值是否已经超时,未超时则返回false if (num > DateTime.UtcNow.ToUnixTimeMs()) { redisClient.UnWatch(); return false; } //key超时重新生成 using (IRedisTransaction transaction = redisClient.CreateTransaction()) { if (command == null) { command = r => r.Set<string>(key, lockString); } transaction.QueueCommand(command); return transaction.Commit(); } }; } //当返回falase 这边会while重复执行 ExecExtensions.RetryUntilTrue(action, timeOut); } public static void RetryUntilTrue(Func<bool> action, TimeSpan? timeOut) { int i = 0; DateTime utcNow = DateTime.UtcNow;
//当等待时间超过超时时间,直接抛出异常 while (!timeOut.HasValue || ((DateTime.UtcNow - utcNow) < timeOut.Value)) { i++; if (action()) { return; } SleepBackOffMultiplier(i); } throw new TimeoutException(string.Format("Exceeded timeout of {0}", timeOut.Value)); }
前期绕了很多弯路,后面慢慢理解,懂得原理才有利于更好的开发

浙公网安备 33010602011771号