数据库实现分布式锁

1 什么是分布式锁

  • 在单体的应用开发场景中涉及并发同步的时候,大家往往采用Synchronized(同步)或者其他同一个JVM内Lock机制来解决多线程间的同步问题。
  • 在分布式集群工作的开发场景中,就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题,这种跨机器的锁就是分布式锁

image-20230228222616394

2 基于数据库实现分布式锁

基于数据库实现分布式锁主要是利用数据库的唯一索引来实现,唯一索引天然具有排他性,这刚好符合我们对锁的要求

2.1 设计原理

  • 同一时刻只能允许一个竞争者获取锁。加锁时我们在数据库中插入一条记录,利用唯一键进行防重。
  • 当竞争者A加锁成功后,第竞争者B再来加锁就会抛出唯一索引冲突,如果抛出这个异常,我们就判定竞争者B加锁失败
  • 竞争者B加锁失败后,会阻塞等待,一直到竞争者A释放锁(也就是删除记录后),再去获取锁

image-20230228222626149

2.2 实现注意事项

  • 没有锁超时机制。如果程序发生了异常,将无法删除数据,也就是锁无法被释放掉,需要自己写一套锁超时机制,比如:在表中新增一列,用于记录失效时间,并且需要有定时任务清除这些失效的数据;
  • 基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,可以考虑实现数据库的高可用方案
  • 自旋实现阻塞效果。当获取锁失败时自旋转
  • 如果使用数据库自增 id ,规律太明显
  • 受单表数据量的限制 在高并发场景下,我们都知道 MySQL 的单张表根本不可能容纳大量数据(性能等原因的限制);如果是将单表拆成多表,还是用数据库自增 id 的话,就存在了 id 重复的情况了,很显然这是业务不允许的。

db操作性能较差,并且有锁表的风险,一般不考虑。

2.3 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 */
public abstract class AbstractLock implements Lock{

/**
* 加锁,增加重试逻辑
*/
@Override
public void lock() {
//尝试获取锁
if(tryLock()){
System.out.println("---------获取锁---------");
}else {
//等待锁 阻塞
waitLock();
//重试
lock();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MysqlDistributedLock extends AbstractLock {

@Autowired
private MethodlockMapper baseMapper;

@Override
public boolean tryLock() {
try {
//插入一条数据 insert into
baseMapper.insert(new Methodlock("lock"));
}catch (Exception e){
//插入失败
return false;
}
return true;
}

@Override
public void waitLock() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

@Override
public void unlock() {
//删除数据 delete
baseMapper.deleteByMethodlock("lock");
System.out.println("-------释放锁------");
}


数据库实现分布式锁
http://example.com/数据库实现分布式锁/
作者
Panyurou
发布于
2021年12月29日
许可协议