几个概念

线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。

进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。

分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

合理的分布式锁应该具有哪些特性

在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行高可用的获取锁与释放锁高性能的获取锁与释放锁具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)具备锁失效机制,防止死锁具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败

目前可供选择的分布式锁技术方案

1.利用数据库本身的乐观锁(新增字段version,每次更新版本号+1,参考mysql的MVCC机制)或者排他锁来实现(for update)

2.利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。Zookeeper 设计的初衷,就是为了实现分布式锁服务的。

3.利用redis的SET my_key my_value NX PX milliseconds命令实现或者内嵌的lua脚本,鉴于redis的特性,他们都具有原子性

4.利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情况下,才能 add 成功,也就意味着线程得到了锁。

5.Chubby:Google 公司实现的粗粒度分布式锁服务,底层利用了 Paxos 一致性算法。

6.基于 Consul 做分布式锁,主要利用 Consul 的 Key / Value 存储 API 中的 acquire 和 release 操作来实现

谈谈如何优雅的通过redis实现分布式锁

在设计redis分布式锁,我们要考虑的点:

1.命令具有原子性,不允许多个客户端可以同时执行同一条指令,包含加锁和释放锁的命令

2.锁超时,针对锁应该设置超时时间防止单点客户端宕机后锁得不到释放造成其他等待锁的线程无限等待

3.锁续约,简单来说,假设我们给锁设置的失效时间为2s,但业务执行完毕需要3s,导致其他线程过早拿到锁,可能对临界区资源造成数据安全问题,同时,执行unlock的时候,释放掉了其他进程持有的锁。

大概思维导图如下:

下面拆分:

1.加锁

加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间

SET lock_key random_value NX PX 5000

值得注意的是:
random_value 是客户端生成的唯一的字符串(可以参考分布式id算法,像UUID,Snowflake等等)。
NX 代表只在键不存在时,才对键进行设置操作。
PX 5000 设置键的过期时间为5000毫秒。

这样,如果上面的命令执行成功,则证明客户端获取到了锁。

2.解锁

解锁的过程就是将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉。这时候random_value的作用就体现出来。

为了保证解锁操作的原子性,我们用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。

3.关键代码

加锁

解锁

至于守护线程,可以参考JDK的ScheduleService,根据key的TTL创建定时任务去监测当前的业务的执行时间从而判断是否决定锁续约,当然还有其他很多方法,这只是一个参考。

总结:基于上面的实现,我们基本上实现了单机的redis分布式锁,但它依然有个明显的缺点,即不可重入,如果同一个进程进行多次加锁,则显得有点捉襟见肘了。其实,参看JDK的重入锁,我们也可以轻松的设计出redis分布式锁,下面我们来了解一下Redisson.

Redisson分布式锁

Redisson为我们提供了更好的实现,几门满足了分布式锁的所有特性。我们来了解一下

Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

Redisson可谓是强大到不可思议,几乎包含了我们想要的关于分布式锁想要的一切特性,拆箱即用,非常方便,而且锁的种类繁多,像可重入锁,联锁,红锁,公平锁,信号量,可过期信号量,闭锁等等。本文就结合源码大概讲解一下redisson基于JDK重入锁实现的分布式锁,希望你能get到它的思想并运用到项目中。

先给一下Maven配置:

org.redisson   redisson   3.11.6

代码简单示例:

源码解析:

1.加锁

RLock lock = client.getLock("lock1"); 这句代码就是为了获取锁的实例,然后我们可以看到它返回的是一个RedissonLock对象。加锁的代码都是lockInterruptibly 方法提供支持的,我们只分析这个方法

如上代码,就是加锁的全过程。先调用tryAcquire来获取锁,如果返回值ttl为空,则证明加锁成功,返回;如果不为空,则证明加锁失败。这时候,它会订阅这个锁的Channel,等待锁释放的消息,然后重新尝试获取锁。流程如下:

接下来就要看tryAcquire方法

在这里,它有两种处理方式,一种是带有过期时间的锁,一种是不带过期时间的锁。接着往下看,tryLockInnerAsync方法是真正执行获取锁的逻辑,它是一段LUA脚本代码。在这里,它使用的是hash数据结构。同时注意scheduleExpirationRenewal,redisson就是通过它进行所续约的。

这段LUA代码看起来并不复杂,有三个判断:

  • 通过exists判断,如果锁不存在,则设置值和过期时间,加锁成功
  • 通过hexists判断,如果锁已存在,并且锁的是当前线程,则证明是重入锁,加锁成功
  • 如果锁已存在,但锁的不是当前线程,则证明有其他线程持有锁。返回当前锁的过期时间,加锁失败

加锁成功后,在redis的内存数据中,就有一条hash结构的数据。Key为锁的名称;field为随机字符串+线程ID;值为1。如果同一线程多次调用lock方法,值递增1。这正好吻合了JDK重入锁的设计思想。

2.解锁

通过调用unlock方法来解锁

然后我们再看unlockInnerAsync方法。这里也是一段LUA脚本代码。

如上代码,就是释放锁的逻辑。同样的,它也是有三个判断:

  • 如果锁已经不存在,通过publish发布锁释放的消息,解锁成功
  • 如果解锁的线程和当前锁的线程不是同一个,解锁失败,抛出异常
  • 通过hincrby递减1,先释放一次锁。若剩余次数还大于0,则证明当前锁是重入锁,刷新过期时间;若剩余次数小于0,删除key并发布锁释放的消息,解锁成功

这样,关于redisson重入锁的加锁解锁都已经分析完了。注意,redisson的锁续约是采用了watchdog机制,其实它就是一个定时调度任务,如果你没有设置锁的过期时间,redisson会给一个默认值30s,这样看门狗就会每10s进行一次续约,保证锁一致持有在手中,当然它依然有自己的熔断机制,假设持有锁的线程进入了死循环,在几次续约后便不再续约。同样,在解锁时,取消了锁续约机制。

总结

1.上面介绍的均为单机模式下的分布式锁实现方式,如果强行使用到集群模式下存在一定的风险,我们知道redis集群模式下使用的主从复制模式(异步),如果客户端在加锁成功的一刹那,master节点故障,导致slave节点没有同步到对应的key值,可能存在多个客户端获取通一把锁。至于集群模式下分布式锁如何实现和原理细节,请参考http://redis.cn/topics/distlock.html。

2.同时对于redisson,可重入锁只是其冰山一角,如果感兴趣的话可以去官网了解其架构和底层原理,本文不宜过多讲解。github地址:https://github.com/redisson/redisson。

附上redisson架构图:

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

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

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

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

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

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

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

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

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

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

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

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

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

  7. redis setnx 过期时间_阿里面试官:你确定你用过 Redis 分布式锁吗?

    你有听说过 Redlock 吗? 别整些花里胡哨的,Redlock 全称 Redis Distributed Lock,即用 Redis 实现的分布式锁. Redis 热身知识 Redis 命令参考: ...

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

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

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

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

最新文章

  1. python爬虫---从零开始(一)初识爬虫
  2. springboot的jsp应该放在哪_详解SpringBoot 添加对JSP的支持(附常见坑点)
  3. 端应用研发进入云原生时代
  4. 力扣--- 滑动谜题
  5. 异常处理 Exception
  6. 红魔游戏手机6 Pro氘锋透明版明日开启预售:售价5599元
  7. 【hue】 hue+sentry界面没有添加角色的按钮
  8. Docker安装实践Jenkins
  9. 在docker中配置apt工具与python的源均为国内源
  10. Shiro整合JWT实现认证和权限鉴定(执行流程清晰详细)
  11. maya arnold渲染器产品快速灯光渲染模板文件下载
  12. 如何使用lerna管理你的仓库
  13. opencv处理图像数据时候,出现图像全黑
  14. ubuntu16.04不能访问新加卷
  15. FPGA 之 SOPC 系列(二)SOPC开发流程及开发平台简介
  16. java实体类生成mysql表_springboot+mybatis通过实体类自动生成数据库表的方法
  17. 百度地图运行轨迹根据车速显示不同颜色线
  18. 25. Linux中的web服务器Apache
  19. c语言程序设计教程高佳琴主编答案,数据结构与算法应用教程.ppt
  20. RocketMQ 消费端限流

热门文章

  1. 金山网盾3.5.3版本预升级公告
  2. 构建基于Ceph的文件共享服务
  3. Vue中的前后台交互
  4. oracle 优化之批量处理bulk correct 和 forall
  5. Centos服务器常用安装指南
  6. 针对maven install 报错:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1 解决方案...
  7. 1个多月就能看到效果的减肥大法 - 生活至上,美容至尚!
  8. Lintcode 973 解题思路及c++代码
  9. 偏心率e用于描述某一轨道与圆轨道的区别
  10. 国家会展中心启动5G网络建设,推进无人驾驶等应用场景落地