iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > html >如何深入理解Redis分布式锁
  • 104
分享到

如何深入理解Redis分布式锁

2024-04-02 19:04:59 104人浏览 八月长安
摘要

这篇文章主要介绍“如何深入理解Redis分布式锁”,在日常操作中,相信很多人在如何深入理解Redis分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何深入理解Redi

这篇文章主要介绍“如何深入理解Redis分布式”,在日常操作中,相信很多人在如何深入理解Redis分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何深入理解Redis分布式锁”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

 什么是分布式锁

说到Redis,我们第一想到的功能就是可以缓存数据,除此之外,Redis因为单进程、性能高的特点,它还经常被用于做分布式锁。

锁我们都知道,在程序中的作用就是同步工具,保证共享资源在同一时刻只能被一个线程访问,Java中的锁我们都很熟悉了,像synchronized  、Lock都是我们经常使用的,但是Java的锁只能保证单机的时候有效,分布式集群环境就无能为力了,这个时候我们就需要用到分布式锁。

分布式锁,顾名思义,就是分布式项目开发中用到的锁,可以用来控制分布式系统之间同步访问共享资源,一般来说,分布式锁需要满足的特性有这么几点:

1、互斥性:在任何时刻,对于同一条数据,只有一台应用可以获取到分布式锁;

2、高可用性:在分布式场景下,一小部分服务器宕机不影响正常使用,这种情况就需要将提供分布式锁的服务以集群的方式部署;

3、防止锁超时:如果客户端没有主动释放锁,服务器会在一段时间之后自动释放锁,防止客户端宕机或者网络不可达时产生死锁;

4、独占性:加锁解锁必须由同一台服务器进行,也就是锁的持有者才可以释放锁,不能出现你加的锁,别人给你解锁了;

业界里可以实现分布式锁效果的工具很多,但操作无非这么几个:加锁、解锁、防止锁超时。

既然本文说的是Redis分布式锁,那我们理所当然就以Redis的知识点来延伸。

实现锁的命令

先介绍下Redis的几个命令,

1、SETNX,用法是SETNX key value

SETNX是『 SET if Not eXists』(如果不存在,则 SET)的简写,设置成功就返回1,否则返回0。

如何深入理解Redis分布式锁

setnx用法

可以看出,当把key为lock的值设置为"Java"后,再设置成别的值就会失败,看上去很简单,也好像独占了锁,但有个致命的问题,就是key没有过期时间,这样一来,除非手动删除key或者获取锁后设置过期时间,不然其他线程永远拿不到锁。

既然这样,我们给key加个过期时间总可以吧,直接让线程获取锁的时候执行两步操作:

SETNX Key 1 EXPIRE Key Seconds

这个方案也有问题,因为获取锁和设置过期时间分成两步了,不是原子性操作,有可能获取锁成功但设置时间失败,那样不就白干了吗。

不过也不用急,这种事情Redis官方早为我们考虑到了,所以就引出了下面这个命令

2、SETEX,用法SETEX key seconds value

将值 value 关联到 key ,并将 key 的生存时间设为 seconds (以秒为单位)。如果 key 已经存在,SETEX  命令将覆写旧值。

这个命令类似于以下两个命令:

SET key value EXPIRE key seconds  # 设置生存时间

这两步动作是原子性的,会在同一时间完成。

如何深入理解Redis分布式锁

setex用法

3、PSETEX ,用法PSETEX key milliseconds value

这个命令和SETEX命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像SETEX命令那样,以秒为单位。

不过,从Redis 2.6.12 版本开始,SET命令可以通过参数来实现和SETNX、SETEX、PSETEX 三个命令相同的效果。

就比如这条命令

SET key value NX EX seconds

加上NX、EX参数后,效果就相当于SETEX,这也是Redis获取锁写法里面最常见的。

怎么释放锁

释放锁的命令就简单了,直接删除key就行,但我们前面说了,因为分布式锁必须由锁的持有者自己释放,所以我们必须先确保当前释放锁的线程是持有者,没问题了再删除,这样一来,就变成两个步骤了,似乎又违背了原子性了,怎么办呢?

不慌,我们可以用lua脚本把两步操作做拼装,就好像这样:

if redis.call("get",KEYS[1]) == ARGV[1] then     return redis.call("del",KEYS[1]) else     return 0 end

KEYS[1]是当前key的名称,ARGV[1]可以是当前线程的ID(或者其他不固定的值,能识别所属线程即可),这样就可以防止持有过期锁的线程,或者其他线程误删现有锁的情况出现。

代码实现

知道了原理后,我们就可以手写代码来实现Redis分布式锁的功能了,因为本文的目的主要是为了讲解原理,不是为了教大家怎么写分布式锁,所以我就用伪代码实现了。

首先是redis锁的工具类,包含了加锁和解锁的基础方法:

public class RedisLockUtil {      private String LOCK_KEY = "redis_lock";      // key的持有时间,5ms     private long EXPIRE_TIME = 5;      // 等待超时时间,1s     private long TIME_OUT = 1000;      // redis命令参数,相当于nx和px的命令合集     private SetParams params = SetParams.setParams().nx().px(EXPIRE_TIME);      // redis连接池,连的是本地的redis客户端     JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);           public boolean lock(String id) {         Long start = System.currentTimeMillis();         Jedis jedis = jedisPool.getResource();         try {             for (;;) {                 // SET命令返回OK ,则证明获取锁成功                 String lock = jedis.set(LOCK_KEY, id, params);                 if ("OK".equals(lock)) {                     return true;                 }                 // 否则循环等待,在TIME_OUT时间内仍未获取到锁,则获取失败                 long l = System.currentTimeMillis() - start;                 if (l >= TIME_OUT) {                     return false;                 }                 try {                     // 休眠一会,不然反复执行循环会一直失败                     Thread.sleep(100);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         } finally {             jedis.close();         }     }           public boolean unlock(String id) {         Jedis jedis = jedisPool.getResource();         // 删除key的lua脚本         String script = "if redis.call('get',KEYS[1]) == ARGV[1] then" + "   return redis.call('del',KEYS[1]) " + "else"             + "   return 0 " + "end";         try {             String result =                 jedis.eval(script, Collections.singletonList(LOCK_KEY), Collections.singletonList(id)).toString();             return "1".equals(result);         } finally {             jedis.close();         }     } }

具体的代码作用注释已经写得很清楚了,然后我们就可以写一个demo类来测试一下效果:

public class RedisLockTest {     private static RedisLockUtil demo = new RedisLockUtil();     private static Integer NUM = 101;      public static void main(String[] args) {         for (int i = 0; i < 100; i++) {             new Thread(() -> {                 String id = Thread.currentThread().getId() + "";                 boolean isLock = demo.lock(id);                 try {                  // 拿到锁的话,就对共享参数减一                     if (isLock) {                         NUM--;                         System.out.println(NUM);                     }                 } finally {                  // 释放锁一定要注意放在finally                     demo.unlock(id);                 }             }).start();         }     } }

我们创建100个线程来模拟并发的情况,执行后的结果是这样的:

如何深入理解Redis分布式锁

代码执行结果

可以看出,锁的效果达到了,线程安全是可以保证的。

当然,上面的代码只是简单的实现了效果,功能肯定是不完整的,一个健全的分布式锁要考虑的方面还有很多,实际设计起来不是那么容易的。

我们的目的只是为了学习和了解原理,手写一个工业级的分布式锁工具不现实,也没必要,类似的开源工具一大堆(Redisson),原理都差不多,而且早已经过业界同行的检验,直接拿来用就行。

虽然功能是实现了,但其实从设计上来说,这样的分布式锁存在着很大的缺陷,这也是本篇文章想重点探讨的内容。

分布式锁的缺陷

一、客户端长时间阻塞导致锁失效问题

客户端1得到了锁,因为网络问题或者GC等原因导致长时间阻塞,然后业务程序还没执行完锁就过期了,这时候客户端2也能正常拿到锁,可能会导致线程安全的问题。

如何深入理解Redis分布式锁

客户端长时间阻塞

那么该如何防止这样的异常呢?我们先不说解决方案,介绍完其他的缺陷后再来讨论。

二、redis服务器时钟漂移问题

如果redis服务器的机器时钟发生了向前跳跃,就会导致这个key过早超时失效,比如说客户端1拿到锁后,key的过期时间是12:02分,但redis服务器本身的时钟比客户端快了2分钟,导致key在12:00的时候就失效了,这时候,如果客户端1还没有释放锁的话,就可能导致多个客户端同时持有同一把锁的问题。

三、单点实例安全问题

如果redis是单master模式的,当这台机宕机的时候,那么所有的客户端都获取不到锁了,为了提高可用性,可能就会给这个master加一个slave,但是因为redis的主从同步是异步进行的,可能会出现客户端1设置完锁后,master挂掉,slave提升为master,因为异步复制的特性,客户端1设置的锁丢失了,这时候客户端2设置锁也能够成功,导致客户端1和客户端2同时拥有锁。

为了解决Redis单点问题,redis的作者提出了RedLock算法

RedLock算法

该算法的实现前提在于Redis必须是多节点部署的,可以有效防止单点故障,具体的实现思路是这样的:

1、获取当前时间戳(ms);

2、先设定key的有效时长(TTL),超出这个时间就会自动释放,然后client(客户端)尝试使用相同的key和value对所有redis实例进行设置,每次链接redis实例时设置一个比TTL短很多的超时时间,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。

比如:TTL(也就是过期时间)为5s,那获取锁的超时时间就可以设置成50ms,所以如果50ms内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁;

3、client通过获取所有能获取的锁后的时间减去第一步的时间,还有redis服务器的时钟漂移误差,然后这个时间差要小于TTL时间并且成功设置锁的实例数>=  N/2 + 1(N为Redis实例的数量),那么加锁成功

比如TTL是5s,连接redis获取所有锁用了2s,然后再减去时钟漂移(假设误差是1s左右),那么锁的真正有效时长就只有2s了;

4、如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例。

根据这样的算法,我们假设有5个Redis实例的话,那么client只要获取其中3台以上的锁就算是成功了,用流程图演示大概就像这样:

如何深入理解Redis分布式锁

key有效时长

好了,算法也介绍完了,从设计上看,毫无疑问,RedLock算法的思想主要是为了有效防止Redis单点故障的问题,而且在设计TTL的时候也考虑到了服务器时钟漂移的误差,让分布式锁的安全性提高了不少。

但事实真的是这样吗?反正我个人的话感觉效果一般般,

首先第一点,我们可以看到,在RedLock算法中,锁的有效时间会减去连接Redis实例的时长,如果这个过程因为网络问题导致耗时太长的话,那么最终留给锁的有效时长就会大大减少,客户端访问共享资源的时间很短,很可能程序处理的过程中锁就到期了。而且,锁的有效时间还需要减去服务器的时钟漂移,但是应该减多少合适呢,要是这个值设置不好,很容易出现问题。

然后第二点,这样的算法虽然考虑到用多节点来防止Redis单点故障的问题,但但如果有节点发生崩溃重启的话,还是有可能出现多个客户端同时获取锁的情况。

假设一共有5个Redis节点:A、B、C、D、E,客户端1和2分别加锁

  1. 客户端1成功锁住了A,B,C,获取锁成功(但D和E没有锁住)。

  2. 节点C的master挂了,然后锁还没同步到slave,slave升级为master后丢失了客户端1加的锁。

  3. 客户端2这个时候获取锁,锁住了C,D,E,获取锁成功。

这样,客户端1和客户端2就同时拿到了锁,程序安全的隐患依然存在。除此之外,如果这些节点里面某个节点发生了时间漂移的话,也有可能导致锁的安全问题。

所以说,虽然通过多实例的部署提高了可用性和可靠性,但RedLock并没有完全解决Redis单点故障存在的隐患,也没有解决时钟漂移以及客户端长时间阻塞而导致的锁超时失效存在的问题,锁的安全性隐患依然存在。

到此,关于“如何深入理解Redis分布式锁”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: 如何深入理解Redis分布式锁

本文链接: https://www.lsjlt.com/news/84033.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何深入理解Redis分布式锁
    这篇文章主要介绍“如何深入理解Redis分布式锁”,在日常操作中,相信很多人在如何深入理解Redis分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何深入理解Redi...
    99+
    2022-10-19
  • 深入理解redis分布式锁和消息队列
    最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令。 分布式锁 由于目前一些编程语言,如PHP等,不...
    99+
    2022-06-04
    队列 分布式 消息
  • 玩转Redis-老板带你深入理解分布式锁
    老板:我们每天不都在经历分布式锁吗,我来给你回忆回忆。 小猿:好勒,瓜子板凳已备好。 本文结构 为什么要使用分布式锁 分布式锁有哪些特点 分布式锁流行算法及其优缺点 基本算法 relock算法 token算法 数据库排它锁、ZooKee...
    99+
    2020-02-11
    玩转Redis-老板带你深入理解分布式锁
  • 深入理解:分布式之抉择分布式锁
    前言:目前网上大部分的基于zookpeer,和redis的分布式锁的文章都不够全面。要么就是特意避开集群的情况,要么就是考虑不全,读者看着还是一脸迷茫。坦白说,这种老题材,很难写出新创意,博主内心战战兢兢,如履薄冰,文中有什么不严谨之处,欢...
    99+
    2023-06-05
  • redis深入浅出分布式锁实现上篇
    目录问题描述解决方案编写代码优化之UUID防误删问题描述 随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原...
    99+
    2022-11-13
  • redis深入浅出分布式锁实现下篇
    目录优化之UUID防误删项目中正确使用总结优化之UUID防误删 问题:删除操作缺乏原子性。 场景: index1执行删除时,查询到的lock值确实和uuid相等 uuid=v1 ...
    99+
    2022-11-13
  • 怎么理解Redis分布式锁
    这篇文章主要讲解了“怎么理解Redis分布式锁”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么理解Redis分布式锁”吧!你真的需要分布式锁吗用到分布式锁...
    99+
    2022-10-19
  • 如何理解分布式系统下基于Redis的分布式锁
    这篇文章主要介绍“如何理解分布式系统下基于Redis的分布式锁”,在日常操作中,相信很多人在如何理解分布式系统下基于Redis的分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大...
    99+
    2022-10-18
  • 深入浅出探索Java分布式锁原理
    目录什么是分布式锁?它能干什么?分布式锁实现方案基于数据库的分布式锁实现方案实现原理方案分析基于Redis的分布式锁实现方案基于sentnx命令的实现原理方案分析基于Redisson...
    99+
    2022-11-13
  • 怎么理解Redis中的分布式锁
    本篇内容介绍了“怎么理解Redis中的分布式锁”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Redis 分...
    99+
    2022-10-18
  • 怎么理解redis抉择分布式锁
    本篇内容介绍了“怎么理解redis抉择分布式锁”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!zookeeper可靠性比redis强太多,只是...
    99+
    2023-06-04
  • 详解redis如何实现分布式锁
    小编这次要给大家分享的是详解redis如何实现分布式锁,文章内容丰富,感兴趣的小伙伴可以来了解一下,希望大家阅读完这篇文章之后能够有所收获。前言系统的不断扩大,分布式锁是最基本的保障。与单机的多线程不一样的...
    99+
    2022-10-18
  • 如何解决redis分布式锁超时
    解决redis分布式锁超时的方法:当锁超时时间快到期且逻辑未执行完,可延长锁超时时间。示例:if redis.call("get",KEYS[1]) == ARGV[1] thenredis.call("set",KEYS[1],ex=30...
    99+
    2022-10-11
  • Redis如何实现分布式锁详解
    目录一、前言二、实现原理2.1 加锁2.2 解锁三、通过RedisTemplate实现分布式锁四、通过Redisson实现一、前言 在Java的并发编程中,我们通过锁,来避免由于竞争...
    99+
    2022-11-12
  • 分布式锁的原理及Redis怎么实现分布式锁
    这篇文章主要介绍“分布式锁的原理及Redis怎么实现分布式锁”,在日常操作中,相信很多人在分布式锁的原理及Redis怎么实现分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解...
    99+
    2023-02-02
    redis
  • Redis实现分布式锁详解
    目录一、前言为什么需要分布式锁?二、基于Redis实现分布式锁为什么redis可以实现分布式锁?如何实现?锁的获取锁的释放三、如何避免死锁?锁的过期时间如何设置?避免死锁锁过期处理释放其他服务的锁如何处理呢?那么redi...
    99+
    2023-04-09
    Redis实现分布式锁 Redis分布式锁
  • 详解redis分布式锁(优化redis分布式锁的过程及Redisson使用)
    目录1. redis在实际的应用中2.如何使用redis的功能进行实现分布式锁2.1 redis分布式锁思想2.1.1设计思想:2.1.2 根据上面的设计思想进行代码实现2.2 使用...
    99+
    2022-11-12
  • redis分布式锁的实现原理详解
    首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 1.互斥性。在任意时刻,只有一个客户端能持有锁。 2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有...
    99+
    2022-11-13
  • Redis如何实现分布式锁
    目录一、前言二、正文今天我们来聊一聊分布式锁的那些事。 相信大家对锁已经不陌生了,我们在多线程环境中,如果需要对同一个资源进行操作,为了避免数据不一致,我们需要在操作共享资源之前进行...
    99+
    2022-11-12
  • Redis分布式锁如何实现
    这篇文章将为大家详细讲解有关Redis分布式锁如何实现,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。什么是分布式锁?要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、...
    99+
    2022-10-18
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作