分布式锁之数据库锁

来源:互联网 发布:qq邮箱smtp设置 端口25 编辑:程序博客网 时间:2024/06/12 00:38

在分布式环境下经常会出现这样的需求,多个服务器节点调用远程服务器的某项资源,但是这样的资源在同一时间点只允许一个服务器节点使用,类似于这样机器与机器之间的并发无法通过传统java并发API来解决.于是便有了分布式锁

数据库锁是并发锁的一种实现

分布式锁需要满足以下两个条件

  1. 在分布式环境下,在同一时间只能被一台机器的一个线程执行
  2. 为了避免死锁,分布式锁是一把可重入锁
  3. 锁的获取和释放必须高性能
CREATE TABLE `resource_lock` (  `id` int(11) NOT NULL AUTO_INCREMENT  `resource_name` varchar(64) NOT NULL ,  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',  `holder_info` varchar(64) NOT NULL,  PRIMARY KEY (`id`),  UNIQUE KEY `uidx_resource` (`resource_name`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '资源锁表';

锁表的关键点在于唯一索引 UNIQUE KEY,这一条件保证了在任何时候每个资源只能有一个主机能够获取该资源.而holder_info记录了获取资源的是谁,这个字段使分布式锁支持可重入性,从而避免了死锁

如果我们锁住某个资源资源的时候,我们首先获取资源,获取资源的方法是在该表中添加一个资源,如果添加成功,则获取资源成功,反之失败

这个分布式锁仍然存在如下问题:
1.数据库必须是集群状态,若是单点数据库,一旦数据库瘫痪,整个系统都将处于不可用状态
2.如果某台主机获取了该锁,但是因为意外情况宕机,导致该资源一直处于占用状态.所以需要在应用层加上定时task,根据updateTime清除到时但是没有被释放的锁

如果我们需要该锁支持阻塞,也就是当节点没有获取到锁的时候就等待,直到获取锁,我们在插入数据(也就是获取锁)失败的情况下,使用mysql悲观锁锁定该锁,mysql代码如下

-- 获取锁代码如下begin;select * from resource_lock where resource_name='resource' for update
-- 业务逻辑dosomething()
-- 释放锁代码如下commit;

该方法有几个注意点:

1.务必对resource_name添加索引,否则mysql将会锁全表,这会造成其它线程都无法申请锁
2.其它线程等待获取锁的时候阻塞时间是有限制的,超时后mysql会报如下的错误

 insert into resource_lock values(1,'resource',current_timestamp,'holder_info');

lock wait time可以自己更改mysql配置

3.我们要使用排它锁进行分布式锁的lock,如果一个排他锁不提交,会长时间占用数据库连接池连接,如果这样的连接变多,可能会造成其它线程获取mysql连接失败

基于数据库的分布式锁优缺点分析
优点:
1.直接借助数据库容易理解
2.数据库锁具有持久化特性
缺点
1.操作数据库会有性能开销
2.数据库阻塞锁的实现可能会造成占用大量数据库连接池连接导致其它线程无连接可用