redis分布式锁+redisson框架

目录

🧂1.锁的类型

🌭2.基于redis实现分布式

 🥓3. 基于redisson实现分布式锁


1.锁的类型

  • 1.本地锁:synchronize、lock等,锁在当前进程内,集群部署下依旧存在问题
  • 2.分布式锁:redis、zookeeper等实现,虽然还是锁,但是多个进程共用的锁标记,可以用Redis、Zookeeper, Mysql等都可以

2.基于redis实现分布式锁

1.加锁 setnx key value

  • setnx 的含义就是 SET if Not Exists,有两个参数 setnx(key, value),该方法是原子性操作
  • 如果 key 不存在,则设置当前 key 成功,返回 1;
  • 如果当前 key 已经存在,则设置当前 key 失败,返回 0

2.解锁 del(key)

得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入,调用 del(key) 

3.配置锁超时 expire(key,30)

客户端崩溃或者网络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放 

4.代码 

正常的加锁逻辑,但存在问题

  public void coupon(){
        String key="coupon_id";
        if (setnx(key,1)==1){
            expire(key,30, TimeUnit.MILLISECONDS)
            try{
                //业务
            }finally {
                del(key);
            }
        }else {
            //睡眠,然后自旋调用
            coupon();
        }
    }

5.存在的问题 

  • 1.问题一
    • 多个命令之间不是原子性操作,如setnx和expire之间,如果setnx成功,则这个资源就是死锁但是expire失败,且宕机了,则这个资源就是死锁。
  • 2.解决
    • 使用原子命令:同时设置和配置过期时间 setnx / setex
    • redisTemplate.opsForValue().setIfAbsent(key, v: "lock", l: 30,TimeUnit.SECoNDS);
  • 1.问题二 :
    • 业务超时,存在其他线程勿删,key 30秒过期,假如线程A执行很慢超过30秒,则key就被释放了,其他线程B就得到了锁,这个时候线程A执行完成,而B还没执行完成,结果就是线程A删除了线程B加的锁
  • 2.解决
    • 可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁,那 value 应该是存当前线程的标识或者uuid
  • 3.但是,删除锁时也不是原子性操作。
    • 当线程A获取到正常值时,返回带代码中判断期间锁过期了,线程B刚好重新设置了新值,线程A那边有判断value是自己的标识,然后调用del方法,结果就是删除了新设置的线程B的值
    • 核心还是判断和删除命令 不是原子性操作导致

6.使用lua脚本

  • 问题:加锁使用setnx setex可以保证原子性,那解锁使用判断和删除怎么保证原子性 ????
  • 多个命令的原子性:采用lua脚本+redis,由于【判断和删除】是lua脚本执行,所以要么全成功,要么全失败

lua脚本 

                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  • 使用redis+lua分布式锁,正常严谨的逻辑 

redisTemplate.execute返回值不必须是 long 类型,但你必须确保 Redis 脚本返回的数据与你在 DefaultRedisScript 中指定的类型相匹配。

public class Main {
    @Autowired
    StringRedisTemplate redisTemplate;

    /**
     * 分布式锁-redis
     */
    @Test
    public void test() {
        //根据业务,动态获取key
        String key = "coupon_id";
        //随机生成uuid,作为value值
        String uuid = CommonUtil.generateUUID();
        //设置key的同时,设置过期时间;获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, uuid, 30, TimeUnit.MILLISECONDS);
        //判断是否加锁成功
        if (lock) {
            //枷锁成功
            try {
                //执行业务
            } finally {
                //释放锁,使用lua脚本保持原子性
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //执行lua脚本
                Integer result = redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class), Arrays.asList(key), uuid);
            }
        } else {
            //睡眠一定时间
            //加锁失败,自旋
            test();
        }
    }
}
  • 锁的过期时间,如何实现锁的自动过期 或者 避免业务执行时间过长,锁过期了?
  • 答:一般把锁的过期时间设置久一点,比如10分钟时间 

 3. 基于redisson实现分布式锁

1.添加依赖

            <!--分布式锁-->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.12.0</version>
            </dependency>

2.添加redissonClient

如果报错的话,查看java版本是否正确

@Configuration
@Data
public class RedissonConfig {

    //从配置文件获取redis的相关信息
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private String redisPort;

    /**
     * 使用redisson作为分布式锁
     * @return
     */
    @Bean
    public RedissonClient redissonClient() {
        //1.创建配置对象
        Config config = new Config();
        //2.连接redis
        config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort);
        //3,创建redissonClient对象
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

3.实现分布式锁

myLock.lock()如果不指定过期时间,则默认30s后过期

  @Autowired
    private RedissonClient redissonClient;

    @GetMapping("/lock")
    public JsonData test() {
        //1.获取锁
        RLock myLock = redissonClient.getLock("my_lock");
        //2.手动加锁
        myLock.lock();
        try {
            //3.业务实现
            System.out.println("加锁成功~" + Thread.currentThread().getName());
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //4.手动解锁
            myLock.unlock();
            System.out.println("解锁成功~" + Thread.currentThread().getName());
        }
        return JsonData.buildSuccess();
    }

4.看门狗机制 

Redis锁的过期时间小于业务的执行时间该如何续期?

  • 为了避免这种情况的发生, Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。
  • Redisson中客户端一旦加锁成功,就会启动一个watch dog看门狗。watch dog是一个后台线程,会每隔10秒检查一下,如果客户端还持有锁key,那么就会不断的延长锁key的生存时间。
  • 1.指定加锁时间 

myLock.lock(10, TimeUnit.SECONDS) 

  • 指定加锁时间后,默认10秒后过期,并且没有看门狗机制
  • 2.不指定加锁时间

 myLock.lock(10, TimeUnit.SECONDS) 

  • 默认30秒后过期,存在看门狗机制
  • 只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔(【LockWatchingTimeOut看门狗默认时间】/3)这么长时间自动续期;

相关推荐

  1. Redis - 分布式Redisson

    2024-04-13 15:38:03       54 阅读
  2. 三、详解Redis分布式&Redisson分布式

    2024-04-13 15:38:03       58 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-13 15:38:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-13 15:38:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-13 15:38:03       82 阅读
  4. Python语言-面向对象

    2024-04-13 15:38:03       91 阅读

热门阅读

  1. Qt Designer 如何添加自己制作的控件?

    2024-04-13 15:38:03       30 阅读
  2. C++Qt中异常处理try-catch

    2024-04-13 15:38:03       35 阅读
  3. MATLAB入门介绍

    2024-04-13 15:38:03       36 阅读
  4. C++力扣Leetcode算法5--搜索

    2024-04-13 15:38:03       31 阅读
  5. Dockerfile中 CMD和ENTRYPOINT的区别

    2024-04-13 15:38:03       35 阅读
  6. 【SSH】群晖开启ssh访问

    2024-04-13 15:38:03       30 阅读
  7. 蓝桥杯抱佛脚篇~

    2024-04-13 15:38:03       33 阅读
  8. 从输入URL到页面发生了什么

    2024-04-13 15:38:03       41 阅读
  9. 负载均衡原理及算法

    2024-04-13 15:38:03       45 阅读
  10. 257-这世上有时候就需要人来做傻子

    2024-04-13 15:38:03       34 阅读
  11. 机器人集群激光雷达视野共享

    2024-04-13 15:38:03       33 阅读