Redis深度历险阅读笔记(2)

分布式🔒

> setnx lockkey true  # 使用setnx加锁
...
> del lockkey         # 使用del解锁

如果在setnx和del之前出现异常,导致挂掉了,那么可能造成死锁。

  • 所以要加上过期时间:
> setnx lockkey true  # 使用setnx加锁
> expire lockkey 5    # 加上5s过期时间
...
> del lockkey         # 使用del解锁

如果在setnx和expire之间出现异常挂掉了,同样也会造成死锁

  • Redis 2.8之后,setnx和expire指令可以一起执行:
> set lockkey true ex 5 nx  # 原子实现setnx & expire
> del lockkey

注:一定要注意超时时间不能短于逻辑执行时间,否则锁可能会被第二个线程重新持有

  • 将value设置为一个随机数/或其他标志数,释放锁时校验value是否是自己想要的数(可以用来确保是同一个线程释放这个锁)。但是需要保证【校验+删除】这个操作是原子的,Redis没有提供这样的一起执行的指令,可以使用lua脚本。
# delifequals 
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1]) 
else
    return 0
end

但还有一个问题:一旦Redis节点挂掉,为了保障服务的可用性,我们给该节点挂上一个Slave,但由于主从复制是异步的,如果客户端A在Master加锁成功了,这时Master挂掉了,但是结果并没有同步到Slave,客户端B在Slave也获取到了锁,这样导致A和B都持有了这个锁。

Redis的作者提出了新的算法Redlock,但是这个方案仍然存在争议,这篇文章总结的不错,值得一看:基于Redis的分布式锁到底安全吗

可重入🔒

ReentrantLock。指一个锁支持同一个线程的多次加锁。

Redis的分布式锁如果要支持可重入锁,需要在包装客户端的set方法。

基础的代码我写在了:https://github.com/Carey6918/RedisGo/blob/master/reentrant_lock.go

结语

目前我们开发的项目中,用到的仅仅是setnx+lua脚本实现的分布式锁,并且保证了高可用。但是对于更安全的要求,并没有实现(甚至没有用到Redlock)。也许在某些特定的场景需要实现。

可重入锁的话,我这里是把一个goroutine当作用户态线程来对待的,anyway,感觉目前没啥用,就当自己造了个轮子玩儿。

Table of Contents