1. 基于数据库实现分布式锁
在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。(性能不是特别高)
创建一个表
DROP TABLE IF EXISTS `method_lock`;
CREATE TABLE `method_lock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`method_name` varchar(64) NOT NULL COMMENT '锁定的方法名',
`desc` varchar(255) NOT NULL COMMENT '备注信息',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
想要执行某个方法,就使用这个方法名向表中插入数据。
INSERT INTO method_lock (method_name, desc) VALUES ('methodName', '测试的methodName');
由于对method_name做了唯一性约束,如果有多个请求同时提交到数据库,数据库会保证只有一个操作可以成功,那么就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。
成功插入则获取锁,执行完成后删除对应的行数据释放锁。
delete from method_lock where method_name ='methodName';
2. 基于Redis实现分布式锁
获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值可以为一个随机生成的UUID,通过此在释放锁的时候进行判断。
获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
setnx并不是原子操作,建议使用setex。另外基于Redission也可以实现分布式锁。
3. 基于Zookeeper实现分布式锁
基于ZooKeeper实现分布式锁的步骤如下:
(1)创建一个目录mylock。
(2)线程A想获取锁就在mylock目录下创建临时顺序节点。
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁。
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点。
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
4. 总结
ZooKeeper版本的分布式锁问题相对比较来说少。
锁的占用时间限制:redis就有占用时间限制,而ZooKeeper则没有,最主要的原因是Redis目前没有办法知道已经获取锁的客户端的状态,是已经挂了呢还是正在执行耗时较长的业务逻辑。而ZooKeeper通过临时节点就能清晰知道,如果临时节点存在说明还在执行业务逻辑,如果临时节点不存在说明已经执行完毕释放锁或者是挂了。由此看来redis如果能像ZooKeeper一样添加一些与客户端绑定的临时键,也是一大好事。
是否单点故障:Redis本身有很多中玩法,如客户端一致性hash,服务器端sentinel方案或者cluster方案,很难做到一种分布式锁方式能应对所有这些方案。而ZooKeeper只有一种玩法,多台机器的节点数据是一致的,没有Redis的那么多的麻烦因素要考虑。
Kotlin开发者社区
专注分享 Java、 Kotlin、Spring/Spring Boot、MySQL、redis、neo4j、NoSQL、Android、JavaScript、React、Node、函数式编程、编程思想、"高可用,高性能,高实时"大型分布式系统架构设计主题。