1 redis 实现分布式锁
redis 实现分布式锁应该注意的问题
//1:独占排他预防栈内存溢出
//2:预防死锁 集群中客户端的某个节点挂了,自己的锁没释放,导致其他客户端没法获取到锁
//3:原子性
//4:防止误删
//5:可重入
//6:自动续期
第一个案例:
public void reduce() {
//1:独占排他 使用setnx来实现
//没有过期时间当集群中的某个客户端掉线 ,并且还没执行finally 中的 删除操作没办法释放锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1");
if(!lock){
try {
Thread.sleep(50);
reduce(); //递归的方式可能会导致栈内存溢出
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
try{
String stock = this.redisTemplate.opsForValue().get("stock");
if (StringUtils.isNoneBlank(stock) && Integer.parseInt(stock) != 0) {
Integer st = Integer.parseInt(stock);
if (st > 0) {
this.redisTemplate.opsForValue().set("stock", String.valueOf(--st));
}
}
}catch (Exception e){
}finally {
redisTemplate.delete("lock");
}
}
}
第二个案例 解决客户端下线没办法解锁的问题以及栈内存溢出问题
public void reduce() {
//1:独占排他 使用setnx来实现ex 来解决客户端下线无法释放锁的问题
//2:使用循环代替递归,避免栈内存溢出的问题
String uuid = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS);
while(!lock){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try{
// this.redisTemplate.expire("stock",3, TimeUnit.SECONDS); //不具备原子性
String stock = this.redisTemplate.opsForValue().get("stock");
if (StringUtils.isNoneBlank(stock) && Integer.parseInt(stock) != 0) {
Integer st = Integer.parseInt(stock);
if (st > 0) {
this.redisTemplate.opsForValue().set("stock", String.valueOf(--st));
}
}
}catch (Exception e){
}finally {
//当超过过期时间我们的主流程还没执行完成,这时候 锁过期了 ,但是另外一个线程进来之后发现没有锁了
//于是第二个线程加了锁,这时候 第一个线程的业务执行完了要释放锁,就会把第二个线程的锁 ,这时候锁就失效了
//出现了误删
//所以这里需要处理两个问题 防止误删
//还需要自动续期来解决我们的业务逻辑在过期时间内没执行完的情况
//这种写法 看上去解决了防止误删的问题,但是其实因为查询和删除操作不是原子性的 也还会出问题所以还要借助于LUA 脚本来处理
if(StringUtils.equals(redisTemplate.opsForValue().get("lock"),uuid)){
redisTemplate.delete("lock");
}
}
}
==================================================================
public void reduce() {
//1:独占排他 使用setnx来实现ex 来解决客户端下线无法释放锁的问题
//2:使用循环代替递归,避免栈内存溢出的问题
String uuid = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS);
while(!lock){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try{
// this.redisTemplate.expire("stock",3, TimeUnit.SECONDS); //不具备原子性
String stock = this.redisTemplate.opsForValue().get("stock");
if (stock!= null && stock.length() != 0) {
Integer st = Integer.parseInt(stock);
if (st > 0) {
this.redisTemplate.opsForValue().set("stock", String.valueOf(--st));
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
//解决误删 和 操作原子性问题
//set lock 111
// eval "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock 111
String script ="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList("lock"),uuid);
// if(StringUtils.equals(redisTemplate.opsForValue().get("lock"),uuid)){
// redisTemplate.delete("lock");
// }
}
}
====================================================
可重入锁使用 redis hash 的存储结构
hset key field value
key lockName
filed uuid:thread号
value 没重入一次 加1 每释放一次锁 减去1