1 分布式锁的三种实现方式
第一:基于数据库
第二:基于缓存
第三:基于Zookeeper
2 基于数据库实现分布式锁
要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。
当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。
CREATE TABLE `methodLock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
`desc` varchar(1024) NOT NULL DEFAULT '备注信息',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
当我们想要锁住某个方法时,执行以下SQL:
insert into methodLock(method_name,desc) values (‘method_name’,‘desc’);
方法执行完成后,释放锁:
delete from methodLock where method_name ='method_name'
上述方法看起来原理很简单,但是会存在一些问题?
- 这把锁没有失效是时间,一旦解锁失败,就会导致锁一直在数据库里边,导致业务系统不可用。
- 这把锁是非阻塞的,不管成功还是失败都是立即返回。这把锁是非阻塞的,不管成功还是失败都是立即返回的。
- 这个锁是非重入的。
要解决上述问题,其实也简单,可通过下边的操作来避免上述问题。
这把锁没有失效是时间?有两个方案:
- 使用Spring的嵌套事务来操作。使用分布式锁的时候就起事务,业务代码再起一个事务,使用的传播行为是required new,可以在上层事务回滚后不影响内层事务;
- 一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
非阻塞的?
- 搞一个while循环,不断的往数据库里边插入数据,直到插入成功为止或者到期为止。
这个锁是非重入的?
在数据行记录中,记录下线程的信息,当获取锁的时候,先判断线程是否是当前线程,是的话,返回成功,不是的话,返回失败。
3 基于缓存
相对于基于数据库实现分布式锁的方案来说,基于缓存的实现在性能上能有很大的优势;
目前成熟的缓存数据库有很多,redis、memcached以及阿里能内中缓存中间件tair等
实现的原理是加锁时,利用往缓存里边写入一条数据,其中某一个字段是唯一的。
锁释放时手动删除这个记录。
这个方案也存在一些问题:
- 这把锁是非重入锁
- 这把锁是非阻塞的。
非重入锁?
和数据库实现分布式锁类似,其实问题还是一个问题,解决方案也是类似的。
在存储的数据记录中查询线程信息,如果和当前线程是一致的就直接获取锁,不一致就获取锁失败。
非阻塞的?
同样的加一个循环,直到加锁成为未知。
3 基于zookeeper
可以基于zk的临时节点做分布式锁。
大致思想就是客户端在对某个方法加锁时,都会在相应的方法节点下加一个临时有序节点,在客户端执行完业务,会释放链接,临时节点自然删除。判断是否获取锁的逻辑是判断当前临时节点序号是否是做小的,如果是则获取到了锁。