麒麟v10 上部署 TiDB v5.1.2 生产环境优化实践
706
2023-05-27
基于SpringBoot与数据库表记录的方式实现分布式锁
同一进程内的不同线程操作共享资源时,我们只需要对资源加锁,比如利用JUC下的工具,就可以保证操作的正确性。对JUC不熟悉的同学,可以看看以下的几篇文章:
浅说SynchronizedSynchronized的优化JUC基石——Unsafe类
但是,为了高可用,我们的系统总是多副本的,分布在不同的机器上,以上同进程内的锁机制就不再起作用。为了保证多副本系统对共享资源的访问,我们引入了分布式锁。
分布式锁主要的实现方式有以下几种:
基于数据库的,其中又细分为基于数据库的表记录、悲观锁、乐观锁基于缓存的,比如Redis基于Zookeeper的
今天演示一下最简单的分布式锁方案——基于数据库表记录的分布式锁
主要的原理就是利用数据库的唯一索引(对数据库的索引不了解的同学,可以参考我的另外一篇文章mysql索引简谈)
例如,有以下的一张表:
CREATE TABLE `test`.`Untitled` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增序号', `name` varchar(255) NOT NULL COMMENT '锁名称', `survival_time` int(11) NOT NULL COMMENT '存活时间,单位ms', `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', `thread_name` varchar(255) NOT NULL COMMENT '线程名称', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_name`(`name`) USING BTREE ) ENGINE = InnoDB ROW_FORMAT = Dynamic;
其中name字段加上了唯一索引,多条含有同样name值的新增操作,数据库只能保证仅有一个操作成功,其他操作都会被拒绝掉,并且抛出“重复键”的错误。
那么,当系统1准备获取分布式锁时,就尝试往数据库中插入一条name="key"的记录,如果插入成功,则代表获取锁成功。其他系统想要获取分布式锁,同样需要往数据库插入相同name的记录,当然数据库会报错,插入失败,也就代表着这些系统获取锁失败。当系统1想要释放掉锁时,删除掉此记录即可。thread_name列可以用来保证只能主动释放自己创建的锁。
我们希望实现的分布式锁有以下的效果:
获取锁是阻塞的,获取不到会一直阻塞锁会失效,超过锁的生存时间后,会自动释放掉。这一点可以避免某些系统因为宕机而无法主动释放锁的问题
大致的流程图如下:
使用到了以下依赖:
SpringBootMyBatis-plusLombok
项目的工程目录为:
其中pom文件用到的依赖:
配置项为:
用于映射数据库字段的实体类为:
Dao层:
Service接口层:
Service实现层:
测试类如下:
将代码复制一份出来,将system1改为system2。现在,同时启动两个系统:
system1的输出如下:
system2的输出如下:
第23.037秒时,system1尝试获取锁,23.650秒时获取成功,持有分布式锁。第26秒时system2尝试获取锁,被阻塞。到27.701秒时,system1释放掉了锁,system2在27.749时才获取到了锁,在31秒时释放掉了。
现在我们将system1的业务时长改为10秒,就可以模拟出system2释放system1超时的锁的场景了。
先启动system1,再启动system2
此时system1的输出如下:
system2的输出如下:
14秒时,system1获取到了锁,接着由于业务耗时突然超出预期,需要运行10秒。在此期间,system1创建的锁超过了其存活时间。此时system2在19秒时,删除了此过期锁,接着获取到了锁。24秒时,system1回头发现自己的锁已经被释放掉了,最后system2正常释放掉了自己的锁。
基于数据库实现分布式锁,还有悲观锁与乐观锁方式,我会另开篇幅。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。