Redis 实现分布式锁

V1: setnx命令

一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占用。
先来先占,用完了,再调用del指令释放。
但是有个问题,如果逻辑执行到中间出现异常了,可能会导致del指令没有被调用,这样就会陷入死锁,锁永远得不到释放。

V2: 锁超时释放

所以在拿到锁之后,再给锁加上一个过期时间,比如5s,这样即使中间出现异常也可以保证5秒之后锁会自动释放。
但是如果在setnx和expire之间服务器进程突然挂掉了,就会导致expire得不到执行,也会造成死锁。
这种问题的根源就在于setnx和expire是两条指令而不是原子指令。如果这两条指令,可以一起执行就不会出现问题。

V3:set指令

这个问题在Redis2.8版本中得到了解决,这个版本加入了set指令的扩展参数,使得setnx和expire指令可以一起执行。
上面这个指令就是setnx和expire组合在一起的原子指令

// v1
public class RedisLock {  
    @Autowired  
    StringRedisTemplate stringRedisTemplate;  
    private String name;  
    private Double timeoutSec;  
    private static final String KEY_PREFIX = "lock:";  
    public boolean tryLock(long timeoutSec) {  
        //获取线程标示  
        Long threadId = Thread.currentThread().getId();  
        //获取锁  
        Boolean success = stringRedisTemplate.opsForValue()  
                .setIfAbsent(KEY_PREFIX + name, threadId + "", timeoutSec, TimeUnit.SECONDS);  
        return Boolean.TRUE.equals(success);  
    }  
  
    public void unlock() {  
        //释放锁  
        stringRedisTemplate.delete(KEY_PREFIX + name);  
    }  
}

v4:解决误删问题

线程1进行加锁 但是由于意外原因发送阻塞,没能在设置key过期时间内进行操作。锁自动释放
线程2之后进行加锁,然后正常处理业务。。。
线程1恢复正常 执行完成后直接删除了key,线程1误删了线程2 的锁

为此应该加入验证机制,释放锁之前检查是否是当前线程设置的锁

使用UUID+线程id

private static final string ID_PREFIX = UUID.randomUUID().tostring(true);

@Override
public boolean tryLock(long timeoutSec) {
    // 获取线程标示
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁
    Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
        KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS
    );
    return Boolean.TRUE.equals(success);
}

@Override
public void unlock() {
    // 获取线程标示
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁中的标示
    String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
    // 判断标示是否一致
    if (threadId.equals(id)) {
        // 释放锁
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }
}

v5:lua脚本保证原子性

判断锁和释放锁之间出现了堵塞,也会导致释放其他线程的锁
可以使用lua脚本保证判断锁和释放锁的原子性

Pasted image 20240831224908.png

-- 比较线程标示与锁中的标示是否一致  
if(redis.call('get',KEYS[1]) == ARGV[1]) then  
    -- 释放锁del key  
    return redis.call('del',KEYS[1])  
end  
return 0

private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;  
static {  
    UNLOCK_SCRIPT = new DefaultRedisScript<>();  
    UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));  
    UNLOCK_SCRIPT.setResultType(Long.class);  
}  
  
public void unlock() {  
    //执行lua脚本  
    stringRedisTemplate.execute(  
            UNLOCK_SCRIPT,  
            Collections.singletonList(KEY_PREFIX + name),  
            ID_PREFIX + Thread.currentThread().getId()  
    );  
    //释放锁  
    stringRedisTemplate.delete(KEY_PREFIX + name);  
}

基本完成了一个生产可用的分布式锁

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇