Redis分布式锁的基本理念

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

image.png

1. 加锁

使用 setnx 命令实现加锁

eg:订单问题--多次点击创建问题

加锁伪代码

// TODO key类似订单id,1定位为某一种状态
long status = redisService.setnx(key, "1");
复制代码

当线程执行成功后

  • 返回1:说明key原本不存在,该线程成功获得锁
  • 返回0:说明key存在,线程抢锁失败

2. 解锁

当得到锁的线程执行完任务后需要解锁 del 释放锁,使其他线程能够进入调用

释放锁伪代码

redisService.del(key);
复制代码

释放锁后其他线程可以继续使用 setnx 来获取锁

3. 锁超时

线程在执行过程中出现异常挂掉,并没有释放相应的锁,如果不及时释放,资源将出现死锁,所以在加锁后需要给锁设置相应的超时时间,需要在一定时间后自动释放锁, setnx 没有设置超时方法,需要用到 expire 方法

锁超时时间伪代码

// 30分钟超时时间
redisService.expire(key, 30);
复制代码

加锁综合使用伪代码

long status = redisService.setnx(key, "1");
if(status == 1) {
    // 首次加锁,设置锁超时时间30分
    redisService.expire(key, 30);
    try {
        // TODO 业务操作代码
    } catch(Exception e) {
        // TODO 异常处理
    } finally {
        // 最终操作释放锁
        redisService.del(key);
    }
}
复制代码

4. 问题

  1. setnx 和 expire 非原子性

线程A和线程B共同调用该锁

线程A-->执行 setnx 方法后成功获得锁,未执行设置超时时间 expire 方法,节点1挂掉,锁就变成死锁

线程B-->无法获取该锁

解决:

使用 set 指令操作取代 setnx 指令,增加可选参数,再获取锁的时候添加超时时间

// key--订单id,1--某一value值,30--超时时间,NX--加锁
set(key, 1, 30,NX);
复制代码
  1. del 操作误删锁

线程A和线程B共同调用该锁

线程A-->成功调用锁并设置超时时间,线程A执行过慢,锁过期自动释放后线程A还未执行完

线程B-->A未执行完但锁过期自动释放,B得到锁后正常执行,但A完成任务后继续执行 del 指令删除锁,此时B还没有执行完,A删除释放的B加的锁

解决:

在删除锁之前做判断是否为自己加的锁,加锁的时候可以把当前线程id作为value值存储在锁中

  • 加锁:
String threadId = Thread.currentThread().getId()
set(key,threadId ,30,NX)
复制代码
  • 解锁
if(threadId .equals(redisClient.get(key))){
    del(key)
}
复制代码
  1. 该操作引发第三个问题-并发操作

判断和释放锁是两个独立操作,不具有原子性

需要解决线程A和线程B不能同时执行加锁代码块

获取锁的线程需要开启一个守护线程,用来给快要过期的锁续航,重新设置超时时间

线程A再快到锁超时时间的时候,守护线程执行 expire 指令重新设置超时时间,当线程A执行完后,显式关掉守护线程

猜你喜欢

转载自juejin.im/post/7130431704984420389