redis作为分布式锁的设计

分布式锁的设计

  • 获得锁, if(get(key)不存在 ) set key, val;
  • 删除锁, del key;

获得锁

获得锁之前,要先判断是否已经有锁.

while(1)
  val = redis.get(key)
  if(val != nil )
      wait;
      continue;
  redis.set(key, val)
  break;

但是get和set之间可能遇到别的client set了key,导致多个client 同时获取锁.
如何获取锁的两个过程, get和set的原子性.
使用命令:set key value(uuid) NX PX timeout

NX : key不存在就添加. Key存在就返回nil. Only set the key if it already exist.
XX : key存在才添加, Only set the key if it already exist.
EX / PX : 过期时间的单位, EX表示以秒为单位, PX表示以毫秒为单位.

为什么设置过期时间:
避免客户端崩溃和其他异常导致没有释放锁,锁一直被占用.

为什么value定义为uuid的唯一值
避免异常情况下被误删除导致同时两个client持有共享锁.

value的设计

value的设计最好使用uuid,或者时间戳等. 避免锁被误删除.
异常情况的描述
- A客户端拿到对象锁,但是因为一些原因被阻塞导致无法及时的释放锁
- 因为过期时间已经到了,Redis中的锁对象被删除
- B客户端请求锁操作成功
- A客户端这个时候阻塞操作完成,删除key释放锁
- C客户端请求获取所成功
- 这是B、C都拿到锁了,分布式锁失效

为避免这种情况发生,要保证value的设计是唯一的,
只有拿到锁且value符合预期的客户端才能进行删除。

释放锁

release锁有两个步骤:
1. get , 验证value是否满足期望
2. del 锁

这个步骤如何保证原子性?使用redis的WATCH 命令使用乐观锁的方式.
WATCH 命令本身就是一个乐观锁,它可以在 EXEC 命令执行之前,监视一定数量的 key,并在 EXEC 执行时,检查这些 key 是否被修改过,如果是的话,服务器就拒绝执行事务。

redis.watch(lock)
val = redis.get(lock) 
if val == 期望 
  redis.multi
  redis.del(key)
  redis.exec

悲剧,上边watch的机制仍然不能work. 因为.watch是和multi配合使用. watch实际上是开启了事务之后才生效的. 如何实现?
lua脚本, Redis会把Lua脚本作为一个整体执行,由于Redis是单线程,因此在脚本执行期间,其他脚本或命令是无法插入执行,这个特性符合事务的原子性。

命令原型:

EVAL script numkeys key[key ...] arg [arg...]

可以参考以下的lua脚本:

local key = KEYS[1]  
local val = redis.call("GET", key) 
if val == ARGV[1] 
  then 
    local ret = redis.call("del", key) 
    return ret
end

直接贴以下命令道redis-cli中执行, 可以加深理解 :

set lock 1234
eval 'local key = KEYS[1]  local val = redis.call("GET", key) \
if val == ARGV[1] then local ret = redis.call("del", key) \
return ret end' 1 "lock" "1234"
get lock

其他补充

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值