Redis分布式锁的实现方式

时间:2024-8-27    作者:老大夫    分类: 乐尚代驾


1. Redis原生方法

Redis的setnx在代码中是RedisTemplate.setIfAbsent(并不是.setnx)

  • 注意为了防止在过程中异常导致无法释放锁,需要设置过期时间来解决,但是仍然存在问题,如果业务7秒处理,锁三秒释放,会出现释放不属于自己的锁的情况(锁是一个共用的,别人用着呢就被释放了)

  • 使用UUID给锁加上唯一标识来解决误删锁的问题,但是仍然存在问题,没有原子性,其他线程可以进入打断当前线程

  • 通过Redis的LUA脚本保持原子性,当前线程结束其他线程才能进入。

视频教程:https://www.bilibili.com/video/BV1nW421R7qJ?p=132&spm_id_from=pageDriver&vd_source=2f88c79898ac6db8b9db2d2439d8c6b0

  //UUID防止误删:redis中的setnx进行实现
    @Override
    public void testLock(){
        //从redis中取出数据
        String uuid= UUID.randomUUID().toString();
        //1.获取当前锁
//        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock");
        //添加锁的过期时间,保证即使出现异常也可以实现解锁
        Boolean ifAbsent = redisTemplate.opsForValue()
                .setIfAbsent("lock", uuid,10, TimeUnit.SECONDS);

        //2.如果获取到了锁,从redis中获取数据,数据+1 放回到redis中去
        if(ifAbsent){
            //获取锁成功,执行业务代码
            //1.先从redis中通过key num获取值  key提前手动设置 num 初始值:0
            String value = redisTemplate.opsForValue().get("num");
            //2.如果值为空则非法直接返回即可
            if (StringUtils.isBlank(value)) {
                return;
            }
            //3.对num值进行自增加一
            int num = Integer.parseInt(value);
            redisTemplate.opsForValue().set("num", String.valueOf(++num));

            //3.释放锁,LUA脚本实现
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            //设置LUA脚本
            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                    "then\n" +
                    "    return redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";
            redisScript.setScriptText(script);
            //设置返回结果
            redisScript.setResultType(Long.class);
            redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid); 
        }else{
            //没有获得到锁,重新获取
            try {
                //睡眠
                Thread.sleep(100);
                //自旋重试
                this.testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

2. Redisson框架

Redis提供的分布式锁框架,可以将锁和业务逻辑分离开,方便编写业务。

(1) 编写配置类RedissonClient的配置类

@Data
@Configuration
@ConfigurationProperties(prefix = "spring.data.redis")//redis的配置信息在nacos中调用
public class RedissonConfig {
    private String host;
    private String password;
    private String port;
    private int timeout = 3000;
    private static String ADDRESS_PREFIX = "redis://";

    /**
     * 自动装配
     *
     */
    @Bean
    RedissonClient redissonSingle() {
        Config config = new Config();

        if(!StringUtils.hasText(host)){
            throw new RuntimeException("host is  empty");
        }
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(ADDRESS_PREFIX + this.host + ":" + port)
                .setTimeout(this.timeout);
        if(StringUtils.hasText(this.password)) {
            serverConfig.setPassword(this.password);
        }
        return Redisson.create(config);
    }
}

(2)使用

    //Redisson实现分布式锁
    @Override
    public void testLock(){
        //1.首先通过Redisson创建锁对象
        RLock lock = redissonClient.getLock("lock1");
        //2.尝试获取锁
        //(1) 阻塞一直等待直到获取锁,默认过期时间30s
        lock.lock();
//        //(2)获取到锁,锁过期时间10s
//        lock.lock(10, TimeUnit.SECONDS);
//        //(3)第一个参数:获取锁的时间 30s ;第二个参数:30s以内获取到了锁,过期时间为10s
//        try {
//            lock.tryLock(30,10,TimeUnit.SECONDS);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        //3.成功获取锁后编写业务代码
        //获取锁成功,执行业务代码
        //1.先从redis中通过key num获取值  key提前手动设置 num 初始值:0
        String value = redisTemplate.opsForValue().get("num");
        //2.如果值为空则非法直接返回即可
        if (StringUtils.isBlank(value)) {
            return;
        }
        //3.对num值进行自增加一
        int num = Integer.parseInt(value);
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
        //4.释放锁
        lock.unlock();
    }


扫描二维码,在手机上阅读

推荐阅读: