分布式锁的原理和实现详解

1、基本概念

分布式锁,是单机锁的一种扩展,主要是为了锁住分布式系统中不一样机器代码的物理块或逻辑块。以此保证不一样机器之间的逻辑一致性。

2、一个简单的案例

对DB写操做的双检锁案例

  1. 伪代码以下算法

    if (能够插入一条数据) {
        lock {
            if (能够插入一条数据) {
                // 插入一条数据
            }
        }
    }
  2. 上面代码中的lock若是是单机状况下的锁的话,在一台服务器多线程状况下是没有问题的。可是若是是分布式的状况下,单机的锁只能锁住一台服务器的物理代码块,是没法防住其余机器产生的脏数据的。
  3. 分布式状况下这个lock就须要换成分布式锁以保证数据一致性。

3、分布式锁实现原理

所需的依赖

  1. 一个拥有强一致性的服务发现存储仓库。(保证数据一致性)
  2. 一个具备高可用性的服务发现存储仓库。(保证服务的稳定性)
  3. 在这里咱们使用etcd做为服务发现存储仓库。

具体实现原理

咱们本次所使用的etcd的能力

  1. etcd节点的保持独占能力。(同一时间只有一台服务器能够拥有节点)
  2. 节点事件监听能力。(能够在节点被释放/占有时通知观察者-服务器)
  3. 节点主动释放能力。(服务器能够主动释放拥有节点)
  4. 节点超时自动释放能力。(超时自动释放节点)

在代码中,须要准备如下几个内容

  1. etcd长监听。(监听etcd节点,在节点变动时做出响应)
  2. 事件队列。(存放争夺etcd节点的方法)
  3. 争夺锁事件。
  4. 等待锁超时事件。(没画在流程图中,在争夺锁事件入队列时注册,持有锁时注销)
  5. 持有锁超时事件。(没画在流程图中,在持有锁时注册,在释放锁时注销)
  6. 释放锁事件。

基本代码流程图

  • 正常代码流程图(markdown画的,有点丑)设计模式

    st=>start: 项目启动
    watcher=>operation: 监听etcd
    ed=>end: 等待请求
    
    st->watcher->ed
    st=>start: 开始执行lock
    ed=>end: 结束
    watcher=>operation: 监听etcd
    hasEvent=>condition: 事件队列为空
    pushEvent=>operation: 争夺锁事件 入等待队列
    watcherEvent=>operation: etcd节点超时事件和释放事件
    popEvent=>operation: 争夺锁事件 出事件队列
    execute=>operation: 执行被锁住的代码块
    fight=>condition: 争夺锁失败
    unlock=>operation: 释放锁
    
    st->fight
    fight(no)->execute->unlock->ed
    fight(yes)->pushEvent->watcherEvent->popEvent->fight
  • 事件细节伪代码缓存

    1. 准备一个清空全部相关事件的万能方法服务器

      假设eventQueue是这种业务对应的事件队列
      假设fightEvent是该次执行的争夺锁事件
      假设clear为清除超时事件的方法
      假设holdEvent为持有锁超时事件
      假设waitEvent为等待锁超时事件
      假设unLockEvent为释放锁事件
      
      // 清空全部事件 - clearAll
      eventQueue.remove(fightEvent)
      clear(waitEvent)
      clear(holdEvent)
    2. 监听etcd的两个事件markdown

      // 当etcd锁超时事件或etcd主动释放事件发生时
      
      // 争夺锁事件 出事件队列
      fightEvent = eventQueue.pop
      // 执行争夺锁事件
      执行 fightEvent
    3. 争夺锁事件多线程

      // 争夺锁事件
      执行 etcd争夺锁方法
      if (抢到锁了) {
          // 清空全部事件
          执行 clearAll
          // 设置持有锁超时事件
          设置 holdEvent
      } else {
          // 争夺锁事件若是是二次入队列,建议到队列头,而不是到队列尾
          eventQueue.push(fightEvent)
      }
    4. 等待锁超时事件(用时间轮延时执行)分布式

      // 清空全部事件
      执行 clearAll
      // 抛出异常
      throw new Exception
    5. 持有锁超时事件(用时间轮延时执行)spa

      // 执行释放锁事件
      执行 unLockEvent
    6. 释放锁事件线程

      // 释放锁事件
      执行 etcd释放锁方法
      // 清空全部事件
      执行 clearAll
  • 备注设计

    以上代码能够用有限状态机设计模式来设计业务
  • 图解分布式服务器与etcd集群交互

    1. 项目启动时初始化,服务器A、B、C是一个分布式系统(不考虑哪台为master)
      clipboard.png
    2. 同时发来10个请求,此时通过算法分配假设变成了这样
      clipboard.png
    3. 开始争夺锁时,10个线程同时向etcd争夺锁
      clipboard.png
    4. 假设请求2争夺成功,那么第一次竞争完毕后除了请求2,其余请求都进入了各自的事件队列等待etcd释放锁的通知
      clipboard.png
    5. 当请求2执行完代码块后,向etcd发送释放锁请求
      clipboard.png
    6. etcd收到释放锁请求,将节点删除,触发compareAndDelete事件(释放锁事件)
      clipboard.png
    7. 三台服务器接收到释放锁的信息,各自让事件队列头的请求向etcd发送争夺锁请求
      clipboard.png
    8. 锁抢完后,重复4->6的行为
      clipboard.png
  • 补充说明

    1. etcd还有一个功能是控制时序,这样的话就能够将每一个请求抢锁的行为控制到只剩一次,业务执行的顺序由etcd控制,不过我没有试过,有兴趣的同窗能够试一试。
    2. 以前基于分布式锁的原理还写了一个分布式缓存锁,是经过锁缓存来防止缓存击穿的,有空的时候补上。感谢各位同窗看个人文章看到此处。
相关文章
相关标签/搜索