「如何设计」高可用的分布式锁

「如何设计」:redis

「如何设计」具有幂等性的服务
「如何设计」微服务中高可用的分布式锁算法

分布式锁定义

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

  • 请求串行化
  • 互斥性

第一步是上锁的资源目标,是锁定全局惟一公共资源,只有是全局惟一的资源才存在多个线程或服务竞争的状况,互斥性表现为一个资源的隔离级别串行化,若是对照单机事务ACID的隔离型来讲,互斥性的事务隔离级别是SERLALIZABLE,属于最高的隔离级别。restful

(事务隔离级别:DEFAULT,READ_UNCOMMITTED,READ_COMMITED,REPEATABLE_READ,SERLALIZABLE)网络

分布式锁目的

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

寻找惟一资源进行上锁

例子:分布式

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

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

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

基于redis分布式锁

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

实现方式:
setnx key value Expire_time
获取到锁 返回 1 , 获取失败 返回 0post

存在问题:性能

锁时间不可控

redis 只能在setnx指定一个锁的超时时间,假设初始设定锁的时间是10秒钟,可是业务获取到锁跑了20秒钟,在10秒钟以后,若是又有一个业务能够获取到相同的一把锁,这个时候可能就存在两个相同的业务都获取获得锁的问题,而且两个业务处在并行阶段。也就是第一个获取锁的业务没法对自身的锁进行续租。spa

单点链接超时问题

redis 的client与server端并无维持心跳的机制,若是在链接出现问题,client会获得一个超时的回馈。

主从问题

redis的集群实际上在CAP模式中是处在与AP的模型,保证可用性。在主从复制中“主”有数据,但可能“从”尚未数据,这个时候,一旦主挂掉或者网络抖动等各类缘由,可能会切换到“从”节点,这个时候有可能会致使两个业务线程同时的获取到两把锁。

image

  1. 业务线程-1 向主节点请求锁
  2. 业务线程-1 获取锁
  3. 业务线程-1 获取到锁并开始执行业务
  4. 这个时候redis刚生成的锁在主从之间还未进行同步
  5. redis这时候主节点挂掉了
  6. redis的从节点升级为主节点
  7. 业务线程-2 想新的主节点请求锁
  8. 业务线程-2 获取到新的主节点返回的锁
  9. 业务线程-2 获取到锁开始执行业务
  10. 这个时候 业务线程-1 和 业务线程-2 同时在执行任务

redlock

上述的问题其实并非redis的缺陷,只是redis采用了AP模型,它自己没法确保咱们对一致性的要求。redis官方推荐redlock算法来保证,问题是redlock至少须要三个redis主从实例来实现,维护成本比较高,至关于redlock使用三个redis集群实现了本身的另外一套一致性算法,比较繁琐,在业界也使用得比较少。

能不能使用redis做为分布式锁

能不能使用redis做为分布式锁,这个自己就不是redis的问题,仍是取决于业务场景,咱们先要本身确认咱们的场景是适合 AP 仍是 CP , 若是在社交发帖等场景下,咱们并无很是强的事务一致性问题,redis提供给咱们高性能的AP模型是很是适合的,但若是是交易类型,对数据一致性很是敏感的场景,咱们可能要寻在一种更加适合的 CP 模型

redis可能做为高可用的分布式锁并不合适,咱们须要确立高可用分布式锁的设计目标

高可用分布式锁设计目标

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

三种分布式锁方案对比

- redis zookeeper etcd
一致性算法 zab raft
CAP AP CP CP
高可用 主从 N+1 N+1
实现 setnx create临时有序节点 restful

基于zookeeper分布式锁

刚刚也分析过,redis其实没法确保数据的一致性,先来看zookeeper是否合适做为咱们须要的分布式锁,首先zk的模式是CP模型,也就是说,当zk锁提供给咱们进行访问的时候,在zk集群中能确保这把锁在zk的每个节点都存在。

image

(这个其实是zk的leader经过二阶段提交写请求来保证的,这个也是zk的集群规模大了的一个瓶颈点)

zk 锁实现的原理

说zk的锁问题以前先看看zookeeper中几个特性,这几个特性构建了zk的一把分布式锁 特性:

  • 有序节点

    当在一个父目录下如 /lock 下建立 有序节点,节点会按照严格的前后顺序建立出自节点 lock000001,lock000002,lock0000003,以此类推,有序节点能严格保证各个自节点按照排序命名生成。

  • 临时节点

    客户端创建了一个临时节点,在客户端的会话结束或会话超时,zookepper会自动删除该解ID那。

  • 事件监听

    在读取数据时,咱们能够对节点设置监听,当节点的数据发生变化(1 节点建立 2 节点删除 3 节点数据变成 4 自节点变成)时,zookeeper会通知客户端。

结合这几个特色,来看下zk是怎么组合分布式锁。

image

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

zk分布式锁的代码实现

zk官方提供的客户端并不支持分布式锁的直接实现,咱们须要本身写代码去利用zk的这几个特性去进行实现。

image

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

客户端建立了临时有序节点并创建了事件监听,就可让业务线程与zk维持心跳,这个心跳也就是这把锁的租期。当客户端的业务线程完成了执行就把节点进行删除,也就释放了这把锁,不过中间也可能存在问题

  • 客户端挂掉

    由于注册的是临时节点,客户端挂掉,zk会进行感知,也就会把这个临时节点删除,锁也就随着释放

  • 业务线程假死

    业务线程并无消息,而是一个假死状态,(例如死循环,死锁,超长gc),这个时候锁会被一直霸占不能释放,这个问题须要从两个方面进行解决。

    第一个是自己业务代码的问题,为什么会出现死循环,死锁等问题。

    第二个是对锁的异常监控问题,这个其实也是微服务治理的一个方面。

zk分布式锁 的GC 问题

刚刚说了zk锁的维持是靠zk和客户端的心跳进行维持,若是客户端出现了长时间的GC会出现什么情况

image

  1. 业务线程-1 获取到锁,但未开始执行业务
  2. 业务线程-2 发生长时间的GC
  3. 业务线程-1 和 zk 的心跳发生断链
  4. lock0001 的临时节点由于心跳断链而被删除
  5. 业务线程-2 获取到锁
  6. 业务线程-2 开始执行业务
  7. 业务线程-1 GC完毕,开始执行业务
  8. 业务线程-1 和 业务线程-2 同时执行业务

基于 etcd 分布式锁

etcd分布式锁的实现原理

etcd实现分布式锁比zk要简单不少,就是使用key value的方式进行写入,在集群中,若是存在key的话就不能写入,也就意味着不能获取到锁,若是集群中,能够写入key,就意味着获取获得锁。

etc到使用了raft保证了集群的一致性,也就是在外界看来,只要etcd集群中某一台机器存在了锁,全部的机器也就存在了锁,这个跟zk同样属于强一致性,而且数据是能够进行持久化,默认数据一更新就持久化。

image

锁的租期续约问题

etcd 并不存在一个心跳的机制,因此跟redis同样获取锁的时候就要对其进行expire的指定,这个时候就存在一个锁的租期问题。

租期问题有几种思路能够去解决,这里讨论其中一种:

在获取到锁的业务线程,能够开启一个子线程去维护和轮训这把锁的有效时间,并定时的对这把锁进行续租

image

假设业务线程获取到一把锁,锁的expire时间为10s,业务线程会开启一个子线程经过轮训的方式每2秒钟去把这把锁进行续租,每次都将锁的expire还原到10s,当业务线程执行完业务时,会把这把锁进行删除,事件完毕。

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

  1. 客户端挂掉,业务线程和续租子线程都会挂掉,锁最终会释放
  2. 业务线程假死,这个跟zk的假死状况同样,也是属于业务代码应该解决的问题
  3. 客户端超长GC问题,长GC致使续租子进程没有进行及时续租,锁被超时释放。(GC的问题多是个极端问题,通常GC超过几秒就可能去查看问题了)

总结

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

不管是redis,zk,etcd其实在各个场景下或多或少都存在一些问题,例如redis的AP模型会限制不少使用场景,但它却拥有了几者中最高的性能,zookeeper的分布式锁要比redis可靠不少,但他繁琐的实现机制致使了它的性能不如redis,并且zk会随着集群的扩大而性能更加降低。etcd 看似是一种折中的方案,不过像锁的租期续约都要本身去实现。

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

相关文章
相关标签/搜索