使用redis实现分布式锁

这篇具有很好参考价值的文章主要介绍了使用redis实现分布式锁。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

为什么需要分布式锁

在一个分布式系统中,也会涉及多个节点访问同一个公共资源的情况,此时就需要通过锁来做互斥控制,避免出现类似于“线程安全”的问题,而java的synchronized这样的锁只能在当前进程中生效,在分布式的这种多个进程多个主机的场景无能为力,此时就需要分布式锁。

分布式锁的基础实现

例如:买票场景,现在车站提供了若干车次,每个车次的票数都是固定的。现在又多个服务器节点,都可能需要处理这个买票逻辑,先查询指定车次的余票,如果余票>0,则设置余票值-=1.

使用redis实现分布式锁,redis,分布式,redis
客户端1先查询余票,发现剩余1张,在即将执行1->0过程之前;客户端2也执行查询余票,发现也是剩余1张,也会执行1->0过程。这就造成1张票卖了给两个人,即超卖。
我们可以在上述架构中引入redis,作为分布式锁的管理器。
使用redis实现分布式锁,redis,分布式,redis
所谓的分布式锁,也是一个/一组单独的服务器程序(如redis),给其他服务器提供“加锁”服务。
买票服务器,在进行买票操作的时候,需要先加锁。往redis上设置一个特殊的键值对key-value,完成上述买票操作,再把这个key-value删除掉。其他服务器也想去买票的时候,也去redis上尝试设置key-value,如果发现key-value已经存在,就认为“加锁失败”(是放弃/阻塞等待,就看具体实现)。这样就可以保证,第一个服务器在执行“查询->更新"的过程中,第二个服务器不会执行”查询“,也就解决了”超卖“问题。
:::success
redis中提供的setnx操作,正好适合上述场景。即key不存在就设置,存在则设置失败
:::

引入过期时间

某个服务器中加锁成功后(setnx成功),如果该服务器意外发生宕机,就会导致解锁操作(删除该key)不能执行,就可能引起其他服务器始终无法获取到锁的情况。

在java的多线程编程中,可以把解锁操作放到finally中,保证解锁操作一定会被执行到。但是这种做法只是针对进程内的锁有用(进程异常退出,锁也就随之销毁)。而分布式锁是无效的,服务器宕机以后会导致redis上设置的key无人删除,也就导致其他服务器无法获取到锁

:::info
引入过期时间,使用set ex nx的方式,在设置锁的同时把过期时间设置进去,一但时间到了,key就会自动被删除掉。
:::
注意!此处设置过期时间只能使用一个命令的方式设置。

如果分开设置,比如setnx之后,再来个expire。redis多个指令之间,无法保证原子性(redis的原子性是只能保证执行,不能保证成功)。此时就可能出现这两个命令,一个执行成功,一个执行失败情况

引入校验id

对于redis中写入的加锁键值对,其他节点也是可以删除的。

比如 服务器1写入一个001:1这样的键值对,服务器2是完全可以把001:1给删除掉。当然,服务器2一般不会这样”恶意删除“操作,不过不能保证因为一些bug导致服务器2把锁给误删除

为了解决上述问题,我们可以引入一个校验id。

  1. 给服务器编号,每个服务器都有一个自己的身份标识
  2. 进行加锁的时候,设置key-value。key是针对哪个资源加锁(比如车次),value就可以存储刚才服务器的编号,标识出当前这个锁是哪个服务器加上的。
  3. 解锁的时候,先查询一下这个锁对应的服务器编号,然后判定一下value是否和当前执行解锁的服务器编号一致,如果一致,才能真正执行del,如果不是,就失败。

伪代码如下:

String key = [要加锁的资源 id]; 
String serverId = [服务器的编号]; 
// 加锁, 设置过期时间为 10s 
redis.set(key, serverId, "NX", "EX", "10s");  
// 执⾏各种业务逻辑, ⽐如修改数据库数据. 
doSomeThing();
// 解锁, 删除 key. 但是删除前要检验下 serverId 是否匹配.
if (redis.get(key) == serverId) { 
	redis.del(key); 
}  

但是很明显,在解锁的时候,getdel是两步操作,不是原子的。

引入lua

在服务器内部,可能是多线程的。例如服务器1中有两个线程都在执行上述解锁操作。
使用redis实现分布式锁,redis,分布式,redis
在服务器1中,看起来只是重复执行del操作,问题不大???但是当服务器2,执行加锁时,就可能出现问题了。
线程A执行完del操作后,线程B执行del操作之前,服务器2的线程C正好要执行加锁操作。此时线程A已经把锁删除了,线程C是能够加锁成功的。但是紧接着,线程B就会执行del操作,就会把服务器2的加锁操作给解锁了。虽然del操作中有引入校验id,但是线程B在get操作中已经通过id校验,可以执行del操作,虽然线程C这把锁的id不同,也能够解锁。
使用redis是事务,能够避免命令之间的插队。但是实践中往往是使用lua脚本。由于lua语言非常轻量,因此可以内嵌到redis中。我们可以使用lua编写一些逻辑,把这个脚本上传到redis服务器上,然后就可以让客服端来控制redis执行上述脚本。redis执行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,分布式,redis

引入看门狗(watch dog)

上述方案中仍然存在一个重要问题,在加锁的时候,需要给key设置过期时间。过期时间,设置多少合适呢?

  • 设置太短,就可能业务逻辑还没执行完,就释放锁
  • 设置太长,会导致”锁释放不及时“问题

因此更好的方式是”动态续约“,这就需要服务器这边有一个专门的线程,负责续约这件事。我们把这个负责的线程,叫做”看门狗“(watch dog).

举个具体的例子:
初始情况下设置过期时间10s,同时设定看门狗线程每隔3s检测一次。
当3s时间到的时候,看门狗就会判定当前任务是否完成。

  • 如果任务已经完成,直接通过lua脚本的方式,释放锁(删除key)
  • 如果任务未完成,则把过期时间重新设置为10s,即续约

这样就不用担心锁提前释放的问题了,而且另外一方面,如果服务器挂了,看门狗线程也会被销毁,此时无人续约,这个key自然就可以迅速过期,让其他服务器获取到锁

引入redlock算法

实践中的redis一般使用集群的方式部署的,那么就可能出现以下比较极端的情况。

服务器1向master节点进行加锁操作,这个写入key的过程刚完成,master挂了;slave节点升级成新的master节点,但是由于刚才写入的这个key未来得及同步给slave,此时就相当于服务器1的加锁操作形同虚设。服务器2仍然可以进行加锁,即给新的master写入key,因为新的master不包含刚才的key。

为了解决这个问题,redis作者提出了redlock算法。本质上是使用冗余解决可用性问题
使用redis实现分布式锁,redis,分布式,redis
此处加锁,就是按照一定的顺序,针对redis集群的所有分片都进行加锁操作。如果某个节点挂了(加不上锁了)继续给下一个节点加锁即可。如果写入key成功的节点个数超过总数的一半,就视为加锁成功。同理,进行解锁的时候,也就会把上述节点都设置一遍解锁。文章来源地址https://www.toymoban.com/news/detail-730373.html

到了这里,关于使用redis实现分布式锁的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用 redis 实现分布式接口限流注解 RedisLimit

    前言 很多时候,由于种种不可描述的原因,我们需要针对单个接口实现接口限流,防止访问次数过于频繁。这里就用 redis+aop 实现一个限流接口注解 @RedisLimit 代码 点击查看RedisLimit注解代码 AOP代码 点击查看aop代码 lua脚本代码 注意:脚本代码是放在 resources 文件下的,它的类型是

    2024年02月08日
    浏览(56)
  • Zookeeper 和 Redis 哪种更好? 为什么使用分布式锁? 1. 利用 Redis 提供的 第二种,基于 ZK 实现分布式锁的落地方案 对于 redis 的分布式锁而言,它有以下缺点:

    关于这个问题,我们 可以从 3 个方面来说: 为什么使用分布式锁? 使用分布式锁的目的,是为了保证同一时间只有一个 JVM 进程可以对共享资源进行操作。 根据锁的用途可以细分为以下两类: 允许多个客户端操作共享资源,我们称为共享锁 这种锁的一般是对共享资源具有

    2024年01月16日
    浏览(46)
  • 分布式锁设计选型 不可重入锁建议使用ZooKeeper来实现 可重入锁建议使用Redis来实现 分布式锁:ZooKeeper不可重入锁 Java优化建议

    在设计分布式锁时,需要考虑业务场景和业务需求,以保证锁的正确性和可用性。 例如,在一个电商系统中,每个商品都有一个库存量。为了避免多个用户同时购买同一件商品导致库存出现不一致的情况,可以为每个商品设置一个分布式锁,确保同一时间只能有一个用户购买

    2024年02月08日
    浏览(47)
  • 分布式锁实现(mysql,以及redis)以及分布式的概念

    我旁边的一位老哥跟我说,你知道分布式是是用来干什么的嘛?一句话给我干懵了,我能隐含知道,大概是用来做分压处理的,并增加系统稳定性的。但是具体如何,我却道不出个1,2,3。现在就将这些做一个详细的总结。至少以后碰到面试官可以说上个123。 那么就正式进入

    2024年01月21日
    浏览(57)
  • 在Spring中,可以使用不同的方式来实现分布式锁,例如基于数据库、Redis、ZooKeeper等

    在Spring中,可以使用不同的方式来实现分布式锁,例如基于数据库、Redis、ZooKeeper等。下面是两种常见的实现方式: 使用Redis实现分布式锁: 使用自定义注解实现本地锁: 以上是两种常见的在Spring中实现分布式锁的方式。第一种方式使用Redis作为分布式锁的存储介质,通过

    2024年03月17日
    浏览(45)
  • Redis——》实现分布式锁

    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结——》【SpringBoot】     总结——》【MyBatis、MyBatis-Plus】     总结——》【Linux】     总结——》【MongoDB】    

    2024年02月10日
    浏览(58)
  • redis如何实现分布式锁?

    首先,“分布式锁”的概念,是相对“本地锁”而言。 本地锁比如java中的synchronized 这类 JDK 自带的 本地锁 ,来控制一个 JVM 进程内的多个线程对本地共享资源的访问。 同一时刻只有一个线程可以获取到本地锁访问共享资源。 分布式系统下,不同的服务/客户端通常运

    2024年02月06日
    浏览(59)
  • redis实现分布式延时队列

    延时队列是一种特殊的消息队列,它允许将消息在一定的延迟时间后再进行消费。延时队列的主要特点是可以延迟消息的处理时间,以满足定时任务或者定时事件的需求。 总之,延时队列通过延迟消息的消费时间,提供了一种方便、可靠的方式来处理定时任务和定时事件。它

    2024年02月08日
    浏览(44)
  • Redis分布式锁实现原理

    在早期互联网的架构中,一个应用都是单机进行部署,这种情况下,利用JDK提供的锁机制即可解决共享数据在多线程场景下的线程安全问题,但随着技术的发展,分布式系统架构逐渐普及,在分布式架构中,由于一个应用会进行多机部署,服务器实例之间的JVM是互相独立的,

    2024年02月16日
    浏览(40)
  • 分布式锁之redis实现

    需要挂在的data和redis.conf自行创建即可 不要忘记开放端口6379 修改redis.conf配置文件,设置 requirepass xxxxx 如果直接使用RedisTemplate使用的序列化器是jdk的,存的是二进制,使用StringRedisTemplate默认初始化序列化器就是String类型 执行票数存入redis指令  编写代码演示超卖问题  500

    2024年02月10日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包