分布式锁 - SeeMoonUp/EfftiveRedis GitHub Wiki
分布式锁的理解
所谓分布式锁 说到底就是多个进程或者线程去获取同一个资源 但是只有一个会获取成功 实现了这样的功能就是分布式锁
分布式锁的redis实现
setnx命令
Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。
实际操作如下:
如果此时再次进行setnx的操作
那么此时是不是有点思路了
比如多个线程或者进程 去争夺某一个资源的时候 先去redis中设置一个key 比如上例中的“seemoonup”
这样只有返回1的这个表示成功其他的进程/线程 均为0表示抢夺资源失败
这样就相当于利用了setnx的唯一性 来进行了分布式锁的实现
当得到锁(也就是返回1)的那个线程处理完逻辑了之后将这个锁释放掉 使用方法del seemoonup 让其他的进程/线程获得锁
完整的实现如下:
if (redis.setnx("seemoonup", "true") ==1) {
//do some thing
redis.del("seemoonup")
}
会出现的问题
当或得到锁的那个线程 处理逻辑的过程中发生了宕机 那么redis中的seemooup永远不会释放锁 如下:
if (redis.setnx("seemoonup", "true") ==1) {
//do some thing
int i = 1/0; //这里会抛出异常导致下面的del没有被执行
redis.del("seemoonup")
}
如何解决这个问题呢?
设置过期时间
第一个想到的就是 对这个key进行一个过期时间的设置 比如
if (redis.setnx("seemoonup", "true") ==1) {
redis.expire("seemoonup", 5); //设置为5秒钟过期
//do some thing
int i = 1/0; //这里会抛出异常导致下面的del没有被执行
redis.del("seemoonup")
}
但是因为在setnx和expire还是有可能被中断掉,这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。
redis2.8 扩展参数
如果这两条指令可以一起执行就不会出现问题。所以在Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行 如下图
这个命令的完整意思就是 如果“seemoonup“这个key不存在设置为”false“并且设置过期时间5秒
if (StringUtils.equals("OK", redis.set("seemoonup", "false", "NX", "EX", 5))) {
//处理业务逻辑
.....
//处理完业务逻辑 将锁释放掉
JedisUtil.del("seemoonup");
}
至此基于redis的分布式锁就已经实现了
遗留问题
上例中此时释放锁的可能性有两种:
- 超过5秒 自动过期释放锁
- 处理业务逻辑完成 del释放锁
比如线程1 获得锁 执行业务逻辑时间过长比如达到了8秒 那么5秒钟的时候已经将锁释放 此时线程2可以获得锁 执行到第3秒的时候 线程1执行了del的方法 释放了锁 此时线程3又可以获得锁。
为了避免这个问题,Redis 分布式锁不要用于较长时间的任务。如果真的偶尔出现了,数据出现的小波错乱可能需要人工介入解决。
参考资料:
思考问题:
是否可以使用这种方式来处理 用户重复提交 比如下单接口 设置2秒的过期时间 即两秒内只允许一次请求进来?
有不足之处还望不吝赐教 欢迎关注
未经作者允许 请勿转载,谢谢 :)