Java中的锁主要包括synchronized锁和JUC包中的锁,这些锁都是针对单个JVM实例上的锁,对于分布式环境如果我们需要加锁就显得无能为力。在单个JVM实例上,锁的竞争者通常是一些不同的线程,而在分布式环境中,锁的竞争者通常是一些不同的线程或者进程。如何实现在分布式环境中对一个对象进行加锁呢?答案就是分布式锁。
分布式锁实现方案
目前分布式锁的实现方案主要包括三种:
基于数据库
基于缓存(Redis,memcached,tair)
基于Zookeeper
基于数据库实现分布式锁主要是利用数据库的唯一索引来实现,唯一索引天然具有排他性,这刚好符合我们对锁的要求:同一时刻只能允许一个竞争者获取锁。加锁时我们在数据库中插入一条锁记录,利用业务id进行防重。当第一个竞争者加锁成功后,第二个竞争者再来加锁就会抛出唯一索引冲突,如果抛出这个异常,我们就判定当前竞争者加锁失败。防重业务id需要我们自己来定义,例如我们的锁对象是一个方法,则我们的业务防重id就是这个方法的名字,如果锁定的对象是一个类,则业务防重id就是这个类名。
基于缓存实现分布式锁:理论上来说使用缓存来实现分布式锁的效率最高,加锁速度最快,因为Redis几乎都是纯内存操作,而基于数据库的方案和基于Zookeeper的方案都会涉及到磁盘文件IO,效率相对低下。一般使用Redis来实现分布式锁都是利用Redis的SETNX key value这个命令,只有当key不存在时才会执行成功,如果key已经存在则命令执行失败。
基于Zookeeper:Zookeeper一般用作配置中心,其实现分布式锁的原理和Redis类似,我们在Zookeeper中创建瞬时节点,利用节点不能重复创建的特性来保证排他性。
在实现分布式锁的时候我们需要考虑一些问题,例如:分布式锁是否可重入,分布式锁的释放时机,分布式锁服务端是否有单点问题等。
基于数据库实现分布式锁
上面已经分析了基于数据库实现分布式锁的基本原理:通过唯一索引保持排他性,加锁时插入一条记录,解锁是删除这条记录。下面我们就简要实现一下基于数据库的分布式锁。
表设计
CREATE TABLE `distributed_lock` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`unique_mutex` varchar(255) NOT NULL COMMENT '业务防重id',
`holder_id` varchar(255) NOT NULL COMMENT '锁持有者id',
`create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `mutex_index` (`unique_mutex`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
id字段是数据库的自增id,unique_mutex字段就是我们的防重id,也就是加锁的对象,此对象唯一。在这张表上我们加了一个唯一索引,保证unique_mutex唯一性。holder_id代表竞争到锁的持有者id。
加锁
insert into distributed_lock(unique_mutex, holder_id) values ('unique_mutex', 'holder_id');
如果当前sql执行成功代表加锁成功,如果抛出唯一索引异常(DuplicatedKeyException)则代表加锁失败,当前锁已经被其他竞争者获取。
解锁
delete from methodLock where unique_mutex='unique_mutex' and holder_id='holder_id';
解锁很简单,直接删除此条记录即可。
分析
是否可重入:就以上的方案来说,我们实现的分布式锁是不可重入的,即是是同一个竞争者,在获取锁后未释放锁之前再来加锁,一样会加锁失败,因此是不可重入的。解决不可重入问题也很简单:加锁时判断记录中是否存在unique_mutex的记录,如果存在且holder_id和当前竞争者id相同,则加锁成功。这样就可以解决不可重入问题。
锁释放时机:设想如果一个竞争者获取锁时候,进程挂了,此时distributed_lock表中的这条记录就会一直存在,其他竞争者无法加锁。为了解决这个问题,每次加锁之前我们先判断已经存在的记录的创建时间和当前系统时间之间的差是否已经超过超时时间,如果已经超过则先删除这条记录,再插入新的记录。另外在解锁时,必须是锁的持有者来解锁,其他竞争者无法解锁。这点可以通过holder_id字段来判定。
数据库单点问题:单个数据库容易产生单点问题:如果数据库挂了,我们的锁服务就挂了。对于这个问题,可以考虑实现数据库的高可用方案,例如MySQL的MHA高可用解决方案。