分布式锁简介

什么是分布式锁

  • 在单机环境中,为了防止多进程/多线程/多协程同时访问共享资源造成数据破坏,需要第三方提供一个互斥能力,这个时候可以内核或者是程序运行时(例如GoRuntime)。
  • 在分布式系统中,由于有多台机器,此时内核锁或程序运行时提供的锁无法保证多台机器在访问共享资源时互斥,此时就需要一个分布式锁服务,不同的机器通过该服务获取一把锁,获取到锁的机器就可以排他性的访问共享资源,这样的服务我们统称为分布式锁服务,锁也就叫分布式锁。

细粒度分布式锁

使用场景

适用于分布式锁频繁访问的场景,例如通过分布式锁来判断库存是否超卖。

实现

基于异步复制的分布式系统,例如 mysqlredis 等。

粗粒度分布式锁

使用场景

适用于访问锁频率比较低的场景,例如选主,服务发现。

实现

基于 paxos 协议的分布式一致性系统,例如 chubby, zookeeperetcdconsul 等。

Consul 中分布是锁的实现

Consul 分布式锁概述

Consul是基于raft(类paxos) 的分布式一致性系统,consul提供session机制来构建分布式锁,借鉴了chubby的实现,提供粗粒度的分布式锁服务。

基本概念

session

  • 构成

    • node:表示session对应的consul节点,创建session时可以指定节点,默认是创建session的那个节点。
    • health checks:一系列的健康检查。consul提供两种健康检查的方式,一种是健康检查脚本,另一种是HTTP API Interval Request的方式。健康检查可以通过consulRESTful API创建。创建session的时候可以不设置健康检查,节点宕机持有的锁不会被释放,但是consul提供了强制删除session的操作来处理这种情况。
    • TTLsession存活时间,可以通过不断地刷新TTL来保持session,这也是一种健康检查的机制,但是相比于consulgossip failure detector来说会增加consul server的压力。
    • lock-delay:获得到锁的session失效后,其他的节点并不会立马就获得锁,而是会等待一个lock-delay时间才能获取锁,这是为了防止获得锁的节点由于正在执行的任务耗时较长或节点负载高耗尽 TTL 使得 session 失效的情况下会造成数据不一致问题。
    • behaviorsession失效后对锁的处理行为,默认是release,即session失效后,其获得的锁会被释放,另一种是delete, 即session失效后,其获取的锁会被删除。
  • session 失效场景

    • 节点从consul中下线。
    • 注册的健康检查脚本下线。
    • 健康检查检查到不健康的状态。
    • session被销毁。
    • TTL过期。

KV

  • aquire

    是一钟类CAS操作,只有在没有sessin持有锁的情况下才会成功(持有锁的session可以继续获取)。成功的情况下key对应的内容会更新,LockIndex会递增,锁对应的session id会更新,表示锁由这个session持有。如果已经持有锁的session继续获取锁可以成功,key的内容会更新,但是LockIndex不会增加。

  • release

    也是类CAS操作,通过无效的session取释放锁会失败,释放锁的session不一定是持有锁的session, 因为consul提供了强制删除sesesson机制,让 session 强制失效来释放或删除锁。锁被释放后,LockIndex不会变,但是对应的session id会清空,ModiyIndex增加。

  • sequencer

    (key, LockIndex, session) 构成一个唯一的sequencersequencer可以用来验证请求是否属于当前的锁持有者。因为每次acquire 都会导致LockIndex递增,即使在已持有锁的session中重新获取锁,也能根据LockIndex判断出是不同的请求。同样,如果session失效,相应的LockIndex将为空。

实现

加锁过程

加锁流程如下:

image

释放锁的过程

释放锁的过程比较简单,首先会判断是否当前是否持有锁,如果没有持有锁,则直接返回。如果持有锁直接调用release来释放锁。

使用

Consul提供的分布式锁的api十分友好,如果不设置一些参数的化,如果不想设置lock-delay-time这些参数的化,基本就可以直接用他提供的lockunlock方法,他会自动帮你建立session并保持会话。

我这边对他的使用做了一个配置参数的简单封装。代码放到github上了 distributed-lock