Redis分布式锁实现方式及超时问题解决

一 前言

redis在分布式应用十分广泛,本篇文章也是互联网面试的重点内容,读者至少需要知道为什么需要分布式锁,分布式锁的实现原理,分布式锁的应用场景,在使用分布式锁时遇到哪些问题?你是如何解决的,如果读者能掌握以上问题,那么关于这道面试题,你也就基本过关了;

二 分布式锁的产生背景

分布式锁对应的是多个应用,每个应用中都可能会处理相同的数据,如果多个应用对用一个操作进行了重复操作,就会出现数据不一致,数据重复问题,于是分布式锁应用而生,通常你可以理解为多线程中的synchronized

三 分布式锁的应用场景

多台机器都能执行某个任务,如果限制任务每次只能被一台机器执行,不能重复执行,就可以用分布式锁来做标记秒杀场景,要求并发量很高,那么同一件商品只能被一个用户抢到,就可以使用分布式锁实现比较敏感的数据比如金额修改,同一时间只能有一个人操作,如果2个人同时修改金额,一个加一个减金额,为了防止同时操作造成数据不一致,就可以使用分布式锁实现

四 分布式锁的实现

4.1 分布式锁的实现方式

  • 基于数据库实现分布式锁
  • 基于缓存(redis,memcached,tair)实现分布式锁
  • 基于Zookeeper实现分布式锁

4.2 分布式锁使用原理

每个应用对敏感数据进行操作时都需要向获取一个锁,持有锁的应用才能对数据进行操作,保证在同一时间内只有一台应用能对数据进行操作;

4.3 分布式锁实现过程

基本实现思路:

redis分布式实现是基于 命令setnx key value , 其意指 若该键不存在则创建键,这就保证了redis中只有一个该键,故应用谁先获得该键,谁就拿到了锁的权限;然后业务逻辑执行完毕则需要使用 del key 删除键,表示释放锁;

出现了问题:

如果一台业务逻辑执行完毕,程序出现异常,则锁会一直存在,没有得到释放,其它应用就会无法获得锁,此时就会造成死锁问题;

改进方式:

拿到锁之后,给锁加上一个过期时间,也就是 expire key seconds 指令;此时避免了死锁问题,但是由于业务逻辑执行的时间不同,过期的时间设置也是一个问题,故通常分布式锁不能应用于业务逻辑执行较长的程序;

出现问题:

由于redis 每条指令都是原子性操作,但由于setnx 和 expire 是2 条指令,如果在执行setnx后程序出现问题expire指令未得到执行就会造成死锁问题;

解决问题:

redis2.8版本之后引入了指令 set key value [EX seconds] [PX milliseconds] [NX|XX] ,该指令可以同时执行 setnx 和 expire ,于是解决了死锁问题;

参数列表解释

  • EX seconds: 设定过期时间,单位为秒
  • PX milliseconds: 设定过期时间,单位为毫秒
  • NX: key不存在时设置值
  • XX: key存在时设置值

使用jedis客户端实现分布式锁方式

public boolean lock(Jedis jedis,String key,String val,int expireTime){
  String lock = jedis.set(key, val, "NX", "PX",
      expireTime);
  return "OK".equals(lock);
}

关于未获得锁的解决思路:

可以直接抛出异常让客户重试

可以使用延迟队列

五 分布式锁的超时问题

问题:

如果在加锁和释放锁之间,业务逻辑执行时间太长,导致超出了锁的超时限制,就会出现锁过期问题;换句话说,就是第一台应用执行了业务,导致锁过期;第二台应用此时可以获得锁,进行执行业务,此时第一台应用释放了锁,第二台应用在执行业务的时第三台应用获得了锁执行业务,导致在执行过程中,会有2台应用在同时执行业务逻辑;

解决思路:

在释放锁的时候出现了问题,即每台应用都可以释放锁,这会造成1应用的锁释放了2应用锁的问题,换句话说,很多人手中持有的钥匙是通用的,都可以开同一个门;为了避免这个问题,就是1 应用只能释放1应用上的锁,2应用只能释放2应用上的锁,则需要对释放锁进行身份校验;由于上锁的时候key是唯一,但value可以不同,所以可以根据value进行身份的唯一标识,随机数就是一个很好的选择 :

String value = UUID.randomUUID().toString();

由于考虑到匹配到value校验和del不是同一个操作,故需要使用Lua脚本实现多条指令的原子性执行;

jedis释放锁实现方式:

public void unlock(Jedis jedis,String key,String value) {
    String script_command = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
        "return redis.call('del',KEYS[1]) else return 0 end";
    // 解锁
    jedis.eval(script_command, Collections.singletonList(key), Collections.singletonList(value));

  }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java Redis分布式锁的正确实现方式详解

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁. 可靠性 首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 互斥性.在任意时刻,只有一个客户端能持有锁. 不会发生死锁.即使有一个客户端在

  • SpringBoot使用Redisson实现分布式锁(秒杀系统)

    前面讲完了Redis的分布式锁的实现,接下来讲Redisson的分布式锁的实现,一般提及到Redis的分布式锁我们更多的使用的是Redisson的分布式锁,Redis的官方也是建议我们这样去做的.Redisson点我可以直接跳转到Redisson的官方文档. 1.1.引入Maven依赖 <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter&l

  • 浅谈Redis分布式锁的正确实现方式

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁. 可靠性 首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 1.互斥性.在任意时刻,只有一个客户端能持有锁. 2.不会发生死锁.即使有一个

  • SpringBoot集成Redisson实现分布式锁的方法示例

    上篇 <SpringBoot 集成 redis 分布式锁优化>对死锁的问题进行了优化,今天介绍的是 redis 官方推荐使用的 Redisson ,Redisson 架设在 redis 基础上的 Java 驻内存数据网格(In-Memory Data Grid),基于NIO的 Netty 框架上,利用了 redis 键值数据库.功能非常强大,解决了很多分布式架构中的问题. Github的wiki地址: https://github.com/redisson/redisson/wiki 官方文档

  • Java基于redis实现分布式锁代码实例

    为什么会有这个需求: 例如一个简单用户的操作,一个线程去修改用户状态,首先在在内存中读出用户的状态,然后在内存中进行修改,然后在存到数据库中.在单线程中,这是没有问题的.但是在多线程中由于读取,修改,写入是三个操作,不是原子操作(同时成功或失败),因此在多线程中会存在数据的安全性问题. 这个问题的话,就可以用分布式锁在限制程序的并发执行. 实现思路: 就是进来一个先占位,当别的线程进来操作的时候,发现有人占位了,就会放弃或者稍后再试. 占位的实现: 在redis中的setnx命令来实现,redi

  • Redis实现分布式锁和等待序列的方法示例

    在集群下,经常会因为同时处理发生资源争抢和并发问题,但是我们都知道同步锁 synchronized . cas . ReentrankLock 这些锁的作用范围都是 JVM ,说白了在集群下没啥用.这时我们就需要能在多台 JVM 之间决定执行顺序的锁了,现在分布式锁主要有 redis . Zookeeper 实现的,还有数据库的方式,不过性能太差,也就是需要一个第三方的监管. 背景 最近在做一个消费 Kafka 消息的时候发现,由于线上的消费者过多,经常会遇到,多个机器同时处理一个主键类型的数据

  • Redis分布式锁的实现方式(redis面试题)

    什么是分布式锁? 要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁.进程锁. 线程锁:主要用来给方法.代码块加锁.当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段.线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state). 进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronize

  • springboot+redis分布式锁实现模拟抢单

    本篇内容主要讲解的是redis分布式锁,这个在各大厂面试几乎都是必备的,下面结合模拟抢单的场景来使用她:本篇不涉及到的redis环境搭建,快速搭建个人测试环境,这里建议使用docker:本篇内容节点如下: jedis的nx生成锁 如何删除锁 模拟抢单动作(10w个人开抢) jedis的nx生成锁 对于java中想操作redis,好的方式是使用jedis,首先pom中引入依赖: <dependency> <groupId>redis.clients</groupId> &

  • 详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

    引言:最近回头看了看开发的.Net Core 2.1项目的复盘总结,其中在多处用到Redis实现的分布式锁,虽然在OnResultExecuting方法中做了防止死锁的处理,但在某些场景下还是会发生死锁的问题,下面我只展示部分代码: 问题: (1)这里setnx设置的值"1",我想问,你最后del的这个值一定是你自己创建的吗? (2)图中标注的步骤1和步骤2不是原子操作,会有死锁的概率吗? 大家可以思考一下先,下面让我们带着这两个问题往下看,下面介绍一下使用Redis实现分布式锁常用的

  • Redis分布式锁实现方式及超时问题解决

    一 前言 redis在分布式应用十分广泛,本篇文章也是互联网面试的重点内容,读者至少需要知道为什么需要分布式锁,分布式锁的实现原理,分布式锁的应用场景,在使用分布式锁时遇到哪些问题?你是如何解决的,如果读者能掌握以上问题,那么关于这道面试题,你也就基本过关了: 二 分布式锁的产生背景 分布式锁对应的是多个应用,每个应用中都可能会处理相同的数据,如果多个应用对用一个操作进行了重复操作,就会出现数据不一致,数据重复问题,于是分布式锁应用而生,通常你可以理解为多线程中的synchronized 三 分

  • redis分布式锁及会出现的问题解决

    一.redis实现分布式锁的主要原理: 1.加锁 最简单的方法是使用setnx命令.key是锁的唯一标识,按业务来决定命名.比如想要给一种商品的秒杀活动加锁,可以给key命名为 "lock_sale_商品ID" .而value设置成什么呢?我们可以姑且设置成1.加锁的伪代码如下: setnx(key,1) 当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁:当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败. 2.解锁 有加锁就得有解锁.当得到锁的

  • Go 语言下基于Redis分布式锁的实现方式

    分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁. 项目地址: https://github.com/Spongecaptain/redisLock 1. Go 原生的互斥锁 Go 原生的互斥锁即 sync 包下的 M

  • 如何使用注解方式实现 Redis 分布式锁

    目录 引入 Redisson 初始化 Redisson 编写 Redisson 分布式锁工具类 声明注解 @Lock 注解解析类 引入 Redisson <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.14.1</version> </depend

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

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

  • Redis分布式锁的实现方式

    目录 一.分布式锁是什么 1.获取锁 2.释放锁 二.代码实例 上面代码存在锁误删问题: 三.基于SETNX实现的分布式锁存在下面几个问题 1.不可重入 2.不可重试 3.超时释放 4.主从一致性 四.Redisson实现分布式锁 1.pom 2.配置类 3.测试类 五.探索tryLock源码 1.tryLock源码 尝试获取锁 2.重置锁的有效期 更新有效期 3.调用lua脚本 六.释放锁unlock源码 1.取消更新任务 2.删除定时任务 一.分布式锁是什么 分布式锁是 满足分布式系统或集群

  • java语言描述Redis分布式锁的正确实现方式

    分布式锁一般有三种实现方式:1.数据库乐观锁:2.基于Redis的分布式锁:3.基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁. 可靠性 首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 互斥性.在任意时刻,只有一个客户端能持有锁. 不会发生死锁.即使有一个客户端在持有锁的期间

随机推荐