文章目录
前言
在分布式系统中,想必我们经常会看到锁的应用来保证操作的原子性,使用较简单的例如对象锁,单一锁等等,再高级一点的例如读写锁等等。但是不论是单一锁或者读写锁,在使用上都具有一定的互斥性。这里的互斥性指的是当某个锁持有者持有当前的锁之后,其它线程必须进行阻塞等待操作。这种行为在具有很高workload的系统中,代价还是比较高的。从更深层次来看待这种锁,它是一种单一粒度,较为粗粒度的锁设计模式。那么在实际的应用中,我们是否能够将这种单一锁进行优化呢,使之用起来能够更为的高效。本文笔者将要讲述的将是粗粒度锁的细粒度化改造,改造的实现方式为分级锁的实现。
锁细粒度化改造的好处
为什么我们这么强调锁的细粒度化改造呢?相比于粗粒度锁,它在使用上能够带给系统怎样的帮助呢?
说到这里我们不得不谈到粗粒度锁在使用上的一些弊端,典型的一点比如它会阻塞一些毫无关联的请求操作的处理。比如某个存储系统在根目录下有A,B两个目录,为了保证系统处理请求操作的原子性,我们用锁来做其中的控制。如果我用1个锁来做,则会有一下两种情况发生:
- 系统在执行A目录下的请求操作,持有锁状态,B目录的所有操作被block住。
- 系统在执行B目录下的请求操作,持有锁状态,A目录的所有操作被block住。
但其实上面的场景系统在持有锁的情况去保护A目录的并发修改却同样block住了B目录的操作,这其实是可以避免的,我们完全可以让这2个目录的相关操作并发地执行,然后再用2个对应锁去保证这2个目录空间下的请求操作。这样的话,系统的请求吞吐量将会上升很多。
在上面的例子中从一个全局单一锁到2个命名空间目录单独锁的拆分,就是锁细粒化改造的一个简单例子。下面本文将要讲述的分级锁的设计部分也是采用了上述的思路,但是额外多了部分的优化改造,使之更适用于实际系统的使用。
分级锁的设计和实现
本节将要介绍的分级锁的主要特点在于它包含有多个层级的锁,在这里我们以两级锁为例,在此锁内,包含有2个级别锁:
- Top锁
- Child锁
在分布锁中,遵守以下规则:
在操作开始前,必须先申请得到Top锁来准备获取Child锁,在获取得到Child锁之后,可以再释放Top锁。这里的Child锁,可以理解为就是每个分区锁。这里Top锁的目的是为了保证获取各个分区锁的原子性。
分级锁原型定义如下:
/**
* LatchLock controls two hierarchical Read/Write locks:
* the topLock and the childLock.
* Typically an operation starts with the topLock already acquired.
* To acquire child lock LatchLock will
* first acquire the childLock, and then release the topLock.
*/
public abstract class LatchLock<C> {
// Interfaces methods to be defined for subclasses
/** @return true topLock is locked for read by any thread */
protected abstract boolean isReadTopLocked();
/** @return true topLock is locked for write by any thread */
protected abstract boolean isWriteTopLocked();
protected abstract void readTopdUnlock();
protected abstract void writeTopUnlock();
protected abstract boolean hasReadChildLock();
protected abstract void readChildLock();
protected abstract void readChildUnlock();
protected abstract boolean hasWriteChildLock();
protected abstract<