上一篇文章我们介绍了什么是分布式锁和分布式锁的一些基本概念。这篇文章我们来讲解一下基于数据库如何实现分布式锁。
基于数据库实现分布式锁
基于数据库实现分布式锁可以分为两种方式,分别是基于数据库表和基于数据库排他锁。
基于数据库表
要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现。当我们要锁住某个方法或者资源时,我们就在这张表中增加一条记录,想要释放锁的时候就删除这条记录。
当我们想要锁住某个方法时,执行以下SQL语句:
insert into methodLock(method_name,desc) values ('method_name','desc');
因为我们对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法的具体内容。
当方法执行完成后,想要释放锁时,可以执行以下SQL语句:
delete from methodLock where method_name = 'method_name';
但是上面这种简单的实现存在以下四个问题:
(1)这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用;
(2)这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁;
(3)这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁的动作;
(4)这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
针对以上的问题,我们可以通过以下方式来进行解决:
(1)可以使用数据库集群来代替单点数据库;
(2)可以定义一个定时任务,每隔一定时间把数据库中的超时数据清理一遍来解决锁没有超时时间的问题;
(3)可以使用while循环进行不断的insert尝试,直到成功,解决锁是非阻塞的问题;
(4)可以在数据库中加入一个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁时先查询数据库,看这个字段记录的是否是当前获取锁的主机信息和线程信息,如果是,那就直接进行锁的分配即可。
基于数据库排他锁
除了可以通过增删操作数据表中的记录以外,还可以借助数据库自带的锁来实现分布式锁。
基于MySQL的InnoDB引擎,可以使用以下方式来进行加锁操作:
public boolean lock(){connection.setAutoCommit(false);while(true){try{result = select * from methodLock where method_name = ... for update;if(result == null){return true;}}catch(Exception e){}sleep(1000);}return false;
}
在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁(InnoDB引擎在加锁的时候,只有通过索引进行检索的时候才会使用行锁,否则会使用表锁。这里我们希望使用行锁,就要给method_name添加索引。这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上)。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。
我们可以认为获得排他锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,再通过以下方式进行解锁:
public void unlock(){connection.commit();
}
通过connection.commit()操作来释放锁,可以有效的解决上面提到的无法释放锁和非阻塞锁问题。for update语句会在执行成功之后立即返回,在执行失败时会一直处于阻塞状态,直到成功;使用这种方式,出现意外时数据库会自动将锁释放掉。但是这种方法还是无法直接解决掉数据库单点和锁不可重入问题。
使用这种方法还会带来新的问题,虽然我们对method_name使用了唯一索引,并且显示使用for update来使用行锁。但是,MySQL会对查询进行优化,即使在条件中使用了索引字段,但是否使用索引来进行检索还是由MySQL通过判断不同执行计划的代价来决定。如果最终MySQL走了表锁而没有走行锁,那么这种方式就失效了;另一个问题是,如果我们使用排他锁来实现分布式锁,那么一个排他锁长时间不提交,就会占用数据库连接。一旦类似的连接变得多了,就可能把数据库连接池撑爆。
总结
依赖数据库实现分布式锁有两种方法,一种是基于数据库表,一种是基于数据库的排他锁。直接借助数据库实现分布式锁容易理解,但是其实现上会有各种各样的问题,解决这些问题会使得方案变得复杂,并且操作数据库会带来一定的性能开销,行锁的使用也不想我们认为的那样可靠,尤其是表比较小的时候很可能不走索引导致使用表锁。
这篇文章我们讲解了如何基于数据库实现分布式锁,大家有什么问题或者勘误可以在评论区留言,笔者看到都会回复的。