1、分布式锁简介
为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
- 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。
- 分布式情况下之所以问题变得复杂,主要就是需要考虑到网络的延时和不可靠。
2、我们需要怎样的分布式锁
- 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器-上的一个线程执行。
- 这把锁要是一把可重入锁(避免死锁)。
- 有高可用的获取锁和释放锁功能。
- 获取锁和释放锁的性能要好。
3、分布式锁三种实现方式
- 基于数据库实现
- 基于缓存(Redis等)实现
- 基于Zookeeper实现
3.1、基于数据库实现分布式锁
悲观锁
利用select … where … for update排他锁,for update nowait非等待,,其它线程进入时,直接抛出已经锁,无须等待,for update等前面线程执行完后,才依次执行,排队等待。
注意:其他附加功能与实现一基本一致,这里需要注意的是“where name=lock " , name字段必须要走索引,否则会锁表。有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题。
乐观锁
所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才能觉察到。我们的抢购、秒杀就是用了这种实现以防止超卖。
通过增加递增的版本号字段实现乐观锁。
3.2、基于缓存(Redis等)实现分布式锁
在使用Redis实现分布式锁的时候,主要就会使用到以下三个命令∶
-SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
-expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
-delete
delete key:删除key。
实现思想:
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
基于Redisson可重入锁示例:
RLock lock = redisson.getLock("anyock");
//最常见的使用方法
lock.lock();
//加锁以后1o秒钟自动解锁
//无需调用unlock方法手动解锁
lock.lock(10,TimeUnit.sECONDS);
//尝试加锁,最多等待1oo秒,上锁以后1o秒自动解锁
boolean res = lock.tryLock(100,10,TimeUnit.sECONDS);
if (res) {
try {
...
} finally {
lock.unlock ();
}
}
基于Redisson公平锁锁示例:
RLock fairLock = redisson.getFairLock("anyock");
// 最常见的使用方法
fairLock.lock();
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
fairLock.lock(10,TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = fairLock.tryLock(100,10,TimeUnit.SECONDS);
...
fairLock.unlock();
3.3、基于Zookeeper实现分布式锁
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。
实现步骤:
(1)创建一个目录mylock ;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。