4.1 、实现思路
基于数据库的乐观锁实现分布式锁通常利用唯一索引或版本号机制来确保在高并发场景下的锁定操作。乐观锁适合在冲突较少的场景中使用,依赖于更新时的数据状态一致性判断。以下是一个基于数据库乐观锁的分布式锁实现示例。创建一张optimistic_lock
表:
CREATE TABLE `optimistic_lock` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`lock_name` varchar(50) DEFAULT NULL,
`expire_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '锁过期时间',
`lock_status` int(255) NOT NULL DEFAULT '0' COMMENT '0--正常 1--被锁',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_lock_name` (`lock_name`) USING BTREE
) ENGINE = InnoDB DEFAULT CHARSET=utf8 COMMENT='乐观锁实现分布式锁'
在锁名字段上增加唯一索引,其实现思路是通过数据库的更新数据是否成功能判断是否获取到锁,所以我们要提前将锁名任务添加到表中,expire_at
为锁过期时间,防止未及时释放导致死锁,这里可以通过定时任务删除过期的锁。
4.2 、代码实现
基于数据库乐观锁实现分布锁主要有两个方法:
@Resource
private JdbcTemplate jdbcTemplate;
public boolean lock(String lockName) {
try {
String sql = String.format("update optimistic_lock set lock_status=1, expire_at = NOW() + INTERVAL 1 MINUTE where lock_name ='%s' and lock_status = 0 ;", lockName);
return jdbcTemplate.update(sql) == 1;
} catch (Exception e) {
return false;
}
}
public void unLock(String lockName) {
String sql = String.format("update optimistic_lock set lock_status=0 ,expire_at=now() where lock_name='%s' ;", lockName);
jdbcTemplate.update(sql);
}
4.3 、测试代码
@Resource
private OptimisticLock optimisticLock;
@Test
void testOptimisticLock() {
String lockName = "Hanson";
IntStream.range(1, 10).parallel().forEach(x -> {
try {
if (optimisticLock.lock(lockName)) {
log.info("get lock success");
} else {
log.warn("get lock error");
}
} finally {
optimisticLock.unLock(lockName);
}
});
}
4.4、小结
基于数据库乐观锁的分布式锁具有以下优缺点:
优点:
- 实现简单:易于理解和实现,可以直接利用现有数据库,无需额外分布式中间件。
- 数据库天然一致性:利用数据库的事务和一致性机制,保证并发场景下的数据一致性。
- 适用于小规模系统:对于低并发系统,乐观锁可以有效满足需求,避免引入复杂中间件。
缺点:
- 性能瓶颈:数据库不适合处理高并发锁操作,频繁的读写操作会给数据库带来压力。
- 冲突处理复杂:乐观锁在冲突时需要重试,可能导致操作延迟。
- 锁粒度问题:基于记录的锁粒度较粗,可能导致资源争用。
- 不适合高并发场景:高并发下冲突率增加,重试操作影响性能和响应时间。
- 数据库单点问题:依赖单个数据库节点可能导致单点故障。
- 锁过期处理复杂:数据库锁缺乏自动过期机制,可能导致操作阻塞。