基于redis的分布式锁

概述

在之前, 我也使用redis做过分布式锁, 当时的做法是这样的:

  1. setnx: 向 redis中创建一个过期时间为1s的key, 若创建失败, 则锁获取失败
  2. expire: 获取锁成功后, 给锁增加过期时间
  3. del: 处理后释放锁

当时觉得貌似没什么问题. 是我太天真了, 今天突然想到, 恩, 有问题.

问题

1.如果在第一步之后, 程序崩了, 没有给锁设置过期时间, 导致所有后续操作都无法正常获取到锁. 怎么破?

2.在A成功上锁后, 因为IO阻塞等原因, 执行时间有点长, 锁已经过期了, 这时B过来成功上锁, A在释放锁的时候释放的就是B的锁.

3.redis突然挂了. 如果redis突然挂了, 怎么办? 当然, 可以增加redis节点, 主节点挂了, 从节点立刻补上. 但是, 主节点的数据同步到从节点也是需要时间的吧. 假设一个场景:

  1. A在主节点设置锁
  2. 主节点还没有同步数据的时候, 挂了
  3. 从节点接替成为主节点
  4. B在主节点也成功设置了锁

这个时候, 分布式锁就失效了.

解决

那么有没有办法解决上面的问题呢? 我到万能的谷歌上找了一下, 恩, 真的有.

上面的问题一个一个解决.

问题一

如何避免没有给锁设置过期时间的问题?

其实看看就知道了, 问题出在设置key和设置value分成两条命令执行, 所以导致如果在 setnx命令执行过后, 程序崩溃, expire命令没有正常执行, 将其合并为一条命令就好啦.

set key value NX PX 5000

其中NX表示存在则不设置, PX表示过期时间.

如此, 至少可以保证不会出现没有过期时间的锁了

问题二

如何避免A释放了B的锁.

如何避免释放了其他人的锁呢? 换个问题, 如何保证这个锁是你加的呢? so easy, 加锁的时候, 讲value值设置成一个只有我知道的随机数字, 释放的时候看看值是不是我的就行了.

如此在释放的时候需要两步操作:

  1. 获取redis锁的值
  2. 若值是我的, 释放锁

当然, 为了保证释放锁操作的原子性, 这两步操作最好也能合并为一步操作. 那redis如何实现值是否相同的判断呢? Lua脚本.

简单介绍一下

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 argv1 argv2
# 看懂了吧, 哈哈
# eval 是redis内置的命令
# 第一个参数是运行的脚本逻辑
# 第二个参数表示后面有几个key
# 第五个参数开始就是附加参数, 在脚本逻辑中使用的

所以, 脚本内容如下:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

如此, 至少可以保证不会出现A释放了B锁的情况了

问题三

如何保证在主节点挂掉的时候, 从节点接替后, 不会重复获得锁?

官网上提供了一个方法, 从多个redis实例同时获取锁. 因为我没看太明白, 之后看懂了在说吧. 过…

其实, 如果不是处理金钱这种不容出错的业务, 这种小概率事件个人觉得还是可以容忍的.


总结

最终, 在redis单机下实现的分布式锁操作如下:

# 获取分布式锁,过期时间可调
set lock_key random_value NX PX 5000
# ...do something
# 释放分布式锁
eval "if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end" 1 lock_key random_value 
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请发表评论。x