分布式锁的实现方案

简介

分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性。

分布式锁需要具备的条件:

  • 互斥性:在任意一个时刻,只有一个客户端持有锁。
  • 无死锁:即使持有锁的客户端崩溃或者其他意外事件,锁仍然可以被获取。
  • 容错:只要大部分Redis节点都活着,客户端可以获取和释放锁。

分布式锁的主要实现方式:

  • 数据库
  • Redis等缓存数据库,redissetnx
  • Zookeeper 临时节点

Redis 实现分布式锁

第一阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void hello() {
Integer lock = get("lock");
if(lock == null){
set("lock",1);
//todo
del("lock");
return;
}else {
//自旋
hello();
}
}



存在的问题:

  • 请求同时打进来,所有请求都获取不到锁

第二阶段

使用setnx设置锁

setnx在key不存在时可以设置,存在当前key则不操作

1
2
3
4
5
6
7
8
9
10
11
12
public void hello(){
String lock = setnx("lock"); //0代表没有保存数据,说明有人占坑了,1代表占坑成功
if(lock != 0){
//todo
del("lock");
}else {
//自旋
hello();
}
}


存在的问题:

  • 由于各种问题(未捕获的异常,断电)等导致锁未释放,其他人永远获取不到锁

第三阶段

1
2
3
4
5
6
7
8
9
10
11
public void hello(){
String lock = setnx("lock");
if(lock != 0){
expire("lock",10s);
//todo
del("lock");
}else {
hello();
}
}

存在的问题:

  • 刚拿到锁,服务器出现问题,没来得及设置过期时间

第四阶段

1
2
3
4
5
6
7
8
9
10
11
public void hello() {
String result = setnxex("lock",10s);
if(result == "ok"){
//加锁成功
//执行业务逻辑
//释放锁
}else {
hello();
}
}

存在的问题:

  • 如果业务逻辑超时,导致锁自动删除,业务执行完又删除了一遍,至少多个人都获取到了锁。

第五阶段

1
2
3
4
5
6
7
8
9
10
public void hello() {
String token = UUID;
String result = setnxex("lock",token,10s);
if(result == "ok"){
//执行业务
//释放锁,保证删除自己的锁
get("lock") == token ? del("lock") : return;
}
}

存在的问题:

  • 获取锁的时候,锁的值正在返回的过程中,锁过期,redis删除了锁,但是我们拿到了值,对比成功(此时另一个请求创建了锁),但是该线程又删除了一次锁。

原因:

  • 删锁不是原子性的
  • 解决:Lua脚本

第六阶段

1
2
3
4
5
6
7
8
9
10
11
public void hello() {
String token = UUID;
String lock = redis.setnxex("lock",token,10s);
if(lock == "ok"){
//执行业务
//脚本删除锁
}else {
hello();
}
}

Redisson实现分布式锁

1
2
3
4
5
6
7
public void hello() {
Rlock lock = redisson.getLock("lock");
lock.lock();
//todo
lock.unlock();
}

其他方向考虑

  • 自旋
    • 自旋次数
    • 自旋超时
  • 原子的加锁释放没问题
  • 锁设置
    • 锁粒度:细,记录级别
      • 查询商品详情:进缓存->击穿,穿透,雪崩,不同ID的商品不同的锁
      • 分析好粒度,不要锁住无关数据,一种数据一种锁,一条数据一个锁
  • 锁类型
    • 读写锁
    • 可重入锁

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!