点击上方“Java面试题精选”,关注公众号

面试刷图,查缺补漏

分布式锁常见的三种实现方式:

  1. 数据库乐观锁;

  2. 基于Redis的分布式锁;

  3. 基于ZooKeeper的分布式锁。

本地面试考点是,你对Redis使用熟悉吗?Redis中是如何实现分布式锁的。

要点

Redis要实现分布式锁,以下条件应该得到满足

互斥性

  • 在任意时刻,只有一个客户端能持有锁。

不能死锁

  • 客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

容错性

  • 只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

实现

可以直接通过 set key value px milliseconds nx 命令实现加锁, 通过Lua脚本实现解锁。

//获取锁(unique_value可以是UUID等)SET resource_name unique_value NX PX  30000

//释放锁(lua脚本中,一定要比较value,防止误解锁)if redis.call("get",KEYS[1]) == ARGV[1] then    return redis.call("del",KEYS[1])else    return 0end

代码解释

  • set 命令要用 set key value px milliseconds nx,替代 setnx + expire 需要分两次执行命令的方式,保证了原子性,

  • value 要具有唯一性,可以使用UUID.randomUUID().toString()方法生成,用来标识这把锁是属于哪个请求加的,在解锁的时候就可以有依据;

  • 释放锁时要验证 value 值,防止误解锁;

  • 通过 Lua 脚本来避免 Check And Set 模型的并发问题,因为在释放锁的时候因为涉及到多个Redis操作 (利用了eval命令执行Lua脚本的原子性);

加锁代码分析

首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,用来标识这把锁是属于哪个请求加的,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。

解锁代码分析

将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。在执行的时候,首先会获取锁对应的value值,检查是否与requestId相等,如果相等则解锁(删除key)。

存在的风险

如果存储锁对应key的那个节点挂了的话,就可能存在丢失锁的风险,导致出现多个客户端持有锁的情况,这样就不能实现资源的独享了。

  1. 客户端A从master获取到锁

  2. 在master将锁同步到slave之前,master宕掉了(Redis的主从同步通常是异步的)。主从切换,slave节点被晋级为master节点

  3. 客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。导致存在同一时刻存不止一个线程获取到锁的情况。

redlock算法出现

这个场景是假设有一个 redis cluster,有 5 个 redis master 实例。然后执行如下步骤获取一把锁:

  1. 获取当前时间戳,单位是毫秒;

  2. 跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒;

  3. 尝试在大多数节点上建立一个锁,比如 5 个节点就要求是 3 个节点 n / 2 + 1;

  4. 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了;

  5. 要是锁建立失败了,那么就依次之前建立过的锁删除;

  6. 只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁。

Redis 官方给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看:

https://redis.io/topics/distlock 。

Redisson实现

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。

Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

Redisson 分布式重入锁用法

Redisson 支持单点模式、主从模式、哨兵模式、集群模式,这里以单点模式为例:

// 1.构造redisson实现分布式锁必要的ConfigConfig config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:5379").setPassword("123456").setDatabase(0);// 2.构造RedissonClientRedissonClient redissonClient = Redisson.create(config);// 3.获取锁对象实例(无法保证是按线程的顺序获取到)RLock rLock = redissonClient.getLock(lockKey);try {    /**     * 4.尝试获取锁     * waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败     * leaseTime   锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)     */    boolean res = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);    if (res) {        //成功获得锁,在这里处理业务    }} catch (Exception e) {    throw new RuntimeException("aquire lock fail");}finally{    //无论如何, 最后都要解锁    rLock.unlock();}

加锁流程图

解锁流程图

我们可以看到,RedissonLock是可重入的,并且考虑了失败重试,可以设置锁的最大等待时间, 在实现上也做了一些优化,减少了无效的锁申请,提升了资源的利用率。

需要特别注意的是,RedissonLock 同样没有解决 节点挂掉的时候,存在丢失锁的风险的问题。而现实情况是有一些场景无法容忍的,所以 Redisson 提供了实现了redlock算法的 RedissonRedLock,RedissonRedLock 真正解决了单点失败的问题,代价是需要额外的为 RedissonRedLock 搭建Redis环境。

所以,如果业务场景可以容忍这种小概率的错误,则推荐使用 RedissonLock, 如果无法容忍,则推荐使用 RedissonRedLock。

参考

https://github.com/javazhiyin/advanced-java/
https://crazyfzw.github.io/2019/04/15/distributed-locks-with-redis/

最近三期

【04期】分库分表之后,id 主键如何处理?

【05期】消息队列中,如何保证消息的顺序性?

【06期】单例模式有几种写法?

精选常见面试题、技术知识点,帮助开发者查缺补漏。

java如何保证redis设置过期时间的原子性_【07期】Redis中是如何实现分布式锁的?...相关推荐

  1. java如何保证redis设置过期时间的原子性_分布式锁用 Redis 还是 Zookeeper

    在讨论这个问题之前,我们先来看一个业务场景: 系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户下单. 由于系统有 ...

  2. java如何保证redis设置过期时间的原子性_2020年4月Redis面试题和答案整理

    点关注,不迷路:持续更新Java相关技术及资讯!!! 关注.转发.评论头条号每天分享java 知识,私信回复"源码" 赠送Spring源码分析.Dubbo.Redis.Netty. ...

  3. java如何保证redis设置过期时间的原子性_redis专题系列22 -- 如何优雅的基于redis实现分布式锁

    几个概念 线程锁:主要用来给方法.代码块加锁.当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段.线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比 ...

  4. redis设置过期时间与直接detele key有什么区别

    redis设置过期时间与直接detele key有什么区别 Redis 中设置过期时间和直接删除 key 有以下几点区别: 效率:当使用 key 的过期时间时,Redis 会在 key 过期时自动删除 ...

  5. java redis设置过期时间_Redis的一些核心原理

    点关注,不迷路:持续更新Java相关技术及资讯!!! 一.Redis的单线程和高性能 Redis 单线程为什么还能这么快? 因为它所有的数据都在内存中,所有的运算都是内存级别的运算(纳秒),而且单线程 ...

  6. redis如何设置定时过期_redis补充6之Redis 设置过期时间

    一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间. Redis 中有个设置时间过期的功能,即对存储在 Redis 数据库中的值可以设置一个过期时间.作为一个缓存数据库,这是非常实用的.如我 ...

  7. Redis设置过期时间为当月月底-----自动计算

    今天在新的需求里面,有这么一个要求,要求设置redis过期时间为当月月底,意思是如果一号存进redis,则过期时间为月底:如果29号存进redis,则过期时间同样为月底.我想的就是获取当前时间,算出当 ...

  8. 如何用java操作Redis缓存设置过期时间

    如何用java操作Redis缓存设置过期时间?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获. 在应用中我们会需要使用red ...

  9. redis缓存失效时间设为多少_java操作Redis缓存设置过期时间的方法

    关于Redis的概念和应用本文就不再详解了,说一下怎么在java应用中设置过期时间. 在应用中我们会需要使用redis设置过期时间,比如单点登录中我们需要随机生成一个token作为key,将用户的信息 ...

  10. 玩转Redis-干掉钉子户-没有设置过期时间的key

      <玩转Redis>系列文章 by zxiaofan主要讲述Redis的基础及中高级应用.本文是<玩转Redis>系列第[15]篇,最新系列文章请前往 公众号"zx ...

最新文章

  1. rest-framework之解析器
  2. android版多功能日历,欢迎大家测试
  3. c#输出一个平行四边形_如果Java 和 C# 同时出现,生态也差不多,你选择谁?
  4. node.js 中的package.json文件怎么创建?
  5. loadrunner-3-3场景计划方式与运行模式
  6. 第十二届湖南省赛 (B - 有向无环图 )(拓扑排序+思维)好题
  7. h5弹框滑动 ios_微信 iOS 版更新:细节大更新,你值得拥有
  8. ajax中的简单get请求,jquery 之ajax,get,post异步请求简单代码模版(示例代码)
  9. scp拷贝服务器文件,scp 拷贝文件到远程服务器
  10. 关于如何使用IfcRelAggregates来对IFC中的元素进行关联
  11. 【神经网络】神经元模型和感知器
  12. 软件测试的六大测试质量标准
  13. 电路分析(电路原理)
  14. windows7蓝牙怎么打开_英特尔升级Wi-Fi 和蓝牙驱动,Win10 更新5月版稳了
  15. 中长焦投影仪买哪款好,当贝X3高流明热销你值得看
  16. java程序如何解代数方程_基于代数方程库Algebra.js解二元一次方程功能示例
  17. 如何更改 Win7 网络连接显示名称
  18. 488 祖玛游戏 python
  19. MONGODB 开发架构设计与模型设计
  20. 年薪201万的华为“天才少年”曾是三本复读生,逆袭就是抓住每一次提升自己的机会 | AI大赛报名开启

热门文章

  1. 20191115英文每日一句
  2. Atitit 数据库 负载均衡 方法总结 目录 1. 对称模型负载均衡 vs 非对称模型 2 1.1. 业务分离法 2 1.2. App + db分布式分离法 2 2. 负载均衡算法 2 2.1.
  3. Atitit 图像处理类库大总结attilax qc20
  4. qqzoneQQ空间漏洞扫描器的设计attilax总结
  5. HD Tune Pro: WDC WD1600AAJS-00B8D 160g inMyRitMachi
  6. 如何构建一个可用的企业级API网关?
  7. Julia :元编程、宏
  8. Kubernetes 是什么?为什么也称为 K8S?| 科普
  9. (转)区块链:为什么说finchain是下一代金融应用平台
  10. Rust : 闭包、move 与自由变量的穿越