我们在单机服务器,出现资源的竞争,一般使用synchronized 就可以解决,但是在分布式的服务器上,synchronized 就无法解决这个问题,这就需要一个分布式事务锁。

除此之外面试,基本会问springboot、Redis,然后都会一路再聊到分布式事务、分布式事务锁的实现。

1、常见的分布式事务锁

1、数据库级别的锁

  • 乐观锁,基于加入版本号实现
  • 悲观锁,基于数据库的 for update 实现

2、Redis ,基于 SETNX、EXPIRE 实现

3、Zookeeper,基于InterProcessMutex 实现

4、Redisson,lcok、tryLock(背后原理也是Redis)

本文主要介绍一下Redis和Redisson的分布式事务锁的原理。

2、Redis 搭建模式

Redis 的搭建方式:

  • 单机
  • 主从
  • 哨兵
  • 集群

单机,只要一台Redis服务器,挂了就无法工作了

主从,是备份关系, 数据也会同步到从库,还可以读写分离

哨兵:master挂了,哨兵就行选举,选出新的master,作用是监控主从,主从切换

集群:高可用,分散请求。目的是将数据分片存储,节省内存。

单机:

主从:

哨兵:

集群:

3、几个概念

分布式:简单来说就是将业务进行拆分,部署到不同的机器来协调处理。比如用户在网上买东西,大致分为:订单系统、库存系统、支付系统、、、、这些系统共同来完成用户买东西这个业务操作。

集群:同一个业务,通过部署多个实例来完成,保证应用的高可用,如果其中某个实例挂了,业务仍然可以正常进行,通常集群和分布式配合使用。来保证系统的高可用、高性能。

分布式事务:按照传统的系统架构,下单、扣库存等等,这一系列的操作都是一在一个应用一个数据库中完成的,也就是说保证了事务的ACID特性。如果在分布式应用中就会涉及到跨应用、跨库。这样就涉及到了分布式事务,就要考虑怎么保证这一系列的操作要么都成功要么都失败。保证数据的一致性。

**分布式锁:**因为资源有限,要通过互斥来保持一致性,引入分布式事务锁。

4、Redis分布式锁原理

简单的来说,其实现原理如下:

  • 互斥性

    • 保证同一时间只有一个客户端可以拿到锁。
  • 安全性

    • 只有加锁的服务才能有解锁权限,也就是不能让客户端A加的锁,客户端B、C 都可以解锁。
  • 避免死锁

  • 保证加锁与解锁操作是原子性操作

    • 这个其实属于是实现分布式锁的问题,假设a用redis实现分布式锁
    • 假设加锁操作,操作步骤分为两步:1,设置key set(key,value) 2,给key设置过期时间
    • 假设现在a刚实现set后,程序崩了就导致了没给key设置过期时间就导致key一直存在就发生了死锁。

讲了这么多,Redis实现分布式锁的核心就是:

加锁:

SET key value NX EX timeOut

参数解释:

NX:只有这个key不存才的时候才会进行操作,即 if not exists;
EX:设置key的过期时间为秒,具体时间由第5个参数决定
timeOut:设置过期时间保证不会出现死锁【避免宕机死锁】

代码实现:

 public Boolean lock(String key,String value,Long timeOut){String var1 = jedis.set(key,value,"NX","EX",timeOut); //加锁,设置超时时间 原子性操作if(LOCK_SUCCESS.equals(var1)){return true;}return false;}

总的来说,执行上面的set()方法就只会导致两种结果:

  1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。
  2. 已有锁存在,不做任何操作。

注:从2.6.12版本后, 就可以使用set来获取锁、Lua 脚本来释放锁。setnx是以前刚开始的实现方式,set命令nx、xx等参数,,就是为了实现 setnx 的功能。

解锁:

代码实现:

public Boolean redisUnLock(String key, String value) {String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else  return 0 end";Object var2 = jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value));if (UNLOCK_SUCCESS == var2) {return true;}return false;
}

这段lua代码的意思:首先获取锁对应的value值,检查是否与输入的value相等,如果相等则删除锁(解锁)。

上面加锁、解锁,看着是挺麻烦的,所以就出现了Redisson。

5、Redisson 分布式锁原理

官方介绍:

Redisson是一个在Redis的基础上实现的Java驻内存数据网格。

就是在Redis的基础上封装了很多功能,以便于我们更方便的使用。

只需要三行代码:

RLock lock = redisson.getLock("myLock");
lock.lock(); //加锁
lock.unlock(); //解锁

(1)加锁机制

加锁流程:

redisson的lock()、tryLock()方法 底层 其实是发送一段lua脚本到一台服务器:

if (redis.call('exists' KEYS[1]) == 0) then  +  --  exists 判断key是否存在redis.call('hset' KEYS[1] ARGV[2] 1);  +   --如果不存在,hset存哈希表redis.call('pexpire' KEYS[1] ARGV[1]);  + --设置过期时间return nil;  +                            -- 返回null 就是加锁成功end;  +if (redis.call('hexists' KEYS[1] ARGV[2]) == 1) then  + -- 如果key存在,查看哈希表中是否存在(当前线程)redis.call('hincrby' KEYS[1] ARGV[2] 1);  + -- 给哈希中的key加1,代表重入1次,以此类推redis.call('pexpire' KEYS[1] ARGV[1]);  + -- 重设过期时间return nil;  +end;  +return redis.call('pttl' KEYS[1]); --如果前面的if都没进去,说明ARGV[2]的值不同,也就是不是同一线程的锁,这时候直接返回该锁的过期时间

参数解释:

KEYS[1]:即加锁的key,RLock lock = redisson.getLock("myLock"); 中的myLock

ARGV[1]:即 TimeOut 锁key的默认生存时间,默认30秒

**ARGV[2]:**代表的是加锁的客户端的ID,类似于这样的:99ead457-bd16-4ec0-81b6-9b7c73546469:1

其中lock()默认是30秒的生存时间。

(2)锁互斥

假如客户端A已经拿到了 myLock,现在 有一客户端(未知) 想进入:

1、第一个if判断会执行“exists myLock”,发现myLock这个锁key已经存在了。
2、第二个if判断,判断一下,myLock锁key的hash数据结构中, 如果是客户端A重新请求,证明当前是同一个客户端同一个线程重新进入,所以可从入标志+1,重新刷新生存时间(可重入); 否则进入下一个if。
3、第三个if判断,客户端B 会获取到pttl myLock返回的一个数字,这个数字代表了myLock这个锁key的剩余生存时间。比如还剩15000毫秒的生存时间。

此时客户端B会进入一个while循环,不停的尝试加锁。

(3)watch dog 看门狗自动延期机制

官方介绍:

lockWatchdogTimeout(监控锁的看门狗超时,单位:毫秒)

默认值:30000

监控锁的看门狗超时时间单位为毫秒。该参数只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况。(如果设置了leaseTimeout那就会自动失效了呀~)

看门狗的时间可以自定义设置:

config.setLockWatchdogTimeout(30000);

看门狗有什么用呢?

假如客户端A在超时时间内还没执行完毕怎么办呢? redisson于是提供了这个看门狗,如果还没执行完毕,监听到这个客户端A的线程还持有锁,就去续期,默认是 LockWatchdogTimeout/ 3 即 10 秒监听一次,如果还持有,就不断的延长锁的有效期(重新给锁设置过期时间,30s)

可以在lock的参数里面指定:

lock.lock(); //如果不设置,默认的生存时间是30s,启动看门狗
lock.lock(10, TimeUnit.SECONDS);//10秒以后自动解锁,不启动看门狗,锁到期不续

如果是使用了可重入锁( leaseTimeout):

lock.tryLock(); //如果不设置,默认的生存时间是30s,启动看门狗
lock.tryLock(100, 10, TimeUnit.SECONDS);//尝试加锁最多等待100秒,上锁以后10秒自动解锁,不启动看门狗

这里的第二个参数leaseTimeout 设置为 10 就会覆盖 看门狗的设置(看门狗无效),在10秒后锁就自动失效,不会去续期;如果是 -1 ,就表示 使用看门狗的默认值。

(4)释放锁机制

lock.unlock(),就可以释放分布式锁。就是每次都对myLock数据结构中的那个加锁次数减1。

如果发现加锁次数是0了,说明这个客户端已经不再持有锁了,此时就会用:“del myLock”命令,从redis里删除这个key。

为了安全,会先校验是否持有锁再释放,防止

  • 业务执行还没执行完,锁到期了。(此时没占用锁,再unlock就会报错)
  • 主线程异常退出、或者假死
finally {if (rLock.isLocked()) {if (rLock.isHeldByCurrentThread()) {rLock.unlock();}}}

(5) 缺点

如果是 主从、哨兵模式,当客户端A 把 myLock这个锁 keyvalue写入了 master,此时会异步复制给slave实例。

万一在这个主从复制的过程中 master 宕机了,主备切换,slave 变成了master

那么这个时候 slave还没来得及加锁,此时 客户端A的myLock的 值是没有的,客户端B在请求时,myLock却成功为自己加了锁。这时候分布式锁就失效了,就会导致数据有问题。

所以说Redis分布式说最大的缺点就是宕机导致多个客户端加锁,导致脏数据,不过这种几率还是很小的。


参考:

  • https://www.cnblogs.com/demingblog/p/10295236.html
  • http://www.voidcc.com/redisson/redisson-single-sentinel-mode-configuration
  • https://juejin.im/post/6844903874675867656

Redis分布式事务锁的原理(上)相关推荐

  1. redis分布式事务锁

    redis分布式事务锁:  实现原理:redis通过setNx()方法,setNx()主要的作用就是只能设置同一个key,假如已经存有key,就不能在赋值相同key进入.  避不开的死锁问题:设置失效 ...

  2. Spring Cloud Alibaba微服务项目中集成Redis实现分布式事务锁实践

    引言 我们知道同一个进程里面为了解决资源共享而不出现高并发的问题可以通过高并发编程解决,通过给变量添加volatile关键字实现线程间变量可见:通过synchronized关键字修饰代码块.对象或者方 ...

  3. redis分布式事务脏数据问题

    1.通过redisson处理redis分布式事务锁 redis分布式事务存在的问题? 当一个redis  master宕机了,在进行主从复制的时候又来了一个事务,这时候新的master又好了,这种情况 ...

  4. .net core 下的分布式事务锁

    系统分布式锁的用法 公司框架新增功能分布式锁: 锁的性能之王:缓存 > Zookeeper >= 数据库 锁的实现 实现原理:核心采用StackExchange.Redis的LockTak ...

  5. 阿里分布式事务框架Seata原理解析

    阿里分布式事务框架Seata原理解析 作者:伊凡的一天 链接:https://www.jianshu.com/p/044e95223a17 Seata框架是一个业务层的XA(两阶段提交)解决方案.在理 ...

  6. 【网站架构】一招搞定90%的分布式事务,实打实介绍数据库事务、分布式事务的工作原理应用场景

    大家好,欢迎来到停止重构的频道.本期,我们来聊一下数据库事务以及分布式事务. 大家都在强调事务的重要性,而分布式事务也说是微服务必备的.但又说事务会影响性能,分布式事务更是很复杂的东西.使得大家都很迷 ...

  7. 分布式事务--TCC--流程/原理

    原文网址:分布式事务--TCC--流程/原理_IT利刃出鞘的博客-CSDN博客 简介 本文介绍TCC分布式事务解决方案的流程及原理. 流程 详解 业务操作分两阶段完成 如下图所示,接入TCC前,业务操 ...

  8. 分布式事务框架Seata原理详解

    本文来深度解析下分布式事务框架Seata原理,知其然知其所以然. 文章目录 Seata概述 什么是2PC二阶段提交协议 MySQL XA方案 Seata核心知识 Seata设计目标 Seata组成结构 ...

  9. mysql2阶段提交具体实现_ShardingSphere 4.x 分布式事务之实现原理

    导览 本小节主要介绍ShardingSphere分布式事务的实现原理 两阶段XA事务 Seata柔性事务 两阶段事务-XA 实现原理 实现原理 ShardingSphere里定义了分布式事务的SPI接 ...

最新文章

  1. 简单读!tomcat源码(一)启动与监听
  2. Spring Cloud Alibaba 2.2.6发布:新增Nacos注册快速失败的配置
  3. TFS2008自定义过程模板之 Power Tools 工具篇
  4. html5表格内边框加粗,如何制作加粗的边框表格?
  5. 荣耀20首现身!“保密壳”却暴露双排摄像头
  6. Parcel极速零配置Web应用打包工具
  7. CentOs下部署Core环境
  8. 通达OA办公系统的短信服务器(CDMA)
  9. Python程序发布(打包)及pyInstaller、cx_Freeze工具使用介绍
  10. Dev c++6.0下载
  11. window安装ipython_windows ipython安装
  12. google浏览器呗7654流氓解除
  13. 浅谈人脸识别技术的方法和应用
  14. Java中数字转中文数字
  15. 测试窗体的FormBorderStyle属性,不同属性所对应的窗体边框显示情况
  16. 阿里云性能测试工具PTS
  17. Procmon打开后无反应的问题
  18. 中介者模式及其应用场景
  19. 什么是悲观锁和乐观锁,及分布式锁的实现方式
  20. Spark RDD 特征及其依赖

热门文章

  1. 3DMAX和MAYA的区别在哪?到底哪个更好用
  2. 网站内页关键词与描述设置方法
  3. WinDbg排查.net性能或内存问题步骤简述
  4. 2020年中国水牛奶业生产现状及竞争格局分析,广西省水牛奶产量全国第一「图」
  5. Mplus for Mac 8.3 下载 – 强大的统计建模工具
  6. 爬取百度贴吧帖子页内容
  7. 为什么做微信视频直播?
  8. 计算机视频教程那个好,计算机考研课程视频教程哪个好
  9. 谱聚类(Spectral Clustering)详解
  10. input隐藏域和layui图片上传问题