分布式锁是什么?

       分布式锁,顾名思义,就是在分布式环境下使用的锁,它能够提供进程间(当然也包括进程内)的并发控制能力。在分布式环境中,分布式锁可以保证在同一时刻只有一个实例(或节点)能够进行工作,这个工作一般称为同步逻辑,它可能是一组计算或是对存储(以及外部API)的一些操作。

分布式锁

       就功能而言,分布式锁是可以替代一般(类似JUC这种)单机锁的JUC单机锁对于锁是否可以获取,是依靠内存中的状态值来判定的,而这个状态值称为锁的资源状态。分布式锁和单机锁一样,是依靠可见性的保障来看清楚锁的资源状态,并在此基础上封装出能够提供排他性语义的并发控制装置。二者区别在于单机锁的资源状态存储在进程内,而分布式锁是在进程外,一般是某种网络存储服务

       访问资源状态的最小单位是线程,因此分布式锁控制的粒度同单机锁一样,在多线程程序中,每一时刻只有一个实例中的一个线程能够获取到分布式锁。JUC单机锁的资源状态在实例内部,如果将其移动到进程外,对资源进行获取的系统调用换成网络调用,单机锁不就转变成了分布式锁吗?没错,确实如此,但由于链路的变化会引入一些问题。

       资源状态由内到外的移动不会产生任何变化,而本地调用换成网络调用以及存储服务的加入会产生巨大的变化,主要体现在四个方面:性能、正确性、可用性以及成本问题,在实现分布式锁会遇到的问题会详细探讨它们。

使用分布式锁的原因

       分布式锁是一项比较实用的技术,使用它一般来说会有两个原因:提升效率或确保正确。如果需要在工作中使用到分布式锁,可以尝试问自己一个问题:如果没有分布式锁,会出现什么问题呢?如果是数据错乱,那就是为了确保正确,如果是避免集群中所有节点重复工作,那就是为了提升效率。

       找到使用分布式锁的原因是很重要的,因为它会让你对锁失败后产生的问题有了明确认识,同时对使用何种技术实现的分布式锁在选型上会考虑的更周全。

       比如说:如果使用分布式锁是为了解决效率问题,那么由于偶尔锁失败造成的重复计算问题应该是可以容忍的,这时选择一个成本低的分布式锁实现会是一个好选择。如果使用分布式锁是为了解决正确性问题,而且这个问题非常关键,那就需要尽可能保证分布式锁的可用性以及正确性,这时采用高成本的分布式锁实现就变得很必要了。

分布式锁的分类

       分布式锁的实现有许多种,但可以分为两类,即拉模式和推模式分布式锁,区分二者的依据在于在等待获取锁的实例(或线程)被唤醒的方式。如果需要实例自己轮询获取远端的资源状态来获取锁,这种模式就是拉模式,典型的实现是基于Redis的分布式锁。如果实例是依靠外部通知来触发它去获取锁,这就是推模式,典型的代表是基于ZooKeeper的分布式锁。

       那JUC中的单机锁属于那种模式呢?回忆一下AbstractQueuedSynchronizer(以下简称为:AQS)是如何唤醒等待者的,当拥有锁的线程释放锁时,会触发AQSrelease()方法,该方法会唤醒同步队列中等待的头节点,使之能够尝试去获取锁。唤醒等待锁的节点,就相当于事件通知该节点,因此,JUC中的单机锁可以被认为是推模式。

       拉模式的分布式锁,需要等待获取锁的实例主动以自旋的方式去查看资源状态是否有变化,并根据变化来决定是否可以去获取锁。推模式的分布式锁,资源状态的变化会以事件的形式通知到所有等待锁的实例,当实例收到通知后,就可以去获取锁,而这个通知就相当于(JUC锁的)唤醒动作。

       相比较而言,推模式具备事件驱动的特点,响应会及时一些,而拉模式则需要不断查看资源状态的变化,存在一定的无效请求。两种模式对于存储服务的诉求也是不一样的,推模式对于存储服务要求会比拉模式复杂一些。从直觉上看,推模式仿佛优于拉模式,但在实际环境中,需要综合考虑分布式锁存储服务的功能、可用性与成本等问题,并不是一个简单的选择。

RedisZooKeeper分别实现的分布式锁为例:前者吞吐量高,访问延时小,可用性一般,但实施成本低;后者吞吐量低,访问延时较高,可用性高,但实施成本高。二者将会在后续的文章中进行详细探讨。

results matching ""

    No results matching ""