详解redis分布式锁的这些坑

一、白话分布式

什么是分布式,用最简单的话来说,就是为了较低单个服务器的压力,将功能分布在不同的机器上面,本来一个程序员可以完成一个项目:需求->设计->编码->测试

但是项目多的时候,一个人也扛不住,这就需要不同的人进行分工合作了

这就是一个简单的分布式协同工作了;

二、分布式锁

首先看一个问题,如果说某个环节被终止或者别侵占,就会发生不可知的事情

这就会出现,设计好的或者设计的半成品会被破坏,导致后面环节出错;

这时候,我们就需要引入分布式锁的概念;

何为分布式锁

当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。

用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

分布式锁的条件

  • 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。
  • 这把锁要是一把可重入锁(避免死锁)
  • 这把锁最好是一把阻塞锁
  • 这把锁最好是一把公平锁
  • 有高可用的获取锁和释放锁功能
  • 获取锁和释放锁的性能要好

分布式锁的实现

分布式锁的实现由很多种,文件锁、数据库、redis等等,比较多,在实践中,还是redis做分布式锁性能会高一些;

三、redis实现分布式锁

首先看两个命令:

setnx:将 key 的值设为 value,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。 SETNX 是SET if Not eXists的简写。

127.0.0.1:6379> set lock "unlock"
OK
127.0.0.1:6379> setnx lock "unlock"
(integer) 0
127.0.0.1:6379> setnx lock "lock"
(integer) 0
127.0.0.1:6379>

expire:EXPIRE key seconds

为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除

127.0.0.1:6379> expire lock 10
(integer) 1
127.0.0.1:6379> ttl lock
8
127.0.0.1:6379> get lock
(nil)

基于分布式锁的流程:

这就是一个简单的分布式锁的实现流程,具体代码实现也很简单,就不赘述了;

四、redis实现分布式锁问题

如果出现了这么一个问题:如果setnx是成功的,但是expire设置失败,那么后面如果出现了释放锁失败的问题,那么这个锁永远也不会被得到,业务将被锁死?

解决的办法:使用set的命令,同时设置锁和过期时间

set参数:

set key value [EX seconds] [PX milliseconds] [NX|XX]

EX seconds:设置失效时长,单位秒

PX milliseconds:设置失效时长,单位毫秒

NX:key不存在时设置value,成功返回OK,失败返回(nil)

XX:key存在时设置value,成功返回OK,失败返回(nil)

实践:

127.0.0.1:6379> set unlock "234" EX 100 NX
(nil)
127.0.0.1:6379>
127.0.0.1:6379> set test "111" EX 100 NX
OK

这样就完美的解决了分布式锁的原子性。

五、用锁遇到过哪些问题?又是如何解决的?未关闭资源

由于当前线程 获取到redis 锁,处理完业务后未及时释放锁,导致其它线程会一直尝试获取锁阻塞,例如:用Jedis客户端会报如下的错误信息

1redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

redis线程池已经没有空闲线程来处理客户端命令。使用原生方法记得关闭!

解决的方法也很简单,只要我们细心一点,拿到锁的线程处理完业务及时释放锁

B的锁被A给释放了

我们知道Redis实现锁的原理在于 SETNX命令。当 key不存在时将 key的值设为 value ,返回值为 1;若给定的 key已经存在,则 SETNX不做任何动作,返回值为 0 。

SETNX key value

我们来设想一下这个场景:A、B两个线程来尝试给key myLock加锁,A线程先拿到锁(假如锁3秒后过期),B线程就在等待尝试获取锁,到这一点毛病没有。

那如果此时业务逻辑比较耗时,执行时间已经超过redis锁过期时间,这时A线程的锁自动释放(删除key),B线程检测到myLock这个key不存在,执行 SETNX命令也拿到了锁。

但是,此时A线程执行完业务逻辑之后,还是会去释放锁(删除key),这就导致B线程的锁被A线程给释放了。

为避免上边的情况,一般我们在每个线程加锁时要带上自己独有的value值来标识,只释放指定value的key,否则就会出现释放锁混乱的场景

一般我们可以设置value为业务前缀_当前线程ID或者uuid,只有当前value相同的才可以释放锁

锁过期了,业务还没执行完

redis分布式锁过期,而业务逻辑没执行完的场景,不过,这里换一种思路想问题,把redis锁的过期时间再弄长点不就解决了吗?

那还是有问题,我们可以在加锁的时候,手动调长redis锁的过期时间,可这个时间多长合适?业务逻辑的执行时间是不可控的,调的过长又会影响操作性能。

要是redis锁的过期时间能够自动续期就好了。

为了解决这个问题我们使用redis客户端redisson,redisson很好的解决了redis在分布式环境下的一些棘手问题,它的宗旨就是让使用者减少对Redis的关注,将更多精力用在处理业务逻辑上。

redisson对分布式锁做了很好封装,只需调用API即可。

RLock lock = redissonClient.getLock("stockLock");

redisson在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒。这个机制也被叫做:“看门狗”

redis主从复制的坑

redis高可用最常见的方案就是主从复制(master-slave),这种模式也给redis分布式锁挖了一坑。

redis cluster集群环境下,假如现在A客户端想要加锁,它会根据路由规则选择一台master节点写入key mylock,在加锁成功后,master节点会把key异步复制给对应的slave节点。

如果此时redis master节点宕机从节点复制失败,为保证集群可用性,会进行主备切换,slave变为了redis master。B客户端在新的master节点上加锁成功,而A客户端也以为自己还是成功加了锁的。另外如果主从复制延迟同样也会造成加锁和解锁延迟的问题。

此时就会导致同一时间内多个客户端对一个分布式锁完成了加锁,导致各种脏数据的产生。

毕竟redis是保持的AP而非CP,如果要追求强一致性可以使用zookeeper分布式锁。

以上就是详解redis分布式锁的这些坑的详细内容,更多关于redis分布式锁的这些坑的资料请关注我们其它相关文章!

(0)

相关推荐

  • php基于redis的分布式锁实例详解

    在使用分布式锁进行互斥资源访问时候,我们很多方案是采用redis的实现. 固然,redis的单节点锁在极端情况也是有问题的,假设你的业务允许偶尔的失效,使用单节点的redis锁方案就足够了,简单而且效率高. redis锁失效的情况: 客户端1从master节点获取了锁 master宕机了,存储锁的key还没来得及同步到slave节点上 slave升级为master 客户端2从新的master上获取到同一个资源的锁 于是,客户端1和客户端2同事持有了同一个资源的锁,锁的安全性被打破. 如果我们不考

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

    一.Redis分布式锁概念篇 建议直接采用Redis的官方推荐的Redisson作为redis的分布式锁 1.1.为什么要使用分布式锁 我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的Java多线程的18般武艺进行处理,并且可以完美的运行,毫无Bug! 注意这是单机应用,也就是所有的请求都会分配到当前服务器的JVM内部,然后映射为操作系统的线程进行处理!而这个共享变量只是在这个JVM内部的一块内存空间! 后来业务发展,需要做集群,一个应用需要部署到几台机

  • 详解RedisTemplate下Redis分布式锁引发的系列问题

    自己的项目因为会一直抓取某些信息,但是本地会和线上经常一起跑,造成冲突.这其实就是我们常说的分布式集群的问题了,本地和线上的服务器构成了集群以及QPS为2的小并发(其实也不叫并发,不知道拿什么词形容?). 首先,分布式集群的问题大家都知道,会造成数据库的插入重复问题,会造成一系列的并发性问题. 解决的方式呢也大概如下几点,百度以及谷歌上都能搜到的解决方式: 1:数据库添加唯一索引 2:设计接口幂等性 3:依靠中间件使用分布式锁,而分布式锁又分为Redis和Zookeeper 由于Zookeepe

  • Java基于redis实现分布式锁

    为了保证一个在高并发存场景下只能被同一个线程操作,java并发处理提供ReentrantLock或Synchronized进行互斥控制.但是这仅仅对单机环境有效.我们实现分布式锁大概通过三种方式. redis实现分布式锁 数据库实现分布式锁 zk实现分布式锁 实际上这三种和java对比看属于一类.都是属于程序外部锁. 原理剖析 上述三种分布式锁都是通过各自为依据对各个请求进行上锁,解锁从而控制放行还是拒绝.redis锁是基于其提供的setnx命令. setnx当且仅当key不存在.若给定key已

  • 详解Redis 分布式锁遇到的序列化问题

    场景描述 最近使用 Redis 遇到了一个类似分布式锁的场景,跟 Redis 实现分布式锁类比一下,就是释放锁失败,也就是缓存删不掉.又踩了一个 Redis 的坑-- 这是什么个情况.又是怎样排查的呢? 本文主要对此做个复盘. 问题排查 既然是释放锁有问题,那就先看看释放锁的代码吧. 释放锁 释放锁使用了 Lua 脚本,代码逻辑和 Lua 脚本如下: 释放锁示例代码 public Object release(String key, String value) { Object existedV

  • Redis分布式锁升级版RedLock及SpringBoot实现方法

    分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问,Java中我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的方式.但是现在公司都是流行分布式架构,在分布式环境下,如何保证不同节点的线程同步执行呢?因此就引出了分布式锁,它是控制分布式系统之间互斥访问共享资源的一种方式. 在一个分布式系统中,多台机器上部署了多个服务,当客户端一个用户发起一个数据插入请求时,如果没有分布式锁机制保证,那么那多台机器上的多个服务可能进行并发插

  • redisson分布式锁的用法大全

    Redisson是Redis官方推荐的Java版的Redis客户端.它提供的功能非常多,此处我们只用它的分布式锁功能. 以springboot整合Redisson项目为例 添加springboot maven依赖 <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.15

  • 基于Redis实现分布式锁的方法(lua脚本版)

    1.前言 在Java中,我们通过锁来避免由于竞争而造成的数据不一致问题.通常我们使用synchronized .Lock来实现.但是Java中的锁只能保证在同一个JVM进程内中可用,在跨JVM进程,例如分布式系统上则不可靠了. 2.分布式锁 分布式锁,是一种思想,它的实现方式有很多,如基于数据库实现.基于缓存(Redis等)实现.基于Zookeeper实现等等.为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件 互斥性:在任意时刻,只有一个客户端能持有锁. 不会发生死锁:即使客户端

  • 利用redis实现分布式锁,快速解决高并发时的线程安全问题

    实际工作中,经常会遇到多线程并发时的类似抢购的功能,本篇描述一个简单的redis分布式锁实现的多线程抢票功能. 直接上代码.首先按照慣例,给出一个错误的示范: 我们可以看看,当20个线程一起来抢10张票的时候,会发生什么事. package com.tiger.utils; public class TestMutilThread { // 总票量 public static int count = 10; public static void main(String[] args) { sta

  • 详解redis分布式锁的这些坑

    一.白话分布式 什么是分布式,用最简单的话来说,就是为了较低单个服务器的压力,将功能分布在不同的机器上面,本来一个程序员可以完成一个项目:需求->设计->编码->测试 但是项目多的时候,一个人也扛不住,这就需要不同的人进行分工合作了 这就是一个简单的分布式协同工作了: 二.分布式锁 首先看一个问题,如果说某个环节被终止或者别侵占,就会发生不可知的事情 这就会出现,设计好的或者设计的半成品会被破坏,导致后面环节出错: 这时候,我们就需要引入分布式锁的概念: 何为分布式锁 当在分布式模型下,

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

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

  • 详解redis分布式锁(优化redis分布式锁的过程及Redisson使用)

    目录 1. redis在实际的应用中 2.如何使用redis的功能进行实现分布式锁 2.1 redis分布式锁思想 2.1.1设计思想: 2.1.2 根据上面的设计思想进行代码实现 2.2 使用redisson进行实现分布式锁 1. redis在实际的应用中 不仅可以用来缓存数据,在分布式应用开发中,经常被用来当作分布式锁的使用,为什么要用到分布式锁呢? 在分布式的开发中,以电商库存的更新功能进行讲解,在实际的应用中相同功能的消费者是有多个的,假如多个消费者同一时刻要去消费一条数据,假如业务逻辑

  • redis分布式锁的8大坑总结梳理

    目录 前言 1 非原子操作 2 忘了释放锁 3 释放了别人的锁 4 大量失败请求 5 锁重入问题 6 锁竞争问题 6.1 读写锁 6.2 锁分段 7 锁超时问题 8 主从复制的问题 前言 在分布式系统中,由于redis分布式锁相对于更简单和高效,成为了分布式锁的首先,被我们用到了很多实际业务场景当中. 但不是说用了redis分布式锁,就可以高枕无忧了,如果没有用好或者用对,也会引来一些意想不到的问题. 今天我们就一起聊聊redis分布式锁的一些坑,给有需要的朋友一个参考. 1 非原子操作 使用r

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

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

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

    首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 1.互斥性.在任意时刻,只有一个客户端能持有锁. 2.不会发生死锁.即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁. 3.具有容错性.只要大部分的Redis节点正常运行,客户端就可以加锁和解锁. 4.解铃还须系铃人.加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了. 下边是代码实现,首先我们要通过Maven引入Jedis开源组件,在pom.xml文件加入下面的代码: <depe

  • Redis分布式锁的使用和实现原理详解

    模拟一个电商里面下单减库存的场景. 1.首先在redis里加入商品库存数量. 2.新建一个Spring Boot项目,在pom里面引入相关的依赖. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <gr

  • redis分布式锁的go-redis实现方法详解

    在分布式的业务中 , 如果有的共享资源需要安全的被访问和处理 , 那就需要分布式锁 分布式锁的几个原则; 1.「锁的互斥性」:在分布式集群应用中,共享资源的锁在同一时间只能被一个对象获取. 2. 「可重入」:为了避免死锁,这把锁是可以重入的,并且可以设置超时. 3. 「高效的加锁和解锁」:能够高效的加锁和解锁,获取锁和释放锁的性能也好. 4. 「阻塞.公平」:可以根据业务的需要,考虑是使用阻塞.还是非阻塞,公平还是非公平的锁. redis实现分布式锁主要靠setnx命令 1. 当key存在时失败

随机推荐