Redis中怎么实现分布式锁

发布时间:2021-10-20 11:44:09 作者:iii
来源:亿速云 阅读:130

本篇内容介绍了“Redis中怎么实现分布式锁”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

Redis中怎么实现分布式锁

为什么需要分布式锁

为什么需要分布式锁

使用分布式锁的目的,无外乎就是保证同一时间只有一个客户端可以对共享资源进行操作。

我们在分布式应用进行逻辑处理时经常会遇到并发问题。【相关推荐:Redis视频教程】

比如一个操作要修改用户的状态,修改状态需要先读出用户的状态,在内存里进行修改,改完了再存回去。如果这样的操作同时进行了,就会出现并发问题,因为读取和保存状态这两个操作不是原子的。

这个时候就要使用到分布式锁来限制程序的并发执行。redis作为一个缓存中间件系统,就能提供这种分布式锁机制,

其本质就是在redis里面占一个坑,当别的进程也要来占坑时,发现已经被占领了,就只要等待稍后再尝试

一般来说,生产环境可用的分布式锁需要满足以下几点:

实现方式

使用SETNX实现

SETNX的使用方式为:SETNX key value,只在键key不存在的情况下,将键key的值设置为value,若键key存在,则SETNX不做任何动作。

boolean result = jedis.setnx("lock-key",true)== 1L;
if  (result) {
    try {
        // do something
    } finally {
        jedis.del("lock-key");
    }
 }

这种方案有一个致命问题,就是某个线程在获取锁之后由于某些异常因素(比如宕机)而不能正常的执行解锁操作,那么这个锁就永远释放不掉了。

为此,我们可以为这个锁加上一个超时时间

执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value

执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value

String result = jedis.set("lock-key",true, 5);
if ("OK".equals(result)) {
    try {
        // do something
    } finally {
        jedis.del("lock-key");
    }
}

方案看上去很完美,但实际上还是会有问题

试想一下,某线程A获取了锁并且设置了过期时间为10s,然后在执行业务逻辑的时候耗费了15s,此时线程A获取的锁早已被Redis的过期机制自动释放了

在线程A获取锁并经过10s之后,改锁可能已经被其它线程获取到了。当线程A执行完业务逻辑准备解锁(DEL key)的时候,有可能删除掉的是其它线程已经获取到的锁。

所以最好的方式是在解锁时判断锁是否是自己的,我们可以在设置key的时候将value设置为一个唯一值uniqueValue(可以是随机值、UUID、或者机器号+线程号的组合、签名等)。

当解锁时,也就是删除key的时候先判断一下key对应的value是否等于先前设置的值,如果相等才能删除key

String velue= String.valueOf(System.currentTimeMillis())
String result = jedis.set("lock-key",velue, 5);
if ("OK".equals(result)) {
    try {
        // do something
    } finally {
      	//非原子操作
	      if(jedis.get("lock-key")==value){
		        jedis.del("lock-key");
        }    
    }
}

这里我们一眼就可以看出问题来:GETDEL是两个分开的操作,在GET执行之后且在DEL执行之前的间隙是可能会发生异常的。

如果我们只要保证解锁的代码是原子性的就能解决问题了

这里我们引入了一种新的方式,就是Lua脚本,示例如下:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

其中ARGV[1]表示设置key时指定的唯一值。

由于Lua脚本的原子性,在Redis执行该脚本的过程中,其他客户端的命令都需要等待该Lua脚本执行完才能执行。

确保过期时间大于业务执行时间

为了防止多个线程同时执行业务代码,需要确保过期时间大于业务执行时间

增加一个boolean类型的属性isOpenExpirationRenewal,用来标识是否开启定时刷新过期时间

在增加一个scheduleExpirationRenewal方法用于开启刷新过期时间的线程

加锁代码在获取锁成功后将isOpenExpirationRenewal置为true,并且调用scheduleExpirationRenewal方法,开启刷新过期时间的线程

解锁代码增加一行代码,将isOpenExpirationRenewal属性置为false,停止刷新过期时间的线程轮询

Redisson实现

获取锁成功就会开启一个定时任务,定时任务会定期检查去续期

该定时调度每次调用的时间差是internalLockLeaseTime / 3,也就10秒

默认情况下,加锁的时间是30秒.如果加锁的业务没有执行完,那么到 30-10 = 20秒的时候,就会进行一次续期,把锁重置成30秒

RedLock

在集群中,主节点挂掉时,从节点会取而代之,客户端上却并没有明显感知。原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。然后从节点变成了主节点,这个新的节点内部没有这个锁,所以当另一个客户端过来请求加锁时,立即就批准了。这样就会导致系统中同样一把锁被两个客户端同时持有,不安全性由此产生

Redlock算法就是为了解决这个问题

使用 Redlock,需要提供多个 Redis 实例,这些实例之前相互独立没有主从关系。同很多分布式算法一样,redlock 也使用大多数机制

加锁时,它会向过半节点发送 set指令,只要过半节点 set 成功,那就认为加锁成功。释放锁时,需要向所有节点发送 del 指令。不过 Redlock 算法还需要考虑出错重试、时钟漂移等很多细节问题,同时因为 Redlock 需要向多个节点进行读写,意味着相比单实例 Redis 性能会下降一些

Redlock 算法是在单 Redis 节点基础上引入的高可用模式,Redlock 基于 N 个完全独立的 Redis 节点,一般是大于 3 的奇数个(通常情况下 N 可以设置为 5),可以基本保证集群内各个节点不会同时宕机。

假设当前集群有 5 个节点,运行 Redlock 算法的客户端依次执行下面各个步骤,来完成获取锁的操作

也就是说,假设锁30秒过期,三个节点加锁花了31秒,自然是加锁失败了

在 Redis 官方推荐的 Java 客户端 Redisson 中,内置了对 RedLock 的实现

RedLock问题:

RedLock 只是保证了锁的高可用性,并没有保证锁的正确性

RedLock 是一个严重依赖系统时钟的分布式系统

Martin 对 RedLock 的批评:

“Redis中怎么实现分布式锁”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

推荐阅读:
  1. Redis如何实现分布式锁
  2. Redis分布式锁怎么实现

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

redis

上一篇:什么是php x86

下一篇:有关Python问题的详细解说

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》