分布式锁定义

分布式锁在分布式环境下,锁定全局唯一公共资源,表现为:

  • 请求串行化
  • 互斥性

第一步是上锁的资源目标,是锁定全局唯一公共资源,只有是全局唯一的资源才存在多个线程或服务竞争的情况。

互斥性表现为一个资源的隔离级别串行化,如果对照单机事务 ACID 的隔离性来说,互斥性的事务隔离级别是 SERLALIZABLE,属于最高的隔离级别。

事务隔离级别:

  • DEFAULT
  • READ_UNCOMMITTED
  • READ_COMMITED
  • REPEATABLE_READ
  • SERLALIZABLE

分布式锁目的

分布式锁的目的如下:

  • 解决业务层幂等性
  • 解决 MQ 消费端多次接受同一消息
  • 确保串行|隔离级别
  • 多台机器同时执行定时任务

寻找唯一资源进行上锁

例子:

1. 防止用户重复下单 共享资源进行上锁的对象 : 【用户id】2. 订单生成后发送MQ给消费者进行积分的添加 寻找上锁的对象 :【订单id】3. 用户已经创建订单,准备对订单进行支付,同时商家在对这个订单进行改价 寻找上锁对象 : 【订单id】

基于 Redis 分布式锁

Redis 单线程串行处理天然就是解决串行化问题,用来解决分布式锁是再适合不过。

实现方式:

setnx key value Expire_time 获取到锁 返回 1 , 获取失败 返回 0

存在问题如下:

锁时间不可控

Redis 只能在 Setnx 指定一个锁的超时时间,假设初始设定锁的时间是 10 秒钟,但是业务获取到锁跑了 20 秒钟,在 10 秒钟之后,如果又有一个业务可以获取到相同的一把锁。

这个时候可能就存在两个相同的业务都获取得到锁的问题,并且两个业务处在并行阶段。也就是第一个获取锁的业务无法对自身的锁进行续租。

单点连接超时问题

Redis 的 Client 与 Server 端并没有维持心跳的机制,如果在连接中出现问题,Client 会得到一个超时的回馈。

主从问题

Redis 的集群实际上在 CAP 模式中是处在与 AP 的模型,保证可用性。在主从复制中“主”有数据,但可能“从”还没有数据。这个时候,一旦主挂掉或者网络抖动等各种原因,可能会切换到“从”节点。

这个时候有可能会导致两个业务线程同时的获取到两把锁:

①业务线程-1:向主节点请求锁

②业务线程-1:获取锁

③业务线程-1:获取到锁并开始执行业务

④这个时候 Redis 刚生成的锁在主从之间还未进行同步

⑤Redis 这时候主节点挂掉了

⑥Redis 的从节点升级为主节点

⑦业务线程-2:向新的主节点请求锁

⑧业务线程-2:获取到新的主节点返回的锁

⑨业务线程-2:获取到锁开始执行业务

⑩这个时候业务线程-1和业务线程-2同时在执行任务

Redlock

上述的问题其实并不是 Redis 的缺陷,只是 Redis 采用了 AP 模型,它本身无法确保我们对一致性的要求。

Redis 官方推荐 Redlock 算法来保证,问题是 Redlock 至少需要三个 Redis 主从实例来实现,维护成本比较高。

相当于 Redlock 使用三个 Redis 集群实现了自己的另一套一致性算法,比较繁琐,在业界也使用得比较少。

能不能使用 Redis 作为分布式锁

能不能使用 Redis 作为分布式锁,这个本身就不是 Redis 的问题,还是取决于业务场景,我们先要自己确认我们的场景是适合 AP 还是 CP。

如果在社交发帖等场景下,我们并没有非常强的事务一致性问题,Redis 提供给我们高性能的 AP 模型是非常适合的。

但如果是交易类型,对数据一致性非常敏感的场景,我们可能要寻找一种更加适合的 CP 模型。

Redis 可能作为高可用的分布式锁并不合适,我们需要确立高可用分布式锁的设计目标。

高可用分布式锁设计目标

高可用分布式锁的设计目标如下:

  • 强一致性,是 CP 模型
  • 服务高可用,不存在单点问题
  • 锁能够续租和自动释放
  • 业务接入简单

三种分布式锁方案对比

常用的三种分布式锁方案对比如下图:

基于 Zookeeper 分布式锁

刚刚也分析过,Redis 其实无法确保数据的一致性,先来看 Zookeeper 是否合适作为我们需要的分布式锁。

首先 ZK 的模式是 CP 模型,也就是说,当 ZK 锁提供给我们进行访问的时候,在 ZK 集群中能确保这把锁在 ZK 的每一个节点都存在。

这个实际上是 ZK 的 Leader 通过二阶段提交写请求来保证的,这个也是 ZK 的集群规模大了的一个瓶颈点。

ZK 锁实现的原理

说 ZK 的锁问题之前先看看 Zookeeper 中的几个特性,这几个特性构建了 ZK 的一把分布式锁。

Zookeeper 中的几个特性如下:

  • 有序节点,当在一个父目录下如 /lock 下创建有序节点,节点会按照严格的先后顺序创建出自节点 lock000001,lock000002,lock0000003,以此类推,有序节点能严格保证各个自节点按照排序命名生成。
  • 临时节点,客户端建立了一个临时节点,在客户端的会话结束或会话超时,Zookeeper 会自动删除该节点 ID。
  • 事件监听,在读取数据时,我们可以对节点设置监听,当节点的数据发生变化(1 节点创建,2 节点删除,3 节点数据变动,4 子节点变动)时,Zookeeper 会通知客户端。

结合这几个特点,来看下 ZK 是怎么组合分布式锁:

  • 业务线程-1,业务线程-2 分别向 ZK 的 /lock 目录下,申请创建有序的临时节点。
  • 业务线程-1 抢到 /lock0001 的文件,也就是在整个目录下最小序的节点,也就是线程-1 获取到了锁。
  • 业务线程-2 只能抢到 /lock0002 的文件,并不是最小序的节点,线程 2 未能获取锁。
  • 业务线程-1 与 lock0001 建立了连接,并维持了心跳,维持的心跳也就是这把锁的租期。
  • 当业务线程-1 完成了业务,将释放掉与 ZK 的连接,也就是释放了这把锁。

ZK 分布式锁的代码实现

ZK 官方提供的客户端并不支持分布式锁的直接实现,我们需要自己写代码去利用 ZK 的这几个特性去进行实现:

ZK 分布式锁客户端假死的问题

客户端创建了临时有序节点并建立了事件监听,就可以让业务线程与 ZK 维持心跳,这个心跳也就是这把锁的租期。

当客户端的业务线程完成了执行就把节点进行删除,也就释放了这把锁,不过中间也可能存在问题:

  • 客户端挂掉。因为注册的是临时节点,客户端挂掉,ZK 会进行感知,也就会把这个临时节点删除,锁也就随着释放。
  • 业务线程假死。业务线程并没有消息,而是一个假死状态,(例如死循环,死锁,超长 GC),这个时候锁会被一直霸占不能释放,这个问题需要从两个方面进行解决。

为什么多个线程不可能同时抢到一把锁_分布式为什么一定要有高可用的分布式锁?看完就知道了...相关推荐

  1. 为什么多个线程不可能同时抢到一把锁_并发基础理论:原子性问题、锁、管程...

    我们再回顾一下,原子性问题的根源是CPU切换线程执行指令所导致的,当前一个对共享变量的操作没有完成之前,CPU又切换到另外一个线程来操作对应的共享变量,那么最终产生的结果就可能出现问题. 比如如果现在 ...

  2. 为什么多个线程不可能同时抢到一把锁_HFL Redis_12_redis分布式锁的3种实现方式...

    HotFrameLearning(简称 HFL) Redis_12_redis分布式锁的3种实现方式 - 一.大致介绍 ``` 1.昨天介绍完redis的数据结构后,有小伙伴让本人讲讲redis的分布 ...

  3. java 变量锁_并发编程高频面试题:可重入锁+线程池+内存模型等(含答案)

    对于一个Java程序员而言,能否熟练掌握并发编程是判断他优秀与否的重要标准之一.因为并发编程是Java语言中最为晦涩的知识点,它涉及操作系统.内存.CPU.编程语言等多方面的基础能力,更为考验一个程序 ...

  4. reentrantlock非公平锁不会随机挂起线程?_【原创】Java并发编程系列16 | 公平锁与非公平锁...

    本文为何适原创并发编程系列第 16 篇,文末有本系列文章汇总. 上一篇提到重入锁 ReentrantLock 支持两种锁,公平锁与非公平锁.那么这篇文章就来介绍一下公平锁与非公平锁. 为什么需要公平锁 ...

  5. Java多线程---线程通信(wait,notifyAll,生产者消费者经典范式,owner wait set,自定义显式锁BooleanLock)

    转自:https://blog.csdn.net/qq_35995514/article/details/91128585 1 学习内容 notifyAll 生产者.消费者经典范式 线程休息室 wai ...

  6. 常见面试题:为什么HashMap不是线程安全的呢?(JDK1.7和JDK1.8角度)(看完你就能和面试官笑谈人生了)

    title: 常见面试题:为什么HashMap不是线程安全的呢?(JDK1.7和JDK1.8角度)(看完你就能和面试官笑谈人生了) tags: 面试常见题 常见面试题:为什么HashMap不是线程安全 ...

  7. 图解 Java 线程的生命周期,看完再也不怕面试官问了

    文章首发自个人微信公众号: 小哈学Java www.exception.site/java-concur- 在 Java 初中级面试中,关于线程的生命周期可以说是常客了.本文就针对这个问题,通过图文并 ...

  8. 在c#中用mutex类实现线程的互斥_面试官经常问的synchronized实现原理和锁升级过程,你真的了解吗...

    本篇文章主要从字节码和JVM底层来分析synchronized实现原理和锁升级过程,其中涉及到了简单认识字节码.对象内部结构以及ObjectMonitor等知识点. 阅读本文之前,如果大家对synch ...

  9. 2万字,看完这篇才敢说自己真的懂线程池!

    前言 线程池可以说是 Java 进阶必备的知识点了,也是面试中必备的考点,可能不少人看了一些文章后能对线程池工作原理说上一二,但这还远远不够,如果碰到比较有经验的面试官再继续追问,很可能会被吊打,考虑 ...

最新文章

  1. python入门之函数调用内置函数_第九篇 python基础之函数,递归,内置函数
  2. php和python对比-通过PHP与Python代码对比浅析语法差异
  3. Android 获取外网IP,实测有效
  4. PHP的simplexml_load_file
  5. Ajax与CustomErrors的尴尬
  6. [react] 展示组件和容器组件有什么区别?
  7. 五步让你成为专家级程序员
  8. Mysql5.0没有nvarchar,national
  9. [Windows Server 2012] 安装PHP+MySQL方法
  10. openssl数字证书常见格式与协议介绍
  11. Maven学习总结(23)——Maven常用命令介绍
  12. 报错:undefined reference to `requestVerifyCode(char*)‘解决
  13. mysql水平分表实践记录_MYSQL 分表实践
  14. atitit 部门日常工作流程体系 日常日程表 日常工作内容列表清单.docx
  15. COOC1.9软件 一键做共现矩阵与相异矩阵
  16. 5G NR首版标准R15解读
  17. sublime html主题,2015 和 2016 最好 Sublime Text 3 主题
  18. java gc 监控_JAVA网站full GC监控脚本
  19. 某地环境空气质量分析(1)
  20. java aria,ARIA 标签和关系

热门文章

  1. 绘制pr曲线图_生存分析如何绘制事件发生累计概率曲线图?
  2. 修改after样式_理解:Before和:After伪元素
  3. 难点—在引用数组元素时指针的运算
  4. php 扩展包是什么意思,大家知道扩展用户组是什么意思么??
  5. [BJDCTF 2nd]8086(异或脚本解题)
  6. CTF-window和linux下命令执行的知识
  7. python教程:实现延时回调普通函数的方法
  8. python中dict的fromkeys用法教程
  9. Python函数中参数前带*是什么意思?
  10. python 函数教程:必选参数与默认参数