redis分布式锁的各种坑_山水友相逢wxg的博客-程序员宅基地_redis分布式锁没锁住

技术标签: redis  

用redis做分布式锁,看似是一个非常简单的问题,但是其中却蕴含着很多坑,我从单机方式开始把redis分布式锁的所有坑都踩一踩,豁然开朗。文字描述较少,直接上代码,相信大家一看就懂。尤其是点评把每一个情况的坑描述出来了。后一种情况会把上一种情况的坑给解决掉。

第一种情况:单机方式

我们对一个库存进行扣减的一个简单操作

public class RedisService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public String descStock(){
    	//获取库存量
        int stockNum = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
        if(stockNum>0){
        	//库存减一
            stockNum--;
            //重新设置库存
            redisTemplate.opsForValue().set("stock",stockNum+"");
            System.out.println("库存扣减成功,剩余"+stockNum);
        } else {
            System.out.println("库存扣减失败");
        }
        return "SUCCESS";
    }
}

点评:

在单机环境下,这个代码有什么问题呢?如果并发高的情况下,stockNum是一个共享变量,很明显会带来线程安全问题

第二种情况:单机+同步锁

对于第一种情况,我们的解决办法也很简单,只需要加上synchronized代码块或者Lock锁即可

 public String descStock() {
        synchronized (this) {
            int stockNum = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stockNum > 0) {
                stockNum--;

                redisTemplate.opsForValue().set("stock", stockNum + "");
                System.out.println("库存扣减成功,剩余" + stockNum);
            } else {
                System.out.println("库存扣减失败");
            }
        }
        return "SUCCESS";
    }

点评:

对于这种情况,如果在分布式集群模式下是否能实现共享变量的安全问题呢?明显不能,因为synchronized和Lock都是线程级别的锁,不能在进程之间锁住数据的安全性。这里我们用redis做分布式锁来解决(除了redis,还有zookeeper和数据库来实现分布式锁)

第三种情况:分布式环境+redis实现分布式锁–1:加分布式锁

这里用到了setnx命令:如果key存在着set失败,如果不存在则set成功。设置成功,意味着当前线程拿到锁,设置失败意味着当前线程没有拿到锁。

    public String descStock() {
        String lockKey = "lockKey";
        //类似jedis.setnx(lockKey,"wxgLock");
        //获取分布式锁
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "wxgLock");
        if(!result){
            return "被锁住了,请稍后重试:";
        }
        //throw new Exception(); ---step A
        int stockNum = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
        if (stockNum > 0) {
            stockNum--;

            redisTemplate.opsForValue().set("stock", stockNum + "");
            System.out.println("库存扣减成功,剩余" + stockNum);
        } else {
            System.out.println("库存扣减失败");
        }
        return "SUCCESS";
    }

点评:

问题。锁没有释放,导致其他线程永远拿不到锁,导致死锁。

第四种情况:分布式环境+redis实现分布式锁–2:释放分布式锁

public String descStock() {
        String lockKey = "lockKey";
         //获取分布式锁
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "wxgLock");//类似jedis.setnx(lockKey,"wxgLock");
        try {
            if(!result){
                return "被锁住了,请稍后重试:";
            }
            int stockNum = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stockNum > 0) {
                stockNum--; // step A

                redisTemplate.opsForValue().set("stock", stockNum + "");
                System.out.println("库存扣减成功,剩余" + stockNum);
            } else {
                System.out.println("库存扣减失败");
            }    
        }finally {
        	//释放锁
            redisTemplate.delete(lockKey);
        }
        
        return "SUCCESS";
    }

点评:

注意这里把释放锁放到了finally中是因为如果业务代码异常的话也可以释放锁。难道就没有问题了吗?

问题:当代码执行STEP A 中的时候,如果是服务器宕机了,或者kill -9 了。那么依然会导致锁无法释放,其他服务依然拿不到锁,导致死锁。怎么办?

第五种情况:分布式环境+redis实现分布式锁–3:设置过期时间


    public String descStock() {
        String lockKey = "lockKey";
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "wxgLock");//类似jedis.setnx(lockKey,"wxgLock");
        redisTemplate.expire(lockKey,30, TimeUnit.SECONDS); //设置过期时间30s -- step a
        try {
            if(!result){
                return "被锁住了,请稍后重试:";
            }
            int stockNum = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stockNum > 0) {
                stockNum--;

                redisTemplate.opsForValue().set("stock", stockNum + "");
                System.out.println("库存扣减成功,剩余" + stockNum);
            } else {
                System.out.println("库存扣减失败");
            }
        }finally {
            redisTemplate.delete(lockKey);
        }

        return "SUCCESS";
    }

点评:

这个代码就没有问题吗?如果在设置时间前,也就是step a前就崩了,那么虽然设置成功了,但是,没有设置过期时间成功,依然会有第五种情况的问题,本质是两步操作么有实现原子性。死锁

第六种情况:分布式环境+redis实现分布式锁–4:设置过期时间


    public String descStock() {
        String lockKey = "lockKey";
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "wxgLock",10,TimeUnit.SECONDS);
        try {
            if(!result){
                return "被锁住了,请稍后重试:";
            }
            int stockNum = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stockNum > 0) {
                stockNum--;

                redisTemplate.opsForValue().set("stock", stockNum + "");
                System.out.println("库存扣减成功,剩余" + stockNum);
            } else {
                System.out.println("库存扣减失败");
            }
        }finally {
            redisTemplate.delete(lockKey);
        }

        return "SUCCESS";
    }

点评:

一步操作保证原子性。但是真的没问题了吗?在并发不高的情况下这个分布式锁问题不大,但是在高并发下出现如下问题:

在这里插入图片描述

也就是说自己创建的锁被其他线程给删除掉了!怎么办?可以引入一个唯一值,如thread ID或者uuid等

 public String descStock() {
        String lockKey = "lockKey";
        String tid = Thread.currentThread().getId()+"";
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, tid,10,TimeUnit.SECONDS);//类似jedis.setnx(lockKey,"wxgLock");
        try {
            if(!result){
                return "被锁住了,请稍后重试:";
            }
            int stockNum = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stockNum > 0) {
                stockNum--;

                redisTemplate.opsForValue().set("stock", stockNum + "");
                System.out.println("库存扣减成功,剩余" + stockNum);
            } else {
                System.out.println("库存扣减失败");
            }
        }finally {
            if(tid.equals(redisTemplate.opsForValue().get(lockKey))){
                redisTemplate.delete(lockKey);
            }
        }

        return "SUCCESS";
    }

点评:

这样就真的没问题了吗?一脸蒙蔽。还有一个问题:我们设置了10s的过期时间真的够吗?如果业务代码的一个sql慢查询超过了10s。那么还是会导致代码没有执行完,锁已经自动释放了,其他线程可以获取锁(多个线程同时获得了锁),导致线程安全问题。那么设置几秒合适呢?好像没有合适的值吧。那怎么办?----可以用一个定时器来实现:锁的续命

第七种情况:分布式环境+redis实现分布式锁–5:锁的续命

锁的续命就是每隔一段时间判断当前线程的锁是否存在,如果存在则重置过期时间(给锁续了一条命!)
直接上代码:单开一个线程(可以用线程池优化),实现锁的续命

 public String descStock() {
        String lockKey = "lockKey";
        String tid = Thread.currentThread().getId()+"";
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, tid,10,TimeUnit.SECONDS);//类似jedis.setnx(lockKey,"wxgLock");
        
        
        //锁续命的逻辑:可能不严谨,但是思路应该就这样
        new Thread(()->{
            Timer timer =new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    String tid = redisTemplate.opsForValue().get(lockKey);
                    if(id!=null&&tid.equals(id)){
                        //锁续命
                        redisTemplate.opsForValue().setIfAbsent(lockKey, tid,10,TimeUnit.SECONDS);
                    } else {
                   		 timer.cancel(); 
                    }
                   
                }
            },2000,3000);//延迟2s执行,每隔3s执行一次续命逻辑
        });
        
        
        try {
            if(!result){
                return "被锁住了,请稍后重试:";
            }
            int stockNum = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stockNum > 0) {
                stockNum--;

                redisTemplate.opsForValue().set("stock", stockNum + "");
                System.out.println("库存扣减成功,剩余" + stockNum);
            } else {
                System.out.println("库存扣减失败");
            }
        }finally {
            if(tid.equals(redisTemplate.opsForValue().get(lockKey))){
                redisTemplate.delete(lockKey);
            }
        }

        return "SUCCESS";
    }

点评:

这样的话redis分布式锁就差不多了。但是操作到第七种的情况下已经变得复杂了,也不是所有人能考虑到高并发下的这些所有情况,所以我们可以借用Redission来实现分布式锁。

第八种情况:分布式环境+redis实现分布式锁–6:redission

public String descStock() {
        String lockKey = "lockKey";
        RLock lock = redisson.getLock(lockKey); 
        try {
            lock.lock();
            int stockNum = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stockNum > 0) {
                stockNum--;

                redisTemplate.opsForValue().set("stock", stockNum + "");
                System.out.println("库存扣减成功,剩余" + stockNum);
            } else {
                System.out.println("库存扣减失败");
            }
        }finally {
           lock.unlock();
        }

        return "SUCCESS";
    }

点评:

lock.lock(); lock.unlock();两行代码解决,方便!且默认已经解决了以上的所有问题。

redission内部是用lua脚本实现的,本来想上源码分析的,但是码字实在太累了,有空再补上吧。撤了撤了。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36794621/article/details/104883953

智能推荐

redis分布式锁及会出现的问题解决

主要给大家介绍了关于redis分布式锁及会出现问题的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

Redis分布式锁的实现方式(redis面试题)

主要介绍了Redis分布式锁的实现方式(面试常见),需要的朋友可以参考下

Redis分布式锁实现方式及超时问题解决

主要介绍了Redis分布式锁实现方式及超时问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

AOP方式实现的redis分布式锁

redis分布式锁的实现: 1、利用LUA脚本,防止redis意外导致死锁。 2、基于AOP方式实现。 3、只需要在方法上声明@DistributedLock(可以是controller的方法也可以是service的公共方法)即可实现锁功能。 4、支持...

用Redis实现分布式锁_redis_分布式_源码

用Redis实现分布式锁的简易教程,在ppt中附有代码解析

深入理解redis分布式锁和消息队列

最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令。 分布式锁 由于目前一些编程语言,如PHP等,不能在内存中使用锁,或者如Java这样...

redis分布式锁工具类

现在很多项目单机版已经不满足了,分布式变得越受欢迎,同时也带来很多问题,分布式锁也变得没那么容易实现,分享一个redis分布式锁工具类,里面的加锁采用lua脚本(脚本比较简单,采用java代码实现,无须外部调用...

golang实现Redis分布式自旋锁+本地自旋锁

golang使用redis的setnx实现了一个自选锁,有key超时,同时也有我们调用redis链接时的超时。 package locker import ( context github.com/go-redis/redis runtime time ) type Lock struct { resource string...

Redis分布式锁解决接口幂等的两种方案

Redis分布式锁解决接口幂等的两种方案一、背景二、基础知识三、解决方案四、实验五、说在最后的话 一、背景 还在为不了解分布式锁而烦恼吗?还在为众多微服务接口不幂等而发愁吗?如果是,并且有兴趣同我一起学习,...

Java基于redis实现分布式锁代码实例

主要介绍了Java基于redis实现分布式锁代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

基于redis分布式锁实现秒杀功能

主要为大家详细介绍了基于redis分布式锁实现秒杀功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

Redis实现分布式锁和等待序列的方法示例

主要介绍了Redis实现分布式锁和等待序列的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

Redis Template实现分布式锁的实例代码

主要介绍了Redis Template实现分布式锁,需要的朋友可以参考下

redis和zookeeper实现分布式锁的区别

redis 会尝试在所有master上创建锁,但是只对一部分节点创建锁,个数为(n/2+1)个,获取一个毫秒级时间戳,设置创建时间,如果创建时间小于超时时间就视为成功,如果创建失败就顺序删除。只要创建好一个分布式锁,...

Redis上实现分布式锁以提高性能的方案研究

主要介绍了Redis上实现分布式锁以提高性能的方案研究,其中重点需要理解异步算法与锁的自动释放,需要的朋友可以参考下

redis实现分布式锁(java/jedis)

redis实现分布式锁(java/jedis),其中包含工具方法以及使用demo 本资源是利用java的jedis实现 redis实现分布式锁(java/jedis),其中包含工具方法以及使用demo 本资源是利用java的jedis实现

java基于jedisLock—redis分布式锁实现示例代码

主要介绍了jedisLock—redis分布式锁实现示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

SpringBoot整合Redis正确的实现分布式锁的示例代码

主要介绍了SpringBoot整合Redis正确的实现分布式锁的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

redis分布式锁原理与实例

2 redis分布式锁的原理 2.1 分布式锁的基本实现 2.2 如何避免死锁 2.3 解锁 3 redis分布式锁实现示例 4 验证 4.1 创建redis连接池 4.2 单线程验证 4.3 多线程验证 1 分布式锁的实现方式 分布式锁常用的有...

Python实现的redis分布式锁功能示例

主要介绍了Python实现的redis分布式锁功能,结合实例形式分析了Python操作redis分布式锁与解锁功能相关操作技巧,需要的朋友可以参考下

spring-boot-redis-token 分布式锁 redis session

http://localhost:8080/ass/getUser 测试是否登录 http://localhost:8080/any/user/passLogin 登录测试 http://localhost:8080/lock redis分布式锁测试

用注解实现redis分布式锁,防止短时间内重复请求

用注解实现redis分布式锁,防止短时间内重复请求,尤其对于请求耗时较长的方法,希望对大家有帮助

redis分布式锁.zip

本资源为一步一步实现redis分布式锁的demo,利用redis实现高可用的分布式锁,规避各种坑、坑、坑!

基于 Redis 的分布式锁

自己整理的如何利用redis实现分布式锁,redis实现分布式锁看这一篇绝对够。

详解使用Redis SETNX 命令实现分布式锁

本篇文章主要介绍了详解使用Redis SETNX 命令实现分布式锁,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

springboot基于redis分布式锁

SpringBoot基于redis的分布式锁,有word使用文档,根据文档配置即可使用

Go-用Redis实现分布式锁与实现任务队列

用Redis实现分布式锁 与 实现任务队列

基于redis分布式锁_代码实现

1、工具类实现redis分布式锁 说明,代码可以封装在工具jar中,方便各系统引用。 import com.myfutech.common.util.constant.RedisPrefix; import lombok.extern.slf4j.Slf4j; import org.springframework.data....

基于redis分布式锁实现“秒杀”

从业务场景出发,从抽象到实现阐述了如何利用redis实现分布式锁,完成简单的秒杀功能,也记录了笔者思考的过程,希望能给阅读到本篇文章的人一些启发。

随便推点

推荐文章

热门文章

相关标签