一文详解如何使用Redis实现分布式锁

 更新时间:2022年09月06日 08:50:10   作者:程序员读书​​​​​​​  
这篇文章主要介绍了一文详解如何使用Redis实现分布式锁,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下

1. 什么是分布式锁

当我们在编写多线程代码的时候,不同的线程可能会发生资源的争夺,为了避免资源争夺造成的错误,我们会对资源上锁,只有获得锁的线程才能继续往下执行。

进程中的锁,本质就是内存中一个变量,当一个线程执行某个操作申请加锁时,如果能成功把代表锁的变量值设置为1,则表示获得了锁,其他线程想要获得锁时会阻塞,而拥有锁的线程执行完操作后,再把锁的值设置为0,则表示释放了锁。

上面我们说的是在一台服务器的进程内不同线程之间的锁,这个锁是放在内存中的,而对于分布式应用程序来说,不同的应用(进程或线程)部署在不同的服务器上,这样就不能通过内存中的变量来表示锁。

即然在一台服务器上可以通过内存这块共享的空间来表示锁,那么对于分布式应用程序来说,可以共享存储系统来存储一个共享锁,这就是分布式锁,而Redis作为内存数据库,执行非常快,很适合作为实现分布式锁的共享存储系统。

2. 使用Redis实现分布式锁

对于一个锁来说,其实只有两个操作,加锁和释放锁,下面我们看来看通过Redis要怎么实现?

2.1 加锁

Redissetnx命令会判断键值是否存在,如果存在则不做任何操作,并返回0,如果不存在,则创建并赋值,并返回1,因此我们可以执行setnx为一个代表锁键设置值,如果能设置成功,则表示获得锁,失败则无法获得锁。

# 使用key为lock来表示一个锁
setnx lock 1 

2.2 释放锁

当执行好操作之后,要释放锁的时候直接把Redis里的键值lock删除就可以了,这样其他进程才能通过setnx命令重新设置并获得该锁。

# 释放锁
del lock

通过上面两个命令,我们实现了一个简单的分布式锁,但这里就出现了一个问题:如果一个进程通过setnx命令加锁之后,在执行具体操作出错了,没有办法及时释放锁,那么其他进程就无法获得该锁,系统便无法继续往下执行,解决这个问题的办法就是为锁设置一个有效期,在这个有效期之后,自动释放锁。

2.3 给锁设置有效期

给锁设置有效期非常简单,直接使用Redisexpire命令就可以了,如:

# 加锁
setnx lock 1 
# 给锁设置10s有效期
expire lock 10 

但是,现在又出现另一个问题了,如果我们在设置了锁之后,执行expire命令之前该进程挂掉了,那么expire就没有执行成功,锁一样是没有被释放掉的,所以一定要保证上面两个命令要一起执行,怎么保证呢?

有两个方法,一个是使用LUA语言编写的脚本,另一个是使用Redisset命令,set命令后面跟nx参数后,执行的效果与setnx一致,且set命令可以跟ex参数来设置过期时间,所以我们可以使用set命令把setnxexpire两个合并在一起,这样就可以保证执行的原子性了。

# 判断是否键值是否存在,ex后面跟着的是键值的有效期,10s
set lock 1 nx ex 10 

解决了锁的有效问题,现在我们再来看另外一个问题。

如上图所示,现在有ABC三个不同服务器上的进程在执行某个操作都需要获得锁,执行后要释放锁。

现在的情况是进程A执行第2步时卡顿了(上面绿色区域所示),且时间超出了锁有效期,所以进程A设置的锁自动释放了,这时候进程B获得了锁,并开始执行操作,但由于进程A只是卡顿了而已,所以会继续执行的时候,在第3步的时候会手动释放锁,但是这个时候,锁由线程B所拥有,也就是说进程A删除的不是自己的锁,而进程B的锁,这时候进程B还没执行完,但锁被释放后,进程C可以加锁,也就是说由于进程A卡顿释放错了锁,导致进程B和进程C可以同时获得锁

怎么避免这种情况呢?如何区分其他进程的锁,避免删除其他进程的锁呢?答案就是每个进程在加锁的时候,给锁设置一个唯一值,并在释放锁的时候,判断是不是自己设置的锁。

2.4 给锁设置唯一值

给锁设置唯一值的时候,一样是使用set命令,唯一的不同是将键值1改为一个随机生成的唯一值,比如uuid。

 # rand_uid表示唯一id
set lock rand_id nx ex 10

当锁里的值由进程设置后,释放锁的时候,就需要判断锁是不是自己的,步骤如下:

  • 通过Redisget命令获得锁的值
  • 根据获得的值,判断锁是不是自己设置的
  • 如果是,通过del命令释放锁。

此时我们看到,释放锁需要执行三个操作,如果三个操作依次执行的话,是没有办法保证原子性的,比如进程A在执行到第2步后,准备开始执行del命令时,而锁由时有效期到了,被自动释放了,并被其他服务器上的进程B获得锁,但这时候线程A执行del还是把线程B的锁给删掉了。

解决这个问题的办法就是保证上述三个操作执行的原子性,即在执行释放锁的三个操作中,其他进程不可以获得锁,想要做到这一点,需要使用到LUA脚本。

2.5 通过LUA脚本实现释放锁的原子性

Redis支持LUA脚本,LUA脚里的代码执行的时候,其他客户端的请求不会被执行,这样可以保证原子性操作,所以我们可以使用下面脚本进行锁的释放:

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

将上述脚本保存为脚本后,可以调用Redis客户端命令redis-cli来执行,如下:

# lock为key,rand_id表示key里保存的值
redis-cli --eval unlock.lua lock , rand_id 

3. 小结

无论是本地锁还是分布式锁,锁的本质就是一个共享的变量,只是在实现分布式锁时候,把这个变量移到了Redis服务器所在的内存中。

在上面实现分布式锁的过程中我们碰到了以下几个问题:

  • 如何保证加锁操作的原子性?
  • 如何保证进程崩溃自动释放锁?
  • 如何避免删错其他进程的锁?
  • 如何保证释放锁操作的原子性?

在解决上述问题的时候,我们也一步步完善一个可以在实际开发中应用的Redis分布式锁。

到此这篇关于一文详解如何使用Redis实现分布式锁的文章就介绍到这了,更多相关Redis实现分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Redis SETNX命令在Spring Cloud中的分布式锁用法详解

    Redis SETNX命令在Spring Cloud中的分布式锁用法详解

    在Spring Cloud项目中,使用Java和Redis结合实现的分布式锁可以确保订单的一致性和并发控制,通过合理使用锁的粒度以及注意事项,可以减少死锁问题并提高系统的并发性能,这篇文章主要介绍了Redis SETNX命令在Spring Cloud中的分布式锁用法详解,需要的朋友可以参考下
    2023-10-10
  • Redis哨兵模式实现一主二从三哨兵

    Redis哨兵模式实现一主二从三哨兵

    本文主要介绍了Redis哨兵模式实现一主二从三哨兵,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • SpringBoot整合Redis入门之缓存数据的方法

    SpringBoot整合Redis入门之缓存数据的方法

    Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API,下面通过本文给大家介绍下SpringBoot整合Redis入门之缓存数据的相关知识,感兴趣的朋友一起看看吧
    2021-11-11
  • Redis中的数据过期策略详解

    Redis中的数据过期策略详解

    这篇文章主要介绍了Redis中的数据过期策略,文中通过示例代码介绍的很详细,相信对大家的理解和学习具有一定的参考借鉴价值,有需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-01-01
  • Redis的4种缓存模式分享

    Redis的4种缓存模式分享

    这篇文章主要介绍了Redis的4种缓存模式分享,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-07-07
  • redis锁机制介绍与实例

    redis锁机制介绍与实例

    今天小编就为大家分享一篇关于redis锁机制介绍与实例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • Reactor WebFlux集成Redis处理缓存操作

    Reactor WebFlux集成Redis处理缓存操作

    这篇文章主要为大家介绍了Reactor WebFlux集成Redis处理缓存操作示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Redis的过期键删除策略原理说明

    Redis的过期键删除策略原理说明

    这篇文章主要介绍了Redis的过期键删除策略原理说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • Redisson分布式锁解锁异常问题

    Redisson分布式锁解锁异常问题

    文章主要描述了在使用Redisson进行分布式锁操作时,遇到的`IllegalMonitorStateException`异常,通过分析报错信息,作者发现了问题的根源在于线程尝试解锁但未按顺序执行,作者提出了两种解决方案:在解锁前增加判断,确保线程已获得锁
    2024-12-12
  • 手动实现Redis的LRU缓存机制示例详解

    手动实现Redis的LRU缓存机制示例详解

    这篇文章主要介绍了手动实现Redis的LRU缓存机制示例详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03

最新评论