Redis的事务和分布式锁实现

先做Redis事务介绍

Redis提供了简单的事务命令:Watch、Multi、Exec,注意没有Rollback。
简单应用:

watch aaa
watch bbb
multi
set aaa value1
set bbb value2
set ccc value3
exec

上述Redis指令中,先对2个key进行监视:aaa和bbb,
然后通过multi启动事务,再加入3个指令,最后用exec执行3个指令。
任意线程如果对aaa或bbb的值进行了修改,都会导致事务失败,不执行。
比如在watch aaa之后,任意线程对aaa的值进行了修改,都会导致exec失败,例如下面的set bbb都会失败

// 当前线程在watch和multi之间修改了监视的值
// 注意下面第2个watch没作用
watch aaa
set aaa 123
watch aaa
multi
set bbb value2
exec
// a线程
watch aaa
multi
set bbb value2
exec

// b线程在a线程的watch和exec之间执行了:
set aaa 123

注意:exec会清除所有的watch,
但是执行了watch,如果没有执行exec或unwatch,将会影响当前连接的其它事务。

Redis事务版本差异

在Redis2.6.5以前的版本,Multi事务中出错的语法命令会忽略,成功的命令会继续执行,
从2.6.5版本开始,如果事务中的命令有错误,会导致整个事务不执行,例如下面的sett错误指令:

// 2.6.4及以前的版本
set aaa 1
multi
set aaa 2
sett bbb 3
exec
get aaa // 返回2
// 2.6.5及以后的版本
set aaa 1
multi
set aaa 2
sett bbb 3
exec
get aaa // 返回1

Redis分布式锁实现

简单版本,先尝试死循环加锁,成功后执行业务,再删除锁:

/// <summary>
/// 加锁执行指定的方法
/// </summary>
/// <param name="lockKey">加锁Key</param>
/// <param name="timeOut">加锁时长</param>
/// <param name="action">业务逻辑</param>
public static void LockSimple(string lockKey, TimeSpan timeOut, Action action)
{
    var guid = Guid.NewGuid().ToString("N");// 用于解锁比对
    var beginTime = DateTime.Now;
    using (var redis = RedisManage.GetClient())
    {
        // 尝试循环加锁直到超时或成功为止
        while (!redis.Add(lockKey, guid, timeOut))
        {
            if ((DateTime.Now - beginTime).TotalMilliseconds > timeOut.TotalMilliseconds)
                throw new TimeoutException("获取Redis锁超时");
            Thread.Sleep(10);
        }
        try
        {
            action?.Invoke(); // 业务逻辑
        }
        finally
        {
            redis.Watch(lockKey);
            var currentVal = redis.Get<string>(lockKey);
            using (var transaction = redis.CreateTransaction())
            {
                // 避免误删其它任务的锁
                transaction.QueueCommand(r => currentVal == guid && r.Remove(lockKey));
                transaction.Commit();
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/youbl/article/details/80273019