Redis SETNX实现分布式锁

1、某进程1执行 SETNX lock 以尝试获取锁

2、由于某进程2已获得了锁,所以进程1执行 SETNX lock 返回0,即获取锁失败

3、进程1执行 GET lock 来检测锁是否已超时,如果没超时,则线程等待一段时间,再次检测

4、如果进程1检测到锁已超时,即当前的时间大于键 lock 的值,进程1会执行以下操作

GETSET lock <current Unix timestamp + lock timeout + 1>

5、由于 GETSET 操作在设置键的值的同时,还会返回键的旧值,通过比较键 lock 的旧值是否小于当前时间,可以判断进程是否已获得锁

6、假如另一个进程3也检测到锁已超时,并在进程1之前执行了 GETSET 操作,那么进程1的 GETSET 操作返回的是一个大于当前时间的时间戳,这样进程1就不会获得锁而继续等待。注意到,即使进程1接下来将键 lock 的值设置了比进程3设置的更大的值也没影响。

另外,值得注意的是,在进程释放锁,即执行 DEL lock 操作前,需要先判断锁是否已超时。如果锁已超时,那么锁可能已由其他进程获得,这时直接执行 DEL lock 操作会导致把其他进程已获得的锁释放掉。

C# Code

using System;
using System.Threading;
using System.Threading.Tasks;
using CSRedis;

namespace RedisLockDemo
{
    public class CsRedisLock
    {
        private static readonly int _lock_timeout = 40;
        private static readonly string _lock_key = "lock";
        public static void Test()
        {
            var rds = new CSRedisClient("127.0.0.1:6379,password=123456,defaultDatabase=13,poolsize=50,ssl=false");
            RedisHelper.Initialization(rds);

            Parallel.For(0, 13, x =>
            {
                if (GetLock(_lock_key))
                {
                    Console.WriteLine($"person:{x},线程ID:{Thread.CurrentThread.ManagedThreadId},获得锁 woking");

                    if (DateTimeOffset.Now.ToUnixTimeMilliseconds() < RedisHelper.Get<long>(_lock_key))
                    {
                        //释放锁
                        RedisHelper.Del(_lock_key);
                    }
                }
                else
                {
                    Console.WriteLine($"person:{x},线程ID:{Thread.CurrentThread.ManagedThreadId},获取锁异常");
                }
            });
            Console.WriteLine();
        }

        private static bool GetLock(string key)
        {
            bool getLocked = false;
            try
            {
                while (!getLocked)
                {
                    var now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
                    var lock_time = now + _lock_timeout + 1;
                    getLocked = RedisHelper.SetNx(key, lock_time);
                    //判断是否获取锁,
                    if (getLocked || now > RedisHelper.Get<long>(key) && now > RedisHelper.GetSet<long>(key, lock_time))
                    {
                        getLocked = true;
                    }
                    else
                    {
                        Thread.Sleep(30);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return getLocked;
        }
    }
}

相关文档:https://redis.io/commands/setnx

posted @ 2019-08-22 23:29  Rohmeng  阅读(2001)  评论(0编辑  收藏  举报