分布式锁 - SeeMoonUp/EfftiveRedis GitHub Wiki

分布式锁的理解

所谓分布式锁 说到底就是多个进程或者线程去获取同一个资源 但是只有一个会获取成功 实现了这样的功能就是分布式锁

分布式锁的redis实现

setnx命令

Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。

实际操作如下:

setnx.png

如果此时再次进行setnx的操作

20180825153518067083122.png

那么此时是不是有点思路了

比如多个线程或者进程 去争夺某一个资源的时候 先去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 指令可以一起执行 如下图

redisSet2.8.png

这个命令的完整意思就是 如果“seemoonup“这个key不存在设置为”false“并且设置过期时间5秒

if (StringUtils.equals("OK", redis.set("seemoonup", "false", "NX", "EX", 5))) {
	//处理业务逻辑
	.....
	//处理完业务逻辑 将锁释放掉
    JedisUtil.del("seemoonup");
}

至此基于redis的分布式锁就已经实现了

遗留问题

上例中此时释放锁的可能性有两种:

  1. 超过5秒 自动过期释放锁
  2. 处理业务逻辑完成 del释放锁

比如线程1 获得锁 执行业务逻辑时间过长比如达到了8秒 那么5秒钟的时候已经将锁释放 此时线程2可以获得锁 执行到第3秒的时候 线程1执行了del的方法 释放了锁 此时线程3又可以获得锁。

为了避免这个问题,Redis 分布式锁不要用于较长时间的任务。如果真的偶尔出现了,数据出现的小波错乱可能需要人工介入解决。

参考资料:

Redis 深度历险:核心原理与应用实践

思考问题:

是否可以使用这种方式来处理 用户重复提交 比如下单接口 设置2秒的过期时间 即两秒内只允许一次请求进来?

有不足之处还望不吝赐教 欢迎关注

未经作者允许 请勿转载,谢谢 :)