现在面试都会聊到分布式系统,其中不免谈到分布式锁这块的知识,今天就来聊聊如何设计高可用的分布式锁。

作者:陈于喆,来自:51CTO技术栈

分布式锁定义

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

  • 请求串行化

  • 互斥性

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

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

事务隔离级别:

  • DEFAULT

  • READ_UNCOMMITTED

  • READ_COMMITED

  • REPEATABLE_READ

  • SERLALIZABLE

分布式锁目的

分布式锁的目的如下:

  • 解决业务层幂等性

  • 解决 MQ 消费端多次接受同一消息

  • 确保串行|隔离级别

  • 多台机器同时执行定时任务

寻找唯一资源进行上锁

例子:

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

2. 订单生成后发送MQ给消费者进行积分的添加 寻找上锁的对象 :【订单id】

3. 用户已经创建订单,准备对订单进行支付,同时商家在对这个订单进行改价 寻找上锁对象 : 【订单id】

基于 Redis 分布式锁

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

实现方式:

setnx key value Expire_time 获取到锁 返回 1 , 获取失败 返回 0获取到锁 返回 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),这个时候锁会被一直霸占不能释放,这个问题需要从两个方面进行解决。

第一个是本身业务代码的问题,为何会出现死循环,死锁等问题;第二个是对锁的异常监控问题,这个其实也是微服务治理的一个方面。

ZK 分布式锁的 GC 问题

刚刚说了 ZK 锁的维持是靠 ZK 和客户端的心跳进行维持,如果客户端出现了长时间的 GC 会出现什么状况:

①业务线程-1 获取到锁,但未开始执行业务。

②业务线程-2 发生长时间的 GC。

③业务线程-1 和 ZK 的心跳发生断链。

④lock0001 的临时节点因为心跳断链而被删除。

⑤业务线程-2 获取到锁。

⑥业务线程-2 开始执行业务。

⑦业务线程-1 GC完毕,开始执行业务。

⑧业务线程-1 和业务线程-2 同时执行业务。

基于 Etcd 分布式锁

Etcd 分布式锁的实现原理

Etcd 实现分布式锁比 ZK 要简单很多,就是使用 Key Value 的方式进行写入。

在集群中,如果存在 Key 的话就不能写入,也就意味着不能获取到锁,如果集群中,可以写入 Key,就意味着获取得到锁。

Etcd 到使用了 Raft 保证了集群的一致性,也就是在外界看来,只要 Etcd 集群中某一台机器存在了锁,所有的机器也就存在了锁。

这个跟 ZK 一样属于强一致性,并且数据是可以进行持久化,默认数据一更新就持久化。

锁的租期续约问题

Etcd 并不存在一个心跳的机制,所以跟 Redis 一样获取锁的时候就要对其进行 Expire 的指定,这个时候就存在一个锁的租期问题。

租期问题有几种思路可以去解决,这里讨论其中一种:在获取到锁的业务线程,可以开启一个子线程去维护和轮训这把锁的有效时间,并定时的对这把锁进行续租。

假设业务线程获取到一把锁,锁的 Expire 时间为 10s,业务线程会开启一个子线程通过轮训的方式每 2 秒钟去把这把锁进行续租,每次都将锁的 Expire 还原到 10s。

当业务线程执行完业务时,会把这把锁进行删除,事件完毕。

这种思路一样会存在问题:

  • 客户端挂掉,业务线程和续租子线程都会挂掉,锁最终会释放。

  • 业务线程假死,这个跟 ZK 的假死情况一样,也是属于业务代码应该解决的问题。

  • 客户端超长 GC 问题,长 GC 导致续租子进程没有进行及时续租,锁被超时释放。(GC 的问题可能是个极端问题,一般 GC 超过几秒就可能去查看问题了)

总结

首先得了解清楚我们使用分布式锁的场景,为何使用分布式锁,用它来帮我们解决什么问题,先聊场景后聊分布式锁的技术选型。

无论是 Redis,ZK,Etcd,其实在各个场景下或多或少都存在一些问题,例如:

  • Redis 的 AP 模型会限制很多使用场景,但它却拥有了几者中最高的性能。

  • ZK 的分布式锁要比 Redis 可靠很多,但他繁琐的实现机制导致了它的性能不如 Redis,而且 ZK 会随着集群的扩大而性能更加下降。

  • Etcd 看似是一种折中的方案,不过像锁的租期续约都要自己去实现。

简单来说,先了解业务场景,后进行技术选型。

长按二维码 ▲

订阅「架构师小秘圈」公众号

如有启发,帮我点个在看,谢谢↓

分布式为什么一定要有高可用的分布式锁?一线大厂必看!相关推荐

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

    分布式锁定义 分布式锁在分布式环境下,锁定全局唯一公共资源,表现为: 请求串行化 互斥性 第一步是上锁的资源目标,是锁定全局唯一公共资源,只有是全局唯一的资源才存在多个线程或服务竞争的情况. 互斥性表 ...

  2. Spring Cloud(十一)高可用的分布式配置中心 Spring Cloud Bus 消息总线集成(RabbitMQ)

    上一篇文章,留了一个悬念,Config Client 实现配置的实时更新,我们可以使用 /refresh 接口触发,如果所有客户端的配置的更改,都需要手动触发客户端 /refresh ,当服务越来越多 ...

  3. springCloud学习-高可用的分布式配置中心(Spring Cloud Config)

    1.简介 高可用的分布式配置中心,即将配置中心做成一个微服务,将其集群化,从而达到高可用.config-server和config-client向eureka-server注册,且将config-se ...

  4. 日志采集框架Flume以及Flume的安装部署(一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统)...

    Flume支持众多的source和sink类型,详细手册可参考官方文档,更多source和sink组件 http://flume.apache.org/FlumeUserGuide.html Flum ...

  5. 高可用高性能分布式文件系统FastDFS实践Java程序

    在前篇 高可用高性能分布式文件系统FastDFS进阶keepalived+nginx对多tracker进行高可用热备 中已介绍搭建高可用的分布式文件系统架构. 那怎么在程序中调用,其实网上有很多栗子, ...

  6. 分布式架构中常见理论以及如何才能设计出高可用的分布式架构?

    分布式架构中常见理论以及如何才能设计出高可用的分布式架构? 一.前言 我们就来聊一聊目前主流的分布式架构以及分布式架构中常见理论以及如何才能设计出高可用的分布式架构好了.分布式架构中,SOA和微服务架 ...

  7. 高可用,完全分布式Hadoop集群HDFS和MapReduce安装配置指南

    原文:http://my.oschina.net/wstone/blog/365010#OSC_h3_13 (WJW)高可用,完全分布式Hadoop集群HDFS和MapReduce安装配置指南 [X] ...

  8. 【FastDFS】如何打造一款高可用的分布式文件系统?这次我明白了!!

    写在前面 前面我们学习了如何基于两台服务器搭建FastDFS环境,而往往在生产环境中,需要FastDFS做到高可用,那如何基于FastDFS打造一款高可用的分布式文件系统呢?别急,今天,我们就一起来基 ...

  9. 视频教程- 19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合-Java

    19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合 7年的开发架构经验,曾就职于国内一线互联网公司,开发工程师,现在是某创业公司技术负责人, 擅长语言有nod ...

最新文章

  1. 使用opencv dnn 模块调用darknet模型时候出错,不支持relu激活函数
  2. JSON数据与JavaScript对象转换
  3. 使用 Spring Boot 快速构建 Spring 框架应用--转
  4. 老李分享:Web Services 组件 1
  5. win10系统如何把bat批处理文件固定到开始菜单
  6. java工作流引擎Jflow流程事件和流程节点事件设置
  7. mybatis 3的TypeHandler深入解析(及null值的处理)
  8. linux电路图软件有哪些,新手福利,推荐一款好用的电路图绘制软件!
  9. 基于java宠物商店管理系统(java毕业设计)
  10. 苹果mac休眠快捷键_新手小白用苹果电脑搞科研,学会这些才不至于尴尬!
  11. 鼠标滑过图片文字遮罩效果
  12. php高德地图与百度地图转换,高德地图百度地图坐标相互转换
  13. 用代理服务器加速爬虫速率
  14. 软件测试自学到什么程度可以开始找工作?
  15. 商业落地的 DeFi 热潮中,公链们或殊途而同归
  16. 云计算基础知识培训讲义
  17. 【Linux操作系统】1. Linux操作系统简介、安装
  18. 最新版NASA官方网站数据下载流程——以MODIS数据演示为例
  19. php fav ico_前5名:失败原因,Fav桌面环境,代码游戏玩家评论等
  20. java村庄水井_我的世界:它们同样是“水井”却隐藏着不同的“秘密”,见过吗?...

热门文章

  1. UVA11400 照明系统设计 Lighting System Design(线性DP)
  2. 截取前四位字符串_Python的字符串切片
  3. excel表格行列显示十字定位_Excel行列十字交叉高亮显示
  4. sysdba,sysoper,normal 以及sys,sysdba,dba概念区别
  5. webservice中cxf框架的HelloWord
  6. Firewall防火墙应用案例
  7. hibernate中validate的使用(转)
  8. Init进程和进程 ④
  9. oracle查询正在执行的语句和kill session
  10. python ctypes库中动态链接库加载方式