分布式锁实现方案

01 背景

    在单机系统中,多个线程同时访问某个共享资源时,能够采用线程间加锁保证数据一致性。可是,在分布式系统中,程序运行在多台机器上,各个节点之间无从知晓共享资源的锁定状态,即这种共享资源已经不是线程级别的,而是进程之间的。node

    此时,就须要引入分布式锁,以实现多个客户端互斥访问共享资源。redis

    须要加锁的场景须要知足如下条件:算法

    一、共享资源;数据库

    二、共享资源互斥;缓存

    三、多任务环境。网络

    分布式锁的思路是:在系统中提供一个全局惟一的针对共享资源获取锁的组件,系统中须要访问共享资源时,都向该组件申请锁,待使用完毕后,释放锁。并发

02 特色

    分布式锁通常要有如下特色:分布式

    排他性:任意时刻,只能有一个客户端能获取到锁。高并发

    容错性:分布式锁服务通常要知足AP(便可用性Availability、分区容错性Partition tolerance,这与分布式事务强调一致性有区别),也就是说,只要分布式锁服务集群节点大部分存活,客户端就能够进行加锁解锁操做。性能

    避免死锁:分布式锁必定能获得释放,即便客户端在释放以前崩溃或者网络不可达。

03 方案

    针对分布式锁的实现,目前比较经常使用的方案:

    一、 基于数据库实现分布式锁

    二、 基于缓存(redis)实现分布式锁

    三、 基于Zookeeper实现分布式锁

04 DB锁

4.1 实现

    一、惟一约束

    二、基于数据库来作分布式锁的话,一般有两种作法:

    基于数据库的乐观锁(lock in share mode)

    基于数据库的悲观锁(select ... for update)

4.2 特色

    优势:

    实现简单    

    缺点:

    一、可用性差(锁的可用性依赖于数据库,若是数据库故障,则系统不可用);

    二、数据库性能存在瓶颈,不适合高并发场景;

    三、锁的失效时间难以控制,删除锁失败容易致使死锁。即这把锁没有失效时间,一旦解锁操做失败,就会致使锁记录一直在数据库中,其余线程没法再得到到锁。

    说明:通常在分布式系统中使用这种机制实现分布式锁时,须要业务侧增长控制锁超时和重试的流程。

05 Redis分布式锁

5.1 实现

   加锁和解锁的锁必须是同一个,常见的解决方案是给每一个锁一个钥匙(惟一ID),加锁时生成,解锁时判断。

    一、redis原子操做

    基于Redis实现的锁机制,主要是依赖redis自身的原子操做。

    加锁

    setnx命令加锁,并设置锁的有效时间和持有人标识:

    SET user_key user_value NX PX 100

    参数:

    NX:只在在键不存在时,才对键进行设置操做,SET key value NX 效果等同于 SETNX key value

    PX millisecond:设置键的过时时间为millisecond毫秒,当超过这个时间后,设置的键会自动失效

    解锁

    检查是否持有锁,而后删除锁:

    delete values命令删除锁

    value具备惟一性,这是避免了一种状况:假设A获取了锁,过时时间200ms,此时250ms以后,锁已经自动释放了,A去释放锁,可是此时可能B获取了锁。A客户端就不能删除B的锁了。

    二、Redlock

    使用redis作分布式锁的缺点在于:若是采用单机部署模式,会存在单点问题,只要redis故障了。加锁就不行了。

    基于以上的考虑,其实redis的做者也考虑到这个问题,他提出了一个RedLock的算法,这个算法的意思大概是这样的:

    Redlock的实现以下:

    1)获取当前时间。

    2)依次获取N个节点的锁

    3)判断是否获取锁成功。

    若是client在上述步骤中获取到了(N/2 + 1)个节点锁,而且每一个锁的过时时间都是大于0的,则获取锁成功,不然失败。失败时释放锁。

    4)释放锁。

    对全部节点发送释放锁的指令,之因此要对全部节点操做?由于分布式场景下从一个节点获取锁失败不表明在那个节点上加锁失败,可能实际上加锁已经成功了,可是返回时由于网络抖动超时了。

    三、Redisson

    Redisson是一个企业级的开源Redis Client,也提供了分布式锁的支持。

5.2 特色

   优势:

    性能好,实现起来较为方便。

    缺点:

    一、单点问题。这里的单点指的是单master,就算是个集群,若是加锁成功后,锁从master复制到slave的时候挂了,也是会出现同一资源被多个client加锁的。

    二、执行时间超过了锁的过时时间。它获取锁的方式简单粗暴,获取不到锁直接不断尝试获取锁,比较消耗性能。

    三、redis的设计定位决定了它的数据并非强一致性的,在某些极端状况下,可能会出现问题,不够健壮。即使使用redlock算法来实现,在某些复杂场景下,也没法保证其实现100%没有问题。

06 zookeeper分布式锁

6.1 方案

 

    当某客户端要进行逻辑的加锁时,就在zookeeper上的某个指定节点的目录(locker目录)下生成一个惟一的临时有序节点(locker/node_N),而后判断本身是不是这些有序节点中序号最小的一个,若是是,则算是获取了锁。若是不是,则说明没有获取到锁,那么就须要在序列中找到比本身小的那个节点,并对其调用exist()方法,对其注册事件监听,当监听到这个节点被删除了,那就再去判断一次本身当初建立的节点是否变成了序列中最小的。若是是,则获取锁,若是不是,则重复上述步骤。

    当释放锁的时候,只需将这个临时节点删除便可。

6.2 特色

    优势:

    有效的解决单点问题,不可重入问题,非阻塞问题以及锁没法释放的问题。实现起来较为简单。

  缺点:

    性能上不如使用缓存实现分布式锁,若是有较多的客户端频繁的申请加锁、释放锁,对于zk集群的压力会比较大。

07 选择

    具体选择哪一种分布式锁实现方案,须要结合业务场景,结合对性能、可靠性、复杂性的要求,具体以下:

    一、从理解的难易程度角度(从低到高)

    数据库 > 缓存 > Zookeeper

    二、从实现的复杂性角度(从低到高)

    Zookeeper >= 缓存 > 数据库

    三、从性能角度(从高到低)

    缓存 > Zookeeper >= 数据库

    四、从可靠性角度(从高到低)

    Zookeeper > 缓存 > 数据库

    综上所述:

    若是系统不想引入过多网元,能够采用数据库锁实现,好处就是比较容易理解,可是这种方案业务层控制逻辑多且复杂,须要对业务侧足够了解,易于理解可是实现复杂度最高。

    若是追求高性能,Redis是最佳选择,可是redis是有可能存在隐患的,可能会致使数据不对的状况,可靠性不如ZK。

    若是系统已经存在ZK集群,优先选用ZK实现,实现最简单,且能够提供高可靠性,性能稍逊Redis缓存方案。

相关文章
相关标签/搜索