浅谈Redis分布式锁

前言

谈一下自己实现Redis分布式锁的过程以及相应的思考


要考虑什么

技术服务于业务,业务上需要什么技术就要提供什么

锁应该满足以下条件:

  1. 同一个锁在同一时间只能被一个线程拥有
  2. 线程不能释放不属于自己的锁
  3. 拥有锁的线程可以重复获取所拥有的锁
  4. 锁有时间限制

实现

先考虑1,2,3条,这三条无非是关于锁的归属问题,我们只需要将对应 keyvalue 设置

为线程 id 表明归属,加解锁前先判断即可

get test-lock # 锁不存在
set test-lock thread-1 # thread-1 是锁的拥有者

但是稍微考虑可以发现以下问题

# Thread-1 & Thread-2
get test-lock # 同时发现锁不存在
set test-lock thread-1 # Thread-1设置锁
set test-lock thread-2 # Thread-2设置锁

也就是两个线程同时发现锁不存在并且都尝试获取锁的时候,其中一个线程获取的锁会被另一个线程覆盖,违反第1条,所以 判断锁是否存在及后续获取锁应该是原子操作

然后考虑第4条,也是比较简单,获取锁后设置过期时间即可

set test-lock thread-1
expire test-lock 100 # 100s后过期

这里也可以发现问题,如果线程获取锁之后设置过期时间之前挂了,那么锁将一直存在导致所有需要锁的线程饥饿

所以获取锁和设置过期时间也应该是原子操作


Redis的原子操作

redis其实已经替我们考虑到了原子问题,从文档摘取和需求相关的指令

在这里插入图片描述

SETNX 如果值不存在就设值,否则什么也不做,解决1,2,3条中出现的问题

在这里插入图片描述
SETEX 设置值的同时设置过期时间,解决第4条出现的问题

以上两个指令好像解决了问题 ,但是为了能使锁正常工作,我们的需求会升级成:

  1. 如果锁不存在那么获取锁并设置过期时间(第1,4条)
  2. 如果锁存在那么判断当前线程是否为拥有者,如果是则刷新过期时间(第3条)
  3. 如果锁存在那么判断当前线程是否为拥有者,如果是则删除锁(第2条)

查看Redis的指令集并没有发现相关指令,怎么办?


Lua

Lua是一门脚本语言,先看下Redis文档中对 Lua 的相关应用
在这里插入图片描述
在这里插入图片描述

可以发现Redis的 EVAL 指令接收 Lua 脚本作为参数并将整个脚本当成原子操作

所以升级后的需求我们使用Lua脚本就可以实现了,给出示例代码:

public Boolean lock(String key, String holder, Long expireTime) {
        String lockScript = "if redis.call('set', KEYS[1], KEYS[2], 'ex', KEYS[3], 'nx') then " +
                                "return 1 " +
                            "elseif redis.call('get', KEYS[1]) == KEYS[2] then " +
                                "return redis.call('expire', KEYS[1], KEYS[3]) " +
                            "else " +
                                "return 0 " +
                            "end";
        return connection.sync().eval(lockScript, ScriptOutputType.BOOLEAN, key, holder, String.valueOf(expireTime));
}

public Boolean unlock(String key, String holder) {
        String unlockScript = "if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end";
        return connection.sync().eval(unlockScript, ScriptOutputType.BOOLEAN, key, holder);
}

题外

业界其实有挺多成熟的分布式锁方案,即使不直接拿来用也有很大的参考意义。例如参考Redisson时就发现对于锁的重入方面,锁有计数器,进入一次计数器加一,释放一次计数器减一,在计数器为0时则删除锁。这样的设计虽然在我的业务场景上用不上,但以后有更多业务场景时就可能要借鉴它。

然后关于Redis的客户端估计大多数人都是用的 Jedis,毕竟它是如此成熟好用。其实这里我想推荐新秀Lettuce,理由只有一条 ——Spring Boot已经将默认的Redis实现由 Jedis 更改为 Lettuce

最后实现分布式锁时我们不仅要考虑加解锁逻辑的正确性,还要考虑Redis本身的可用性,所以 哨兵模式 了解一下


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值