基于数据库的分布式锁方案:
锁的表结构
1
2
3
4
5
6
7
8
9
10
11
12
13CREATE TABLE `resource_lock`
(
`keyResource` varchar(45) COLLATE utf8_bin NOT NULL DEFAULT '资源主键',
`lockStatus` char(1) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'S-成功,F-失败,P-过期',
`lockFlag` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '1是已经锁 0是未锁',
`beginTime` datetime DEFAULT NULL COMMENT '开始时间',
`endTime` datetime DEFAULT NULL COMMENT '结束时间',
`UUID` char(36) COLLATE utf8_bin NOT NULL DEFAULT '抢到锁的节点的UUID',
`time` int(10) unsigned NOT NULL DEFAULT '60' COMMENT '方法生命周期内只允许一个结点获取一次锁,单位:分钟',
PRIMARY KEY (`keyResource`) USING BTREE
) ENGINE = InnoDB
DEFAULT CHARSET = utf8
COLLATE = utf8_binResourceMapper.xml 自定义 SQL 语句 :
使用
select for ... update
:查询数据的同时为数据加写锁,直到事务提交时释放。注意,在Springboot+Mybatis中,数据源底层为每一次数据库操作(包括查询操作,即使它不修改数据)都自动执行了事务提交操作,所以在下面的 service层 要使用 @Transactional 关闭自动提交,否则一加上锁就会被立刻释放。
**getLock(Param:keyResource)**:
1
2
3
4
5<select id="getLock" parameterType="java.lang.String" resultType="com.michael.usercenter.model.domain.ResourceLock">
select * from 'resource_lock'
where keyResource = #{keyResource,jdbcType=VARCHAR}
for update
</select>
ResourceLockService 接口设计:(仿照Redisson,设计 getLock 和 tryLock 的原因在上面 Redisson分布式锁小节有解释)
**ResourceLock: getLock(String keyResource)**:
@Transactional:主要的作用是关闭自动提交。
如果不关闭自动提交,在下面调用
mapper.getLock()
中,select for update本身也会触发事务提交,导致锁立刻失效。调用
mapper.getLock()
,返回数据库中资源的锁lock
如果
lock
为空,则创建lock
:keyResource
设为当前资源keystatus
设为 SlockFlag
设为1beginTime
设为当前时间time
数据库默认为60min,表示锁的生效时长endTime
=beginTime
+time
- 生成UUID并填入
- 向数据库中更新
lock
返回
lock
boolean: tryLock(String keyResource, int waitTime, int leaseTime):
- @Transactional
- 开始
waitTime
计时:- 调用
mapper.getLock()
,获取lock
- 检查
lock
的字段:- 如果
lockFlag
为1且status
为P,表示正在被占用,如果计时还未结束,则重复上一步 - 如果
lockFlag
为1且status
为F,表示正在被占用但是已经过期,如果计时还未结束,则重复上一步(TODO 锁的过期管理机制) - 如果
lockFlag
为0且status
为S,表示锁空闲,进行下一步
- 如果
- 如果计时结束,则直接返回false
- 调用
- 如果锁空闲:
beginTime
设为当前时间time
设为leaseTime
的值endTime
=beginTime
+time
lockFlag
设为1status
设为P- 生成UUID并填入
- 向数据库中更新
lock
- 返回true
boolean: lock(String keyResource, int leaseTime):
TODO
void: unlock(String keyResource, String UUID):
unlock() 相当于 Redisson 的 isHeldByCurrentThread() + unlock(),但是 Redisson 用 lua 脚本保证了这一组合的原子性。这里不打算引入 lua 脚本,所以整合为一个方法,配合行级锁来保证原子性。
- @Transactional
- 调用
mapper.getLock()
,得到lock
- 将
lock.UUID
与UUID
比较。 - 如果相等:
lockFlag
设为0status
设为S- 向数据库更新
lock
- 如果不等,则直接返回