先做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();
}
}
}
}