0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深入理解redis分布式锁

科技绿洲 来源:Java技术指北 作者:Java技术指北 2023-10-08 14:13 次阅读

深入理解redis分布式锁

哈喽,大家好,我是指北君。
本篇文件我们来介绍如何Redis实现分布式锁的演进过程,以及为什么不能直接用Setnx实现分布式锁。

1、分布式锁简介

分布式锁是控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

图片

业界流行的分布式锁实现,一般有这3种方式:

  • 基于数据库实现的分布式锁
  • 基于Redis实现的分布式锁
  • 基于Zookeeper实现的分布式锁

这里主要介绍如何通过 Redis 来实现分布式锁。在介绍 Redis 分布式锁之前,我们首先介绍一下实现Redis 分布式锁的关键命令。

2、setnx

setnx key value

Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。

设置成功,返回 1 。设置失败,返回 0 。

PS:Redis 官方是不推荐基于 setnx 命令来实现分布式锁的,因为会存在很多问题,

①、单点问题。比如:

  • 1、客户端A 从master拿到锁lock01
  • 2、master正要把lock01同步(Redis的主从同步通常是异步的)给slave时,突然宕机了,导致lock01没同步给slave
  • 3、主从切换,slave节点被晋级为master节点
  • 4、客户端B到master拿lock01照样能拿到。这样必将导致同一把锁被多人使用。

②、锁的高级用法,比如读写锁、可重入锁等等,setnx 都比较难实现。

这里先介绍基于 sentnx 实现的分布式锁,后面会介绍官方推荐的基于 redisson 来实现分布式锁。

3、Redis-分布式锁-阶段1

接到上文,查询三级分类数据,如果我们部署了多个商品服务,然后多个线程同时去获取三级分类数据,如果不加分布式锁,就会导致,每一个部署的商品服务第一次查询都会走 DB。

public Map< String, List< Catelog2Vo >> getCatelogJsonWithRedisLock() throws InterruptedException {
    // 一、获取分布式锁
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");

    if(lock){
        // true 表示加锁成功,执行相关业务
        Map< String, List< Catelog2Vo >> dataFromDb = getDataFromDb();
        stringRedisTemplate.delete("lock");
        return dataFromDb;
    }else{
        System.out.println("获取分布式锁失败...等待重试...");
        //加锁失败...重试机制
        //休眠一百毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //自旋的方式
        return getCatelogJsonWithRedisLock();
    }
}

图片

4、Redis-分布式锁-阶段2

设置锁自动过期

public Map< String, List< Catelog2Vo >> getCatelogJsonWithRedisLock() throws InterruptedException {
    // 一、获取分布式锁
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");

    if(lock){
        // true 表示加锁成功,执行相关业务
        // 设置过期时间
        stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
        Map< String, List< Catelog2Vo >> dataFromDb = getDataFromDb();
        stringRedisTemplate.delete("lock");
        return dataFromDb;
    }else{
        System.out.println("获取分布式锁失败...等待重试...");
        //加锁失败...重试机制
        //休眠一百毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //自旋的方式
        return getCatelogJsonWithRedisLock();
    }
}

图片

5、Redis-分布式锁-阶段3

setnx 命令和过期时间保证原子性。

public Map< String, List< Catelog2Vo >> getCatelogJsonWithRedisLock() throws InterruptedException {
    // 一、获取分布式锁
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111",30,TimeUnit.SECONDS);

    if(lock){
        // true 表示加锁成功,执行相关业务
        // 设置过期时间
        //stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
        Map< String, List< Catelog2Vo >> dataFromDb = getDataFromDb();
        stringRedisTemplate.delete("lock");
        return dataFromDb;
    }else{
        System.out.println("获取分布式锁失败...等待重试...");
        //加锁失败...重试机制
        //休眠一百毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //自旋的方式
        return getCatelogJsonWithRedisLock();
    }
}

图片

6、Redis-分布式锁-阶段4

保证删除的是自己的锁。

public Map< String, List< Catelog2Vo >> getCatelogJsonWithRedisLock() throws InterruptedException {
    // 一、获取分布式锁
    String uuid = UUID.randomUUID().toString();
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);

    if(lock){
        // true 表示加锁成功,执行相关业务 
        // 设置过期时间
        //stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
        Map< String, List< Catelog2Vo >> dataFromDb = getDataFromDb();
        String lockValue = stringRedisTemplate.opsForValue().get("lock");
        if(uuid.equals(lockValue)){
            stringRedisTemplate.delete("lock");
        }
        return dataFromDb;
    }else{
        System.out.println("获取分布式锁失败...等待重试...");
        //加锁失败...重试机制
        //休眠一百毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //自旋的方式
        return getCatelogJsonWithRedisLock();
    }
}

图片

7、Redis-分布式锁-阶段5

通过Lua脚本保证删除锁和判断锁两个操作原子性

public Map< String, List< Catelog2Vo >> getCatelogJsonWithRedisLock(){
    // 一、获取分布式锁
    String uuid = UUID.randomUUID().toString();
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);

    if (lock) {
        System.out.println("获取分布式锁成功...");
        Map< String, List< Catelog2Vo >> dataFromDb = null;
        try {
            //加锁成功...执行业务
            dataFromDb = getDataFromDb();
        } finally {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

            //删除锁
            stringRedisTemplate.execute(new DefaultRedisScript< Long >(script, Long.class), Arrays.asList("lock"), uuid);

        }
        //先去redis查询下保证当前的锁是自己的
        //获取值对比,对比成功删除=原子性 lua脚本解锁
        // String lockValue = stringRedisTemplate.opsForValue().get("lock");
        // if (uuid.equals(lockValue)) {
        //     //删除我自己的锁
        //     stringRedisTemplate.delete("lock");
        // }

        return dataFromDb;
    }else{
        System.out.println("获取分布式锁失败...等待重试...");
        //加锁失败...重试机制
        //休眠一百毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //自旋的方式
        return getCatelogJsonWithRedisLock();
    }
}

这也是分布式锁的最终模式,需要保证两个点:加锁【设置锁+过期时间】和删除锁【判断+删除】原子性。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 主机
    +关注

    关注

    0

    文章

    897

    浏览量

    34607
  • key
    key
    +关注

    关注

    0

    文章

    44

    浏览量

    12744
  • Redis
    +关注

    关注

    0

    文章

    362

    浏览量

    10492
收藏 人收藏

    评论

    相关推荐

    redis分布式锁场景实现

    今天带大家深入剖析一下Redis分布式锁,彻底搞懂它。 场景 既然要搞懂Redis分布式锁,那肯定要有一个需要它的场景。 高并发售票问题就是
    的头像 发表于 09-25 17:09 452次阅读

    深入理解Android

    深入理解Android
    发表于 08-20 15:30

    在 Java 中利用 redis 实现一个分布式服务

    在 Java 中利用 redis 实现一个分布式服务
    发表于 07-05 13:14

    分布式Redis的五种数据类型

    分布式_Redis》_概述汇总
    发表于 10-15 10:55

    对栈的深入理解

    为什么要深入理解栈?做C语言开发如果栈设置不合理或者使用不对,栈就会溢出,溢出就会遇到无法预测乱飞现象。所以对栈的深入理解是非常重要的。注:动画如果看不清楚可以电脑看更清晰啥是栈先来看一段动画:没有
    发表于 02-15 07:01

    为什么要深入理解

    [导读] 从这篇文章开始,将会不定期更新关于嵌入C语言编程相关的个人认为比较重要的知识点,或者踩过的坑。为什么要深入理解栈?做C语言开发如果栈设置不合理或者使用不对,栈就会溢出,溢出就会遇到无法
    发表于 02-15 06:09

    深入理解Android》文前

    深入理解Android》文前
    发表于 03-19 11:23 0次下载

    深入理解Android:卷I》

    深入理解Android:卷I》
    发表于 03-19 11:23 0次下载

    Redis分布式锁真的安全吗?

    今天我们来聊一聊Redis分布式锁。
    的头像 发表于 11-02 14:07 703次阅读

    手撸了个Redis分布式

    实现分布式锁的方式有很多,其中 Redis 是最常见的一种。而相较于 Java + Redis 的方案,我个人更倾向于 Go+Redis,从编程难易程度上来说,要简单得多。
    的头像 发表于 11-03 14:44 469次阅读

    redis分布式锁如何实现

    Redis分布式锁是一种基于Redis实现的机制,可以用于多个进程或多台服务器之间对共享资源的并发访问控制。在分布式系统中,由于多个进程或多台服务器同时访问共享资源,可能会发生数据竞争
    的头像 发表于 11-16 11:29 299次阅读

    redis分布式锁死锁处理方案

    中,Redis分布式锁也可能遭遇死锁问题,即多个线程相互等待对方释放锁的情况。本文将详细介绍Redis分布式锁死锁的原因,并提供解决死锁的多种策略。 一、
    的头像 发表于 11-16 11:44 508次阅读

    redis分布式锁的应用场景有哪些

    Redis分布式锁是一种基于Redis实现的分布式锁机制,可以在分布式环境下确保资源的独占性,避免并发访问时的数据争用问题。下面将详细介绍
    的头像 发表于 12-04 11:21 554次阅读

    如何实现Redis分布式

    Redis是一个开源的内存数据存储系统,可用于高速读写操作。在分布式系统中,为了保证数据的一致性和避免竞态条件,常常需要使用分布式锁来对共享资源进行加锁操作。Redis提供了一种简单而
    的头像 发表于 12-04 11:24 382次阅读

    redis分布式锁的缺点

    Redis分布式锁是一种常见的用于解决分布式系统中资源争用问题的解决方案。尽管Redis分布式锁具有很多优点,但它也存在一些缺点。本文将从几
    的头像 发表于 12-04 14:05 473次阅读