Redis实现分布式锁详解

目录
  • 一、前言
    • 为什么需要分布式锁?
  • 二、基于redis实现分布式锁
    • 为什么redis可以实现分布式锁?
    • 如何实现?
      • 锁的获取
      • 锁的释放
  • 三、如何避免死锁?锁的过期时间如何设置?
    • 避免死锁
    • 锁过期处理
      • 释放其他服务的锁如何处理呢?
      • 那么redis宕机了呢?
  • 四、RedLock
    • 什么是RedLock?
    • 实现流程
    • 分布式系统中的NPC问题
    • 个人思考
  • 五、基于zookeeper实现分布式锁
    • 什么是zookeeper(zk)?
    • zookeeper节点介绍
    • zookeeper分布式锁的实现
  • 六、zookeepe和redisr两者的优缺
    • zookeeper
    • Redis
  • 七、那么实际的工作中,该如何选择呢?

一、前言

为什么需要分布式锁?

在我们的日常开发中,一个进程中当多线程的去竞争某一资源的时候,我们通常会用一把锁来保证只有一个线程获取到资源。如加上synchronize关键字或ReentrantLock锁等操作。

那么,如果是多个进程相互竞争一个资源,如何保证资源只会被一个操作者持有呢?

例如:微服务的架构下,多个应用服务要同时对同一条数据做修改,那么要确保数据的正确性,就只能有一个应用修改成功。

server1、server2、server3 这三个服务都要修改amount这个数据,每个服务更新的值不同,为了保证数据的正确性,三个服务都向lock server服务申请修改权限,最终server2拿到了修改权限,即server2将amount更新为2,其他服务由于没有获取到修改权限则返回更新失败。

二、基于redis实现分布式锁

为什么redis可以实现分布式锁?

因为redis是一个单独的非业务服务,不会受到其他业务服务的限制,所有的业务服务都可以向redis发送写入命令,且只有一个业务服务可以写入命令成功,那么这个写入命令成功的服务即获得了锁,可以进行后续对资源的操作,其他未写入成功的服务,则进行其他处理。

如何实现?

redis的String类型就可以实现。

锁的获取

setnx命令:表示SET if Not eXists,即如果 key 不存在,才会设置它的值,否则什么也不做。

两个客户端同时向redis写入try_lock,客户端1写入成功,即获取分布式锁成功。客户端2写入失败,则获取分布式锁失败。

锁的释放

当客户端1操作完后,释放锁资源,即删除try_lock。那么此时客户端2再次尝试获取锁时,则会获取锁成功。

那么这样分布式锁就这样结束了?不不不,现实往往有很多情况出现。

假如客户端1在获取到锁资源后,服务宕机了,那么这个try_lock会一直存在redis中,那么其他服务就永远无法获取到锁了。

如何解决这个问题呢?

三、如何避免死锁?锁的过期时间如何设置?

避免死锁

设置键过期时间,超过这个时间即给key删除掉。

这样的话,就算当前服务获取到锁后宕机了,这个key也会在一定时间后被删除,其他服务照样可以继续获取锁。

给serverLock键设置一个10秒的过期时间,10秒后会自动删除该键。

这样虽然解决了上面说的问题,但是又会引入新的问题。

假如服务A加锁成功,锁会在10s后自动释放,但由于业务复杂,执行时间过长,10s内还没执行完,此时锁已经被redis自动释放掉了。此时服务B就重新获取到了该锁,服务B开始执行他的业务,服务A在执行到第12s的时候执行完了,那么服务A会去释放锁,则此时释放的却是服务B刚获取到的锁。

这会有锁过期和释放其他服务锁这种严重的问题。

锁过期处理

那么锁过期这种问题该如何处理的?

虽然可以通过增加删除key时间来处理这个问题,但是并没有从根本上解决。假设设个100s,绝大多数都是1s后就会释放锁,但是由于服务宕机,则会导致100s内其他服务都无法获取到锁,这也是灾难性的。

我们可以这样做,在锁将要过期的时候,如果服务还没有处理完业务,那么将这个锁再续一段时间。比如设置key在10s后过期,那么再开启一个守护线程,在第8s的时候检测服务是否处理完,如果没有,则将这个key再续10s后过期。

在Redisson(Redis SDK客户端)中,就已经帮我们实现了这个功能,这个自动续时的我们称其为”看门狗”。

释放其他服务的锁如何处理呢?

每个服务在设置value的时候,带上自己服务的唯一标识,如UUID,或者一些业务上的独特标识。这样在删除key的时候,只删除自己服务之前添加的key就可以了。

如果需要先查看锁是否是自己服务添加的,需要先get取出来判断,然后再进行del。这样的话就无法保证原子性了。

我们可以通过Lua脚本,将这两个操作合并成一个操作,就可以保证其原子性了。

Lua脚本的话,我也不会,用到的时候百度就完了。

如果是在单redis实例的情况下,上面的已经完全实现了分布式锁的功能了。

那么redis宕机了呢?

这个时候就得引入redis集群了。

但是涉及到redis集群,就会有新的问题出现,假设是主从集群,且主从数据并不是强一致性。当主节点宕机后,主节点的数据还未来得及同步到从节点,进行主从切换后,新的主节点并没有老的主节点的全部数据,这就会导致刚写入到老的主节点的锁在新的主节点并没有,其他服务来获取锁时还是会加锁成功。此时则会有2个服务都可以操作公共资源,此时的分布式锁则是不安全的。

redis的作者也想到这个问题,于是他发明了RedLock。

四、RedLock

什么是RedLock?

要实现RedLock,需要至少5个实例(官方推荐),且每个实例都是master,不需要从库和哨兵。

实现流程

1、客户端先获取当前时间戳T1

2、客户端依次向5个master实例发起加锁命令,且每个请求都会设置超时时间(毫秒级,注意:不是锁的超时时间),如果某一个master实例由于网络等原因导致加锁失败,则立即想下一个master实例申请加锁。

3、当客户端加锁成功的请求大于等于3个时,且再次获取当前时间戳T2,

当时间戳T2 - 时间戳T1 < 锁的过期时间。则客户端加锁成功,否则失败。

4、加锁成功,开始操作公共资源,进行后续业务操作

5、加锁失败,向所有redis节点发送锁释放命令

即当客户端在大多数redis实例上申请加锁成功后,且加锁总耗时小于锁过期时间,则认为加锁成功。

释放锁需要向全部节点发送锁释放命令。

第3步为啥要计算申请锁前后的总耗时与锁释放时间进行对比呢?

因为如果申请锁的总耗时已经超过了锁释放时间,那么可能前面申请redis的锁已经被释放掉了,保证不了大于等于3个实例都有锁存在了,锁也就没有意义了

这样的话分布式锁就真的没问题了嘛?

1、得5个redis实例,成本大大增加

2、可以通过上面的流程感受到,这个RedLock锁太重了

3、主从切换这种场景绝大多数的时候不会碰到,偶尔碰到的话,保证最终的兜底操作我觉得也没啥问题。

4、分布式系统中的NPC问题

分布式系统中的NPC问题

(可不是游戏里的NPC提问哦)

N:Network Delay,网络延迟

P:Process Pause,进程暂停(GC)

C:Clock Drift,时钟漂移

举个例子吧:

1、客户端 1 请求锁定节点 A、B、C、D、E

2、客户端 1 的拿到锁后,进入 GC(时间比较久)

3、所有 Redis 节点上的锁都过期了

4、客户端 2 获取到了 A、B、C、D、E 上的锁

5、客户端 1 GC 结束,认为成功获取锁

6、客户端 2 也认为获取到了锁,发生【冲突】

在第2步已经成功获取到锁后,由于GC时间超过锁过期时间,导致GC完成后其他客户端也能够获取到锁,此时2个客户端都会持有锁。就会有问题。

这个问题无论是redlock还是zookeeper都会有这种问题。不做业务上的兜底操作就没得解。

时钟漂移问题也只能是尽量避免吧。无法做到根本解决。

个人思考

用RedLock觉得性价比很低。原因如下

1、得额外的多台服务器部署redis,每台服务器可都是钱啊,而且部署和运维的成本也增加了。

2、用RedLock感觉太重了,效率会很低,既然用了redis,就是为了提升效率,结果一个锁大大降低了效率

3、如果在集群情况下有锁丢失的情况,我们业务上做好兜底操作就可以了,可以不用上RedLock。

4、毕竟集群情况下主从切换的场景还是极少的,为了极少的情况去浪费大量的性能,感觉划不来

5、就算是上了RedLock,也是避免不了NPC问题的,还不是得业务上做兜底。

聊了这么多的redis实现分布式锁。也简单了解下zookeeper是如何实现分布式锁的吧。

五、基于zookeeper实现分布式锁

什么是zookeeper(zk)?

zk是一个分布式协调服务,功能包括:配置维护、域名服务、分布式同步、组服务等。

zk的数据结构跟Unix文件系统类似。是一颗树形结构,这里不做详细介绍。

zookeeper节点介绍

zk的节点称之为znode节点,znode节点分两种类型:

1、临时节点(Ephemeral):当客户端与服务器断开连接后,临时znode节点就会被自动删除

2、持久节点(Persistent):当客户端与服务器断开连接后,持久znode节点不会被自动删除

znode节点还有一些特性:

1、节点有序:在一个父节点下创建子节点,zk提供了一个可选的有序性,创建子节点时会根据当前子节点数量给节点名添加序号。例:/root下创建/java,生成的节点名称则为java0001,/root/java0001。

2、临时节点:当会话结束或超时,自动删除节点

3、事件监听:当节点有创建,删除,数据修改,子节点变更的时候,zk会通知客户端的。

zookeeper分布式锁的实现

zookeeper就是通过临时节点和节点有序来实现分布式锁的。

1、每个获取锁的线程会在zk的某一个目录下创建一个临时有序的节点。

2、节点创建成功后,判断当前线程创建的节点的序号是否是最小的。

3、如果序号是最小的,那么获取锁成功。

4、如果序号不是最小的,则对他序号的前一个节点添加事件监听。如果前一个节点被删了(锁被释放了),那么就会唤醒当前节点,则成功获取到锁。

六、zookeepe和redisr两者的优缺

zookeeper

优点:

1、不用设置过期时间

2、事件监听机制,加锁失败后,可以等待锁释放

缺点:

1、性能不如redis

2、当网络不稳定时,可能会有多个节点同时获取锁问题。例:node1由于网络波动,导致zk将其删除,刚好node2获取到锁,那么此时node1和node2两者都会获取到锁。

Redis

优点:性能上比较好,天然的支持高并发

缺点:

1、获取锁失败后,得轮询的去获取锁

2、大多数情况下redis无法保证数据强一致性

七、那么实际的工作中,该如何选择呢?

比如我来说,很简单,没得选,就Redis,为啥?因为公司没有用zk。

具体如何选择,还是得看公司是否有使用相应的中间件。

如果两种公司都有使用,那就具体的看业务场景了,看是基于性能考虑还是其他方面的考虑。

如果用redis的话,个人觉得没必要上RedLock,感觉性价比太低。

但是要注意的是,无论哪一种,在极端的情况下,都会有锁失效或锁冲突的情况出现,因此业务上,设计上要有兜底的方案,不要造成不必要的损失。

本文中没有通过代码来实现分布式锁,只是提供了方向和思路,以及要注意的地方。至于具体如何通过代码实现,Java的话有Redisson封装好了大部分功能,使用起来也比较简单,大家可以参考相应的文档即可。

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

(0)

相关推荐

  • Redisson如何解决redis分布式锁过期时间到了业务没执行完问题

    目录 面试问题 问题分析 如何回答 一.写在前面 二.Redisson实现Redis分布式锁的底层原理 (1)加锁机制 (2)锁互斥机制 (3)watch dog自动延期机制 (4)可重入加锁机制 (5)释放锁机制 (6)上述Redis分布式锁的缺点 总结 面试问题 Redis锁的过期时间小于业务的执行时间该如何续期? 问题分析 首先如果你之前用Redis的分布式锁的姿势正确,并且看过相应的官方文档的话,这个问题So easy.我们来看 很多同学在用分布式锁时,都是直接百度搜索找一个Redis分

  • Redis分布式锁的实现方式

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

  • Redisson实现分布式锁、锁续约的案例

    目录 一.基础 0)Redisson版本说明.案例 1)Redisson连接Redis的方式 2)用到的Redis命令 3)用到的lua脚本语义 二.源码分析 1.RLock 2.加锁流程 3.释放锁流程 一.基础 0)Redisson版本说明.案例 使用当前(2022年12月初)最新的版本:3.18.1: <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifa

  • Redisson分布式锁之加解锁详解

    目录 引言 锁的可重入性 加锁 锁续命 释放锁 引言 2023的金三银四来的没想象中那么激烈,一个朋友前段时间投了几十家,多数石沉大海,好不容易等来面试机会,就恰好被问道项目中关于分布式锁的应用,后涉及Redisson实现分布式锁的原理,答不上来. 锁的可重入性 我们都知道,Java中synchronized和lock都支持可重入,synchronized的锁关联一个线程持有者和一个计数器.当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1.此时其他线程请求该锁,则必须等待.而该持

  • SpringBoot整合分布式锁redisson的示例代码

    目录 1.导入maven坐标 2.redisson配置类(如果redis没有密码就不需要private String password) 3.创建redisson的bean 4.测试,入队 5.测试,出队 6.分布式锁 1.导入maven坐标 <!-- 用redisson作为所有分布式锁,分布式对象等功能框架--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson&

  • Redis实现分布式锁详解

    目录 一.前言 为什么需要分布式锁? 二.基于redis实现分布式锁 为什么redis可以实现分布式锁? 如何实现? 锁的获取 锁的释放 三.如何避免死锁?锁的过期时间如何设置? 避免死锁 锁过期处理 释放其他服务的锁如何处理呢? 那么redis宕机了呢? 四.RedLock 什么是RedLock? 实现流程 分布式系统中的NPC问题 个人思考 五.基于zookeeper实现分布式锁 什么是zookeeper(zk)? zookeeper节点介绍 zookeeper分布式锁的实现 六.zooke

  • Redis如何实现分布式锁详解

    一.前言 在Java的并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题.通常,我们以synchronized .Lock来使用它. 但是Java中的锁,只能保证在同一个JVM进程内中执行.如果在分布式集群环境下,就需要分布式锁了. 通常的分布式锁的实现方式有redis,zookeeper,但是一般我们的程序中都会用到redis,用redis做分布式锁,也能够降低成本. 二.实现原理 2.1 加锁 加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间. 在

  • 基于Zookeeper实现分布式锁详解

    目录 1.什么是Zookeeper? 2.Zookeeper节点类型 3.Zookeeper环境搭建 4.Zookeeper基本使用 5.Zookeeper应用场景 6.Zookeeper分布式锁 7.公平式Zookeeper分布式锁 8.zookeeper和Redis锁对比? 1.什么是Zookeeper? Zookeeper是一个分布式的,开源的分布式应用程序协调服务,是Hadoop和hbase的重要组件. 引用官网的图例: 特征: zookeeper的数据机构是一种节点树的数据结构,zNo

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

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

  • 详解基于redis实现分布式锁

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

  • Redis实现分布式锁的五种方法详解

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

  • 一文详解如何使用Redis实现分布式锁

    目录 1. 什么是分布式锁 2. 使用Redis实现分布式锁 2.1 加锁 2.2 释放锁 2.3 给锁设置有效期 2.4 给锁设置唯一值 2.5 通过LUA脚本实现释放锁的原子性 3. 小结 1. 什么是分布式锁 当我们在编写多线程代码的时候,不同的线程可能会发生资源的争夺,为了避免资源争夺造成的错误,我们会对资源上锁,只有获得锁的线程才能继续往下执行. 进程中的锁,本质就是内存中一个变量,当一个线程执行某个操作申请加锁时,如果能成功把代表锁的变量值设置为1,则表示获得了锁,其他线程想要获得锁

  • 详解Java如何实现基于Redis的分布式锁

    前言 单JVM内同步好办, 直接用JDK提供的锁就可以了,但是跨进程同步靠这个肯定是不可能的,这种情况下肯定要借助第三方,我这里实现用Redis,当然还有很多其他的实现方式.其实基于Redis实现的原理还算比较简单的,在看代码之前建议大家先去看看原理,看懂了之后看代码应该就容易理解了. 我这里不实现JDK的java.util.concurrent.locks.Lock接口,而是自定义一个,因为JDK的有个newCondition方法我这里暂时没实现.这个Lock提供了5个lock方法的变体,可以

  • 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深入浅出分布式锁实现下篇

    目录 优化之UUID防误删 项目中正确使用 总结 优化之UUID防误删 问题:删除操作缺乏原子性. 场景: index1执行删除时,查询到的lock值确实和uuid相等 uuid=v1 set(lock,uuid): index1执行删除前,lock刚好过期时间已到,被redis自动释放,在redis中没有了lock,没有了锁. index2获取了lock index2线程获取到了cpu的资源,开始执行方法 uuid=v2 set(lock,uuid): index1执行删除,此时会把index

随机推荐