Redisson实现Redis分布式锁以及Redlock分布式锁
首先看一段模拟扣减库存的代码:import lombok.extern.slf4j.Slf4j;import org.redisson.Redisson;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplat
首先看一段模拟扣减库存的代码:
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("test")
@Slf4j
public class TestController {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
//模拟扣减库存业务
@RequestMapping("deduct_stock")
public String deductStock(){
//拿出当前商品的库存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock>0){
int realStock = stock-1;
//减完当前商品的库存之后 将库存值写回去
stringRedisTemplate.opsForValue().set("stock",realStock+"");
} else{
throw new RuntimeException("库存不足,扣减失败");
}
return "ens";
}
}
以上代码符合秒杀的时候扣减Redis中库存的逻辑,但是在高并发的情况下,会有超卖问题出现。首先想到的是使用加锁的方式。
synchronized
首先加入synchronized锁,这个时候有且仅有一个线程能执行同步代码块里面的代码。但是在分布式情况下,还是存在超卖问题。在分布式情况下,synchronized锁不住,因为synchronized只在JVM进程内部有效,只在一个tomcat内部有效,分布式的情况下,项目肯定不是部署在一个tomcat服务器上,所以在分布式环境下,synchronized锁不住。
SETNX
格式:setnx key value
将key的值设定为value,当前仅当key不存在。
若给定的key已经存在,则SETNX不做任何动作。
SETNX是SET if Not exists,如果不存在,则SET。
将上面代码使用SETNX加锁:
但是上面的写法还是存在问题,如果当前线程在执行以上方法的时候出现异常,那么就可能执行不到解锁的语句:
stringRedisTemplate.delete(lockKey);
这样子的话,下一个线程来访问的时候就拿不到锁,执行不了减库存的代码。那么就继续改造上面的代码:
之前是担心出现异常,导致无法解锁,那么现在使用try{}finally{}捕捉异常,就算出现异常,也一定会解锁。那么再继续考虑,如果当前线程在执行try块中的代码出现宕机,这种情况下finally快中的代码无法执行,还是解不了锁,如何解决?继续改写代码:
给锁设置超时时间,这个样子,就算在执行try块中的代码出现宕机也没关系,因为这把锁的有效时间只有10s,到了这个时间,这把锁就会被Redis清理掉。但是还是有问题:如果这个时候正好执行到下面这条语句:
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"lavender");
还没来得及给锁设置超时时间,程序就挂了,那么还是解决不了问题,所以希望加锁和设置超时时间是一个原子操作。继续改写以上代码:
使用绿框出的代码就行。但是还会存在问题,可能会出现锁永久失效的问题。看一种场景:1号线程在执行这个方法,并且这个线程需要15s才能执行完,但是上面的代码中锁的有效时间只有10s,意味着当前1号线程在执行到10s后,这把锁就失效了,那么2号线程就会进来加锁,假设2号线程执行完这个方法需要8s,那么当1号线程执行到finally里面的方法时,就会把2号线程加的锁释放掉,那么马上3号线程又进来了。。。以此类推,下一个线程可能一直在删除掉上一个线程加的锁,那么这把锁就会出现永久失效的问题。
继续改写代码:
这个样子就可以保证自己加的锁只能自己解。但是还是存在问题,就是锁的过期时间到底设置多少比较合适,还是上面的场景:假设1号线程执行这段代码需要15s,那么在执行完10s后,锁就失效了,还是其他的线程会进来加锁执行上面的代码,还是存在超卖问题。继续改写,使用Redisson。
Redisson
锁续命:当1号线程加锁成功,执行代码的时候,另外启动一个分线程每隔一段时间来监听1号线程是否还没释放锁,如果还没释放锁以为这1号线程的逻辑代码还没执行完,这个时候分线程会重新设置锁的超时时间,给锁续命,防止出现上面的情况。
想使用Redisson,首先得先在Spring容器中注入Redisson对象:
然后改写之前的代码:
Redisson加锁流程:
Redisson中加锁的关键源码:
大概能看出exists、hset、pexpire这些命令是加锁以及涉及过期时间的,不用担心原子性问题,lua脚本具备原子性。
Redis主从结构锁失效问题(Redlock)
一般实际业务中使用redis是集群架构,而不是单机,集群结构中问题比较多。
假设一种情况:1号线在redis主节点中获得锁,并且去加锁,这个时候redis主节点会立刻返回给1号线程true,表示加锁成功,就可以继续执行扣减库存的逻辑代码。但是当redis主节点向从节点中写入数据的时候,主节点宕机了,这个时候哨兵模式中会选一个从节点作为主节点。那么当2号线程过来的时候,就会往新的主节点中尝试获得锁,毫无疑问这个时候会成功,因为原先主节点中的锁情况还没来得及同步到新的主节点中,那么就会认为之前没有线程获得锁,而真实情况是1号线程已经获得了锁,在它还没有释放之前,其他线程是不能加锁的。这就是redis主从结构锁失效的问题。
还有一种方式可以实现分布式锁:zookeeper,也有主从架构。
CAP理论:C表示一致性、A表示可用性、P表示分区容错性。
Redis集群满足:AP
zookeeper集群满足:CP
Redis中只要在主节点中加锁成功,马上返回给客户端告诉其加锁成功;
zookepper中在主节点中加锁成功之后,不会马上返回给客户端告诉其加锁成功,而是内部要先把数据同步给其它节点,至少集群中要有半数的节点数据同步成功了,才会告诉客户端其加锁成功。
Redlock
以上redis节点没有主从区别,而是平等的节点位置。性能牺牲了,也不一定能解决主从锁失效的问题。
更多推荐
所有评论(0)