05-分布式锁

分布式锁

  • 分布式锁是一种在分布式系统中协调多个进程或服务对共享资源进行互斥访问的机制。它的核心目标是确保在分布式环境下,同一时刻只有一个节点(或实例)能够访问或操作某个共享资源(如数据库、文件、缓存等),从而避免数据不一致或并发冲突。
  • 为何需要分布式锁
    • 避免不同节点重复相同的工作:比如用户执行了某个操作有可能不同节点会发送多封邮件;
    • 避免破坏数据的正确性:如果两个节点在同一条数据上同时进行操作,可能会造成数据错误或不一致的情况出现;

基于数据库实现

  • 它的基本原理和 Redis 的 SETNX 类似,其实就是创建一个分布式锁表,加锁后,我们就在表增加一条记录,释放锁即把该数据删掉
  • 缺点
    • 没有失效时间,容易导致死锁;
    • 依赖数据库的可用性,一旦数据库挂掉,锁就马上不可用;
    • 这把锁只能是非阻塞的,因为数据的 insert 操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作;
    • 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据库中数据已经存在了。

基于Redis实现

逐步演进

set NX

  • 当 key 不存在时,设置 key 的值,存在时,什么都不做
  • 缺点
    • 当设置锁的客户端发生意外无法解锁时,会导致死锁
  • 优化: 设置过期时间

set <key> <value> EX <s> NX

  • 如果set NX 和 设置国企时间分步执行的话,还是可能导致死锁,所以要使用原子指令同时设置 key 以及 过期时间
  • 缺陷
    • 用户 A 先抢占到了锁,并设置了这个锁 10 秒以后自动开锁,锁的编号为 123
    • 10 秒以后,A 还在执行任务,此时锁被自动打开了。
    • 用户 B 看到房间的锁打开了,于是抢占到了锁,设置锁的编号为 123,并设置了过期时间 10 秒
    • 因房间内只允许一个用户执行任务,所以用户 A 和 用户 B 执行任务 产生了冲突
    • 用户 A 在 15 s 后,完成了任务,此时 用户 B 还在执行任务。
    • 用户 A 主动打开了编号为 123 的锁。
    • 用户 B 还在执行任务,发现锁已经被打开了。
    • 用户 B 的锁被 A 主动打开后,A 离开房间,B 还在执行任务。
    • 用户 C 抢占到锁,C 开始执行任务。
    • 因房间内只允许一个用户执行任务,所以用户 B 和 用户 C 执行任务产生了冲突。
  • 优化: 给锁设置不同的编号

将value设置为唯一的编号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1.生成唯一 id
String uuid = UUID.randomUUID().toString();
// 2. 抢占锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
if(lock) {
System.out.println("抢占成功:" + uuid);
// 3.抢占成功,执行业务
List<TypeEntity> typeEntityListFromDb = getDataFromDB();
// 4.获取当前锁的值
String lockValue = redisTemplate.opsForValue().get("lock");
// 5.如果锁的值和设置的值相等,则清理自己的锁
if(uuid.equals(lockValue)) {
System.out.println("清理锁:" + lockValue);
redisTemplate.delete("lock");
}
return typeEntityListFromDb;
} else {
System.out.println("抢占失败,等待锁释放");
// 4.休眠一段时间
sleep(100);
// 5.抢占失败,等待锁释放
return getTypeEntityListByRedisDistributedLock();
}
  • 不足: 分原子性的
  • 改进:使用lua脚本
    1
    2
    3
    4
    5
    6
    if redis.call("get",KEYS[1]) == ARGV[1]
    then
    return redis.call("del",KEYS[1])
    else
    return 0
    end

使用redision

基于zookeeper实现

  • 在zookeeper中

参考文章


05-分布式锁
https://x-leonidas.github.io/2022/02/01/12分布式系统/05-分布式锁/
作者
听风
发布于
2022年2月1日
更新于
2025年5月23日
许可协议