setnx分布式锁原理 分布式为什么一定要有高可用的分布式锁?一线大厂必看!

分布式锁定义

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

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

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

事务隔离级别:

分布式锁目的

分布式锁的目的如下:

寻找唯一资源进行上锁

例子:

1. 防止用户重复下单 共享资源进行上锁的对象 : 【用户id】

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

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

基于 Redis 分布式锁

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

实现方式:

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

存在问题如下:

锁时间不可控

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

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

单点连接超时问题

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

主从问题

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

setnx分布式锁原理_使用memcached实现分布式互斥锁 楚吟风_zookeeper下的分布式锁

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

①业务线程-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 可能作为高可用的分布式锁并不合适,我们需要确立高可用分布式锁的设计目标。

高可用分布式锁设计目标

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

三种分布式锁方案对比

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

setnx分布式锁原理_使用memcached实现分布式互斥锁 楚吟风_zookeeper下的分布式锁

基于 Zookeeper 分布式锁

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

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

setnx分布式锁原理_zookeeper下的分布式锁_使用memcached实现分布式互斥锁 楚吟风

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

ZK 锁实现的原理

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

Zookeeper中的几个特性如下:

zookeeper下的分布式锁_setnx分布式锁原理_使用memcached实现分布式互斥锁 楚吟风

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

ZK 分布式锁的代码实现

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

setnx分布式锁原理_zookeeper下的分布式锁_使用memcached实现分布式互斥锁 楚吟风

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

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

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

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

ZK 分布式锁的 GC 问题

使用memcached实现分布式互斥锁 楚吟风_setnx分布式锁原理_zookeeper下的分布式锁

刚刚说了 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 一样属于强一致性,并且数据是可以进行持久化,默认数据一更新就持久化。

setnx分布式锁原理_zookeeper下的分布式锁_使用memcached实现分布式互斥锁 楚吟风

锁的租期续约问题

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

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

zookeeper下的分布式锁_使用memcached实现分布式互斥锁 楚吟风_setnx分布式锁原理

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

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

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

总结

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

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

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

长按二维码▲

———END———
限 时 特 惠:本站每日持续更新海量各大内部创业教程,一年会员只需128元,全站资源免费下载点击查看详情
站 长 微 信:jiumai99

滚动至顶部