在分布式系统里,我们有时执行定时任务,或者处理某些并发请求,需要确保多点系统里同时只有一个执行线程进行处理。分布式锁就是在分布式系统里互斥访问资源的解决方案。通常我们会更多地使用Redis分布式锁、Zookeeper分布式锁的解决方案。本篇文章介绍的是基于MySQL实现的分布式锁方案,性能上肯定是不如Redis、Zookeeper。对性能要求不高,并且不希望因为要使用分布式锁而引入新组件的时候,就可以考虑使用MySQL分布式锁。
简单实现方案
新建一张表,用于存储锁的信息,需要加锁的时候就插入一条记录,释放锁的时候就删除这条记录
新建一张最简单的表
CREATE TABLE `t_lock` (
`lock_key` varchar(64) NOT NULL COMMENT '锁的标识',
PRIMARY KEY (`lock_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁'
根据插入sql返回受影响的行数,大于0表示成功占有锁
insert ignore into t_lock(lock_key) values(:lockKey)
释放锁的时候就删除记录
delete from t_lock where lock_key = :lockKey
完善实现方案
以上简单实现方案没有考虑异常情况,还需要完善一下才能在生产环境使用。
可靠的MySQL分布式锁需要考虑以下场景:
- 如果持有锁的客户端崩溃了,没有释放锁,其他客户端会一直无法加锁。解决这个问题需要引入过期时间,过期之后允许其他客户端加锁。
- 加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。需要增加锁持有者的字段,加锁时标识锁的持有者,释放时仅当持有者匹配才允许释放。
- 加锁和解锁必须使用单独的数据库事务,否则加锁的sql虽然执行成功了,但是事务没有提交,其他客户端这时候也可以进行加锁,互斥就失效了。
- 高可用。MySQL如果是单点运行,崩溃之后锁就不可用了。通过配置MySQL主从高可用可以解决这个问题。
完善的表结构示例
CREATE TABLE `t_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`lock_key` varchar(64) NOT NULL COMMENT '锁的标识',
`owner` char(36) NOT NULL COMMENT '锁的持有者',
`expire_seconds` int(11) NOT NULL COMMENT '过期时间,单位为秒',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `ukey_lock_key_owner` (`lock_key`,`owner`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁'
加锁
insert into t_lock(lock_key, owner, expire_seconds)
select :lockKey, :owner, :expireSeconds
from (select 1) as T
where exists (
select count(id)
from tb_distributed_lock
where lock_key = :lockKey and expire_seconds >= TIMESTAMPDIFF(SECOND, create_time, NOW())
)
解锁
delete from t_lock where lock_key = :lockKey and owner = :owner
开箱即用
本人按照以上实现思路,封装成开箱即用的使用方式,如下:
添加maven依赖
com.github.zheng93775
mysql-lock
1.0.0
mysql
mysql-connector-java
5.1.40
classpath下配置好mysql-lock.properties
mysql-lock.url=jdbc:mysql://127.0.0.1:3306/test
mysql-lock.username=db_user
mysql-lock.password=db_pass
代码里直接使用MLock
MLock mLock = new MLock("DailyJob");
try {
if (mLock.tryLock()) {
// TODO add your code here
}
} finally {
mLock.unlock();
}
更多的高级用法参见源码地址:https://github.com/zheng93775/mysql-lock/
另外还封装了SpringBoot的自动配置版本,只要引入jar包即可零配置使用MLock(Bean容器里要有DataSource)。源码地址:https://github.com/zheng93775/spring-boot-starter-mysql-lock/
SpringBoot工程添加一下依赖即可
com.github.zheng93775
spring-boot-starter-mysql-lock
1.0.0
mysql
mysql-connector-java
5.1.40