前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBootredis使用setnx完成分布式锁

SpringBootredis使用setnx完成分布式锁

作者头像
暴躁的程序猿
发布2022-07-12 16:50:52
6900
发布2022-07-12 16:50:52
举报

spring boot 版本2.2.0

pom依赖

代码语言:javascript
复制
   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

yml配置redis地址端口

代码语言:javascript
复制
 redis:
    host: 127.0.0.1
    port: 6379

使用springboot自动配置好的StringRedisTemplate来操作redis

分布式锁阶段一 直接使用setnx 加锁

代码语言:javascript
复制
/**
 * redis分布式锁
 * @return
 */
public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
    //redis分布式锁     如果key不存在设置成功
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1");
    if (lock){
         //加锁成功  调用方法返回数据
        Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
        //释放锁
        stringRedisTemplate.delete("lock");
        return dataFromDB;

    }else {
         //没有分布式锁,等待,进入自旋
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //自旋
            return getCatalogJsonFromDbWithRedisLock();
        }
}

存在问题:setnx抢占锁成功,业务代码或者程序在运行过程中宕机了,没有执行锁删除逻辑,就会造成死锁。

解决:设置锁的自动过期,即使没有手动删除,会自动删除; 分布式锁阶段二 使用expire 设置过期时间

代码语言:javascript
复制
/**
     * redis分布式锁
     * @return
     */
    public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        //redis分布式锁     如果key不存在设置成功 
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1");
        if (lock){
             //加锁成功  调用方法返回数据
//            设置过期时间  5分钟
            stringRedisTemplate.expire("lock",5, TimeUnit.MINUTES);
            Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
            //释放锁
            stringRedisTemplate.delete("lock");
            return dataFromDB;

        }else {
         //没有分布式锁,等待,进入自旋
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //自旋
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

这里还有一个问题 设置过期时间跟加锁不是一条语句 如果加锁成功但设置过期时间未成功 还是会出现问题

将加锁与设置过期时间原子完成

代码语言:javascript
复制
    /**
     * redis分布式锁
     * @return
     */
    public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        //redis分布式锁     如果key不存在设置成功  设置过期时间  5分钟
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1",1, TimeUnit.MINUTES);
        if (lock){
             //加锁成功  调用方法返回数据
            Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
            //释放锁
            stringRedisTemplate.delete("lock");
            return dataFromDB;

        }else {
           //没有分布式锁,等待,进入自旋
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //自旋
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

问题 :删除锁直接删除, 由于业务处理时间长,锁过期了,我们直接删除,有可能把别人正在持有的锁删除了,

解决:占锁的时候,值指定为uuid,每个人匹配是自己的锁才删除 分布式锁阶段三 匹配uuid

代码语言:javascript
复制
 /**
     * redis分布式锁
     * @return
     */
    public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        //redis分布式锁     如果key不存在设置成功  设置过期时间  5分钟
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,1, TimeUnit.MINUTES);
        if (lock){
             //加锁成功  调用方法返回数据
//            设置过期时间  5分钟
//            stringRedisTemplate.expire("lock",5, TimeUnit.MINUTES);
            Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
            //获取到锁的值
            String curLock = stringRedisTemplate.opsForValue().get("lock");
            if (curLock.equals(uuid)){
                //如果curLock的值等于当前uuid的值  那么就删除锁
                stringRedisTemplate.delete("lock");
            }
            return dataFromDB;
        }else {
            //没有分布式锁,等待,进入自旋
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //自旋
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

问题

如果 String curLock = stringRedisTemplate.opsForValue().get(“lock”);语句在查询的时候key没有过期,但执行删除之前key过期了 我们删除的还是别人的锁。

解决 使用lua脚本 原子的查询与删除锁

代码语言:javascript
复制
 /**
     * redis分布式锁
     * @return
     */
    public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        //redis分布式锁     如果key不存在设置成功  设置过期时间  5分钟
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,1, TimeUnit.MINUTES);
        if (lock){
             //加锁成功  调用方法返回数据
            Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
            //获取到锁的值
            String curLock = stringRedisTemplate.opsForValue().get("lock");
            if (curLock.equals(uuid)){
                //如果curLock的值等于当前uuid的值  那么就删除锁
                stringRedisTemplate.delete("lock");
            }
            String script="if redis call('get',KEYS[1]) == ARGV[1] then return redis call('del',KEYS[1]) else return 0 end";
            //new DefaultRedisScript<Integer>(script)  泛型为返回值的类型 参数为脚本
            Integer execute = stringRedisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.TYPE), Arrays.asList("lock"), uuid);
            
            return dataFromDB;
        }else {
            //没有分布式锁,等待,进入自旋
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //自旋
            return getCatalogJsonFromDbWithRedisLock();
        }
    }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档