Redis Distributed lock

using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace RedisDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            TestRedisLockWithFencingToken();
            Console.ReadKey();
        }

        public static List<int> SLEEP_OF_SECONDS = new List<int> { 20, 0, 0, 10, 10 };

        public static void TestRedisLockWithFencingToken()
        {
            var tasks = new List<Task>();
            var mockService = new MockService();

            Enumerable.Range(1, 3).ToList().ForEach(it =>
            {
                var task = Task.Run(() =>
                {

                    var taskId = $"task_{it}";
                    var lockHelper = new RedisLockHelper();
                    var lockKey = "article:1";
                    try
                    {
                        Console.WriteLine($"-> {taskId} begin acquire redis lock.");
                        var result = lockHelper.GetLock(lockKey, TimeSpan.FromSeconds(10));
                        if (result.Success)
                        {
                            var taskInfo = new TaskInfo { TaskId = taskId, FencingToken = result.FencingToken ?? 0 };
                            Console.WriteLine($"-> {taskId} acquire lock success {taskInfo}.");
                            var sleepOfSeconds = SLEEP_OF_SECONDS[it - 1];
                            Thread.Sleep(sleepOfSeconds * 1000);
                            mockService.Save(taskInfo);
                            Console.WriteLine($"{taskInfo} Done");
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"{taskId} {ex.Message}");
                    }
                    finally
                    {
                        lockHelper.ReleaseLock("article:1");
                    }
                });
                tasks.Add(task);
            });

            try
            {
                Task.WaitAll(tasks.ToArray());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
    }

    public class TaskInfo
    {
        public long FencingToken { get; set; }
        public string TaskId { get; set; }

        public override string ToString()
        {
            return $"TaskId: {TaskId} - FencingToken: {FencingToken}";
        }
    }

    public class MockService
    {
        private IConnectionMultiplexer connection;
        private String fencingTokenKey = "redis_fencingToken";

        public MockService()
        {
            this.Initialize();
        }

        private void Initialize()
        {
            connection = ConnectionMultiplexer.Connect("localhost");
        }

        public void Close()
        {
            if (connection != null)
            {
                connection.Close();
            }
        }



        public void Save(TaskInfo taskInfo)
        {
            Console.WriteLine($"MockService#Save: {taskInfo}");

            var fencingToken = taskInfo.FencingToken;

            var serverFencingToken = connection.GetDatabase().StringGet(fencingTokenKey);
            Console.WriteLine($"MockService#Save: {taskInfo} - Server Fencing Token {serverFencingToken}");

            if (serverFencingToken.HasValue)
            {
                if (fencingToken == (long)serverFencingToken)
                {
                    Console.WriteLine($"MockService#Save: {taskInfo} Save Success.");
                }
                else
                {
                    Console.WriteLine($"MockService#Save: {taskInfo} Save Failure, Fencingtoken not equal server token.");
                }
            }
            else
            {
                Console.WriteLine($"{taskInfo} Save error");

            }
        }
    }

    public class RedisLockResult
    {
        public bool Success { get; set; }
        public long? FencingToken { get; set; }
    }

    public class RedisLockHelper : IDisposable
    {
        private IConnectionMultiplexer connection;
        private String value;
        private String fencingTokenKey = "redis_fencingToken";

        public RedisLockHelper()
        {
            value = Guid.NewGuid().ToString();
            this.Initialize();
        }

        public RedisLockResult GetLock(String key, TimeSpan expiry)
        {
            var db = connection.GetDatabase();
            var lockSuccess = db.StringSet(key, value, expiry, When.NotExists, CommandFlags.None);

            var start = DateTime.Now;
            while (!lockSuccess)
            {
                Thread.Sleep(50);
                lockSuccess = db.StringSet(key, value, expiry, When.NotExists, CommandFlags.None);
                start = start.AddMilliseconds(50);
                if (DateTime.Now.Subtract(start).TotalSeconds > 20)
                {
                    throw new InvalidOperationException($"can not acquire distributed lock");
                }
            }

            var result = new RedisLockResult()
            {
                Success = lockSuccess
            };

            if (lockSuccess)
            {
                var fencingToken = db.StringIncrement(fencingTokenKey, 1, CommandFlags.None);
                result.FencingToken = fencingToken;
            }

            return result;
        }

        public void ReleaseLock(RedisKey key)
        {
            var lua = @"if redis.call('get', @key) == @value then
                            return redis.call('del', @key)
                        else
                            return 0
                        end";
            var db = connection.GetDatabase();
            var prepare = LuaScript.Prepare(lua);
            var result = db.ScriptEvaluate(prepare, new { key = key, value = value });
        }

        private void Initialize()
        {
            connection = ConnectionMultiplexer.Connect("localhost");
        }

        public void Dispose()
        {
            if (connection != null)
            {
                connection.Dispose();
            }
        }
    }
}
posted @ 2019-08-13 10:46  不要相信我  阅读(3316)  评论(0编辑  收藏  举报