本文共 2221 字,大约阅读时间需要 7 分钟。
更多精彩内容,请关注微信公众号:后端技术小屋
用redis实现分布式锁是一个老生常谈的问题了。因为redis单条命令执行的原子性和高性能,当多个客户端执行setnx(相同key)时,最多只有一个获得成功。因此在对可用性要求不是特别高的场景下,redis分布式锁方案不失为一个性价比高的实现。
setnx lockid random px lock-duration
其中lockid与被锁住资源唯一对应。random为随机值,用于客户端判定自身是否为该锁的owner。lock-duration为锁保持时长,由业务操作耗时决定。如果不设置过期时间,考虑如下时序:
如上所示,客户端A抢到锁了,但是由于某些异常导致进程还没有来得及释放锁就退出了。这样其他客户端setnx的返回永远是0,即永远也抢不到锁。
相反,如果设置过期时间,即使客户端A没有主动释放锁,到了过期时间之后redis也会自动释放。
如果实现为,
setnx lockidexpire lockid lock-duration
除非使用lua script, 否则redis无法支持上述两个命令的原子性,当第一个命令执行完成后,抢到锁的客户端A异常退出了,那么其他客户端将永远抢到锁。
注:redis在2.6.12版本后已经支持setnx命令的TTL参数,这个问题不复存在
假设锁的值为固定值,考虑如下情况
del lockid
如果锁的值是随机值,并且每次成功加锁时,都记录该随机值的话,并且释放锁时,判断锁的值是否等于记录值,等于则del, 不等于则跳过。
如果不使用lua封装释放锁的逻辑,考虑时序:
get lockid
,发现记录值和锁当前值相等,判定该锁为自己所加。del lockid
,客户端B加的锁被A释放而redis执行lua script的原子性能避免上述问题。
如果只在一个redis节点上抢锁,如果该节点宕机,将导致所有的客户端都抢不到锁,无法保证服务的高可用。
redlock是一种基于redis的分布式锁算法。而redsync是redlock算法的golang实现,其暴露了三个API:加锁(Lock),解锁(Unlock),续锁(Extend)
set lockid value NX PX lock-duration
针对所有redis实例,执行lua脚本。这里会判断key对应的value和Mutex在Lock时使用的value值是否一致,只有一致了执行del命令。此举是为了保证每个客户端不会释放别的客户端创建的锁。
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end
如果有超过半数实例上的请求返回,则意味着释放锁成功。否则判定失败。
Extend操作是为了保证当客户端业务处理时长超过expire时间时,客户端可主动延长锁的过期时间,而无需二次抢锁。针对所有redis连接,执行lua脚本,重新设置过期时间
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("pexpire", KEYS[1], ARGV[2]) else return 0 end
半数以上返回成功,则意味着Extend成功
推荐阅读
更多精彩内容,请扫码关注微信公众号:后端技术小屋。如果觉得文章对你有帮助的话,请多多分享、转发、在看。
转载地址:http://dimfz.baihongyu.com/