一文带你搞懂Redis分布式锁

目录
  • 1、分布式锁简介
  • 2、setnx
  • 3、Redis-分布式锁-阶段1
  • 4、Redis-分布式锁-阶段2
  • 5、Redis-分布式锁-阶段3
  • 6、Redis-分布式锁-阶段4
  • 7、Redis-分布式锁-阶段5

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();
    }
}

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

到此这篇关于一文带你搞懂Redis分布式锁的文章就介绍到这了,更多相关Redis分布式锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Redis分布式锁介绍与使用

    目录 分布式锁 业务逻辑分析 Redis命令 代码实现 分布式锁误删问题 问题原因分析 代码实现 Lua脚本 首先,使用idea模拟搭建一个tomcat服务器集群,并使用Nginx对集群中的服务器实现负载均衡 配置完负载均衡之后,发送两次请求就会在idea的运行窗口中发现,两次请求的运行是分别在两个服务器中完成,这就是集群的轮询机制 分布式锁 业务逻辑分析   在单JVM虚拟机多线程执行的情况下,可以使用JVM内部的锁机制来控制多进程的并发执行,借此可以保证一个用户只能下一个优惠券订单.但是在分

  • Java实现redis分布式锁的三种方式

    目录 一.引入原因 二.分布式锁实现过程中的问题 问题一:异常导致锁没有释放 问题二:获取锁与设置过期时间操作不是原子性的 问题三:锁过期之后被别的线程重新获取与释放 问题四:锁的释放不是原子性的 问题五:其他的问题? 三.具体实现 1. RedisTemplate 2. RedisLockRegistry 3. 使用redisson实现分布式锁 一.引入原因 在分布式服务中,常常有如定时任务.库存更新这样的场景. 在定时任务中,如果不使用quartz这样的分布式定时工具,只是简单的使用定时器来

  • 详解Redis分布式锁的原理与实现

    目录 前言 使用场景 为什么要使用分布式锁 如何使用分布式锁 流程图 分布式锁的状态 分布式锁的特点 分布式锁的实现方式(以redis分布式锁实现为例) 总结 前言 在单体应用中,如果我们对共享数据不进行加锁操作,会出现数据一致性问题,我们的解决办法通常是加锁.在分布式架构中,我们同样会遇到数据共享操作问题,此时,我们就需要分布式锁来解决问题,下面我们一起聊聊使用redis来实现分布式锁. 使用场景 库存超卖 比如 5个笔记本 A 看 准备买3个 B 买2个 C 4个 一下单 3+2+4 =9

  • Redis实现分布式锁方法详细

    目录 1. 单机数据一致性 2. 分布式数据一致性 3. Redis实现分布式锁 3.1 方式一 3.2 方式二(改进方式一) 3.3 方式三(改进方式二) 3.4 方式四(改进方式三) 3.5 方式五(改进方式四) 3.6 小结 在单体应用中,如果我们对共享数据不进行加锁操作,会出现数据一致性问题,我们的解决办法通常是加锁. 在分布式架构中,我们同样会遇到数据共享操作问题,本文章使用Redis来解决分布式架构中的数据一致性问题. 1. 单机数据一致性 单机数据一致性架构如下图所示:多个可客户访

  • redis实现分布式锁实例详解

    目录 1.业务场景引入 2.基础环境准备 2.1.准备库存数据库 2.2.创建SpringBoot工程,pom.xml中导入依赖,请注意版本. 2.3.application.properties配置文件 2.4.SpringBoot启动类 2.5.添加Redis的配置类 2.6.pojo层 2.7.mapper层 2.8.SpringBoot监听Web启动事件,加载商品数据到Redis中 3.Redis实现分布式锁 3.1分布式锁的实现类 3.2分布式锁的业务代码 4.分布式锁测试 总结 1.

  • 一文带你搞懂Redis分布式锁

    目录 1.分布式锁简介 2.setnx 3.Redis-分布式锁-阶段1 4.Redis-分布式锁-阶段2 5.Redis-分布式锁-阶段3 6.Redis-分布式锁-阶段4 7.Redis-分布式锁-阶段5 1.分布式锁简介 分布式锁是控制分布式系统不同进程共同访问共享资源的一种锁的实现.如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性. 业界流行的分布式锁实现,一般有这3种方式: 基于数据库实现的分布式锁 基于Redis实现的分布式锁 基于

  • 一文带你搞懂JS中六种For循环的使用

    目录 一.各个 for 介绍 1.for 2.for ... in 3.for ... of 4.for await...of 5.forEach 6.map 二.多个 for 之间区别 1.使用场景差异 2.功能差异 3.性能差异 三.for 的使用 for 循环在平时开发中使用频率最高的,前后端数据交互时,常见的数据类型就是数组和对象,处理对象和数组时经常使用到 for 遍历,因此下班前花费几分钟彻底搞懂这 5 种 for 循环.它们分别为: for for ... in for ... o

  • 一文带你搞懂Numpy中的深拷贝和浅拷贝

    目录 1. 引言 2. 浅拷贝 2.1 问题引入 2.2 问题剖析 3. 深拷贝 3.1 举个栗子 3.2 探究原因 4. 技巧总结 4.1 判断是否指向同一内存 4.2 其他数据类型 5. 总结 1. 引言 深拷贝和浅拷贝是Python中重要的概念,本文重点介绍在NumPy中深拷贝和浅拷贝相关操作的定义和背后的原理. 闲话少说,我们直接开始吧! 2. 浅拷贝 2.1 问题引入 我们来举个栗子,如下所示我们有两个数组a和b,样例代码如下: import numpy as np a = np.ar

  • 一文带你搞懂Maven的继承与聚合

    目录 一.继承 二.继承关系实施步骤 三.聚合与继承的区别 一.继承 我们已经完成了使用聚合工程去管理项目,聚合工程进行某一个构建操作,其他被其管理的项目也会 执行相同的构建操作.那么接下来,我们再来分析下,多模块开发存在的另外一个问题,重复配置的问题,我们先来看张图: ■ spring-webmvc.spring-jdbc在三个项目模块中都有出现,这样就出现了重复的内容 ■ spring-test只在ssm_crm和ssm_goods中出现,而在ssm_order中没有,这里是部分重复的内容

  • 一文带你搞懂Spring响应式编程

    目录 1. 前言 1.1 常用函数式编程 1.2 Stream操作 2. Java响应式编程 带有中间处理器的响应式流 3. Reactor 3.1 Flux & Mono 3.2 Flux Mono创建与使用 4. WebFlux Spring WebFlux示例 基于注解的WebFlux 基于函数式编程的WebFlux Flux与Mono的响应式编程延迟示例 总结 哈喽,大家好,我是指北君. 相信响应式编程经常会在各种地方被提到.本篇就为大家从函数式编程一直到Spring WeFlux做一次

  • 一文带你搞懂Java中的泛型和通配符

    目录 概述 泛型介绍和使用 泛型类 泛型方法 类型变量的限定 通配符使用 无边界通配符 通配符上界 通配符下界 概述 泛型机制在项目中一直都在使用,比如在集合中ArrayList<String, String>, Map<String,String>等,不仅如此,很多源码中都用到了泛型机制,所以深入学习了解泛型相关机制对于源码阅读以及自己代码编写有很大的帮助.但是里面很多的机制和特性一直没有明白,特别是通配符这块,对于通配符上界.下界每次用每次百度,经常忘记,这次我就做一个总结,加

  • 一文带你搞懂PHP对象注入

    目录 背景 漏洞案例 PHP类和对象 php magic方法 php对象序列化 序列化magic函数 php对象注入 常见的注入点 其他的利用方法 如何利用或者避免这个漏洞 结论 背景 php对象注入是一个非常常见的漏洞,这个类型的漏洞虽然有些难以利用,但仍旧非常危险,为了理解这个漏洞,请读者具备基础的php知识. 漏洞案例 如果你觉得这是个渣渣洞,那么请看一眼这个列表,一些被审计狗挖到过该漏洞的系统,你可以发现都是一些耳熟能详的玩意(就国外来说) WordPress 3.6.1 Magento

  • 一文带你搞懂Golang结构体内存布局

    目录 前言 结构体内存布局 结构体大小 内存对齐 总结 前言 结构体在Go语言中是一个很重要的部分,在项目中会经常用到,大家在写Go时有没有注意过,一个struct所占的空间不一定等于各个字段加起来的空间之和,甚至有时候把字段的顺序调整一下,struct的所占空间不一样,接下来通过这篇文章来看一下结构体在内存中是怎么分布的?通过对内存布局的了解,可以帮助我们写出更优质的代码.感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助. 结构体内存布局 结构体大小 结构体实际上就是由各种类型的数据组合而成

  • 一文带你搞懂Java中Get和Post的使用

    目录 1 Get请求数据 1.1 Controller 1.2 Service 1.3 Application 1.4 Postman 2 Post接收数据 2.1 Controller 2.2 Service 2.3 Application 2.4 Postman 3 Post发送数据 3.1 Controller 3.2 Service 3.3 ResponseResult 3.4 Config 3.5 Application 3.6 Postman 1 Get请求数据 项目地址:https

  • 一文带你搞懂Python中的文件操作

    目录 一.文件的编码 二.文件的读取 2.1 open()打开函数 2.2 mode常用的三种基础访问模式 2.3 读操作相关方法 三.文件的写入 写操作快速入门 四.文件的追加 追加写入操作快速入门 五.文件操作综合案例 一.文件的编码 计算机中有许多可用编码: UTF-8 GBK Big5 等 UTF-8是目前全球通用的编码格式 除非有特殊需求,否则,一律以UTF-8格式进行文件编码即可. 二.文件的读取 2.1 open()打开函数 注意:此时的f是open函数的文件对象,对象是Pytho

随机推荐