一、redis分布式锁的简易实现用redis实现分布式锁是一个老生常谈的问题了。因为redis单条命令执行的原子性和高性能,当多个客户端执行setnx(相同key)时,最多只有一个获得成功。因此在对可用性要求不是特别高的场景下,redis分布式锁方案不失为一个性价比高的实现。
二、redis分布式锁的注意事项1. 锁必须要设定一个过期时间如果不设置过期时间,考虑如下时序:
如上所示,客户端A抢到锁了,但是由于某些异常导致进程还没有来得及释放锁就退出了。这样其他客户端setnx的返回永远是0,即永远也抢不到锁。 相反,如果设置过期时间,即使客户端A没有主动释放锁,到了过期时间之后redis也会自动释放。
2. 获取锁的命令不能分为两步执行如果实现为, setnx lockid expire lockid lock-duration 除非使用lua script, 否则redis无法支持上述两个命令的原子性,当第一个命令执行完成后,抢到锁的客户端A异常退出了,那么其他客户端将永远抢到锁。 注:redis在2.6.12版本后已经支持setnx命令的TTL参数,这个问题不复存在 3. 锁的值必须设置为随机值假设锁的值为固定值,考虑如下情况
如果锁的值是随机值,并且每次成功加锁时,都记录该随机值的话,并且释放锁时,判断锁的值是否等于记录值,等于则del, 不等于则跳过。 4. 释放锁时,需使用lua script封装保证原子性如果不使用lua封装释放锁的逻辑,考虑时序:
而redis执行lua script的原子性能避免上述问题。 5. 多个redis节点保证高可用如果只在一个redis节点上抢锁,如果该节点宕机,将导致所有的客户端都抢不到锁,无法保证服务的高可用。 三、redsync实现一览redlock是一种基于redis的分布式锁算法。而redsync是redlock算法的golang实现,其暴露了三个API:加锁(Lock),解锁(Unlock),续锁(Extend) 1. Lock
2. Unlock针对所有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 如果有超过半数实例上的请求返回,则意味着释放锁成功。否则判定失败。 3. ExtendExtend操作是为了保证当客户端业务处理时长超过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成功 |
|