Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >基于redis的分布式锁的分析与实践

基于redis的分布式锁的分析与实践

作者头像
程序员鹏磊
发布于 2019-12-10 09:51:09
发布于 2019-12-10 09:51:09
1K00
代码可运行
举报
文章被收录于专栏:架构师专栏架构师专栏
运行总次数:0
代码可运行

前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了。乐观锁和悲观锁最根本的区别在于线程之间是否相互阻塞。

本文主要来讨论基于redis的分布式锁算法问题

从2.6.12版本开始,redis为SET命令增加了一系列选项(SET key value [EX seconds] [PX milliseconds] [NX|XX]):

EX seconds – 设置键key的过期时间,单位时秒 PX milliseconds – 设置键key的过期时间,单位时毫秒 NX 只有键key不存在的时候才会设置key的值 XX 只有键key存在的时候才会设置key的值

原文地址:https://redis.io/commands/set

中文地址:http://redis.cn/commands/set.html

注意: 由于SET命令加上选项已经可以完全取代SETNX, SETEX, PSETEX的功能,所以在将来的版本中,redis可能会不推荐使用并且最终抛弃这几个命令。

(这里简单提一下,在旧版本的redis中(指2.6.12版本之前),使用redis实现分布式锁一般需要setNX、expire、getSet、del等命令。而且会发现这种实现有很多逻辑判断的原子操作以及本地时间等并没有控制好。)

而在旧版本的redis中,redis的超时时间很难控制,用户迫切需要把setNX和expiration结合为一体的命令,把他们作为一个原子操作,这样新版本的多选项set命令诞生了。然而这并没有完全解决复杂的超时控制带来的问题。

接下来,我们的一切讨论都基于新版redis。

在这里,我先提出几个在实现redis分布式锁中需要考虑的关键问题

1、死锁问题

为了防止死锁,redis至少需要设置一个超时时间;

由1.1引申出来,当锁自动释放了,但是程序并没有执行完毕,这时候其他线程又获取到锁执行同样的程序,可能会造成并发问题,这个问题我们需要考虑一下是否归属于分布式锁带来问题的范畴。

2、锁释放问题,这里会有两个问题

每个获取redis锁的线程应该释放自己获取到的锁,而不是其他线程的,所以我们需要在每个线程获取锁的时候给锁做上不同的标记以示区分;

由2.1带来的问题是线程在释放锁的时候需要判断当前锁是否属于自己,如果属于自己才释放,这里涉及到逻辑判断语句,至少是两个操作在进行,那么我们需要考虑这两个操作要在一个原子内执行,否者在两个行为之间可能会有其他线程插入执行,导致程序紊乱。

3、更可靠的锁

单实例的redis(这里指只有一个master节点)往往是不可靠的,虽然实现起来相对简单一些,但是会面临着宕机等不可用的场景,即使在主从复制的时候也显得并不可靠(因为redis的主从复制往往是异步的)。

关于Martin Kleppmann的Redlock的分析

原文地址: https://redis.io/topics/distlock

中文地址: http://redis.cn/topics/distlock.html

文章分析得出,这种算法只需具备3个特性就可以实现一个最低保障的分布式锁。

1、安全属性(Safety property)

独享(相互排斥)。在任意一个时刻,只有一个客户端持有锁。

2、活性A(Liveness property A)

无死锁。即便持有锁的客户端崩溃(crashed)或者网络被分裂(gets partitioned),锁仍然可以被获取。

3、活性B(Liveness property B)

容错。 只要大部分Redis节点都活着,客户端就可以获取和释放锁.

我们来分析一下:

第一点安全属性意味着悲观锁(互斥锁)是我们做redis分布式锁的前提,否者将可能造成并发;

第二点表明为了避免死锁,我们需要设置锁超时时间,保证在一定的时间过后,锁可以重新被利用;

第三点是说对于客户端来说,获取锁和手动释放锁可以有更高的可靠性。

更进一步分析,结合上文提到的关键问题,这里可以引申出另外的两个问题:

1、怎么才能合理判断程序真正处理的有效时间范围?(这里有个时间偏移的问题)

2、redis Master节点宕机后恢复(可能还没有持久化到磁盘)、主从节点切换,(N/2)+1这里的N应该怎么动态计算更合理?

接下来再看,redis之父antirez对Redlock的评价

原文地址:

http://antirez.com/news/101

文中主要提到了网络延迟和本地时钟的修改(不管是时间服务器或人为修改)对这种算法可能造成的影响。

最后,来点实践吧

1、传统的单实例redis分布式锁实现(关键步骤)

获取锁(含自动释放锁):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SET resource_name my_random_value NX PX 30000

手动删除锁(Lua脚本):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

2、分布式环境的redis(多master节点)的分布式锁实现

为了保证在尽可能短的时间内获取到(N/2)+1个节点的锁,可以并行去获取各个节点的锁(当然,并行可能需要消耗更多的资源,因为串行只需要count到足够数量的锁就可以停止获取了);

另外,怎么动态实时统一获取redis master nodes需要更进一步去思考了。

QA,补充一下说明(以下为我与朋友沟通的情况,以说明文中大家可能不够明白的地方):

1、在关键问题2.1中,删除就删除了,会造成什么问题?

线程A超时,准备删除锁;但此时的锁属于线程B;线程B还没执行完,线程A把锁删除了,这时线程C获取到锁,同时执行程序;所以不能乱删。

2、在关键问题2.2中,只要在key生成时,跟线程相关就不用考虑这个问题了吗?

不同的线程执行程序,线程之间肯虽然有差异呀,然后在redis锁的value设置有线程信息,比如线程id或线程名称,是分布式环境的话加个机器id前缀咯(类似于twitter的snowflake算法!),但是在del命令只会涉及到key,不会再次检查value,所以还是需要lua脚本控制if(condition){xxx}的原子性。

3、那要不要考虑锁的重入性?

不需要重入;try…finally 没得重入的场景;对于单个线程来说,执行是串行的,获取锁之后必定会释放,因为finally的代码必定会执行啊(只要进入了try块,finally必定会执行)。

4、为什么两个线程都会去删除锁?(貌似重复的问题。不管怎样,还是耐心解答吧)

每个线程只能管理自己的锁,不能管理别人线程的锁啊。这里可以联想一下ThreadLocal。

5、如果加锁的线程挂了怎么办?只能等待自动超时?

看你怎么写程序的了,一种是问题3的回答;另外,那就自动超时咯。这种情况也适用于网络over了。

6、时间太长,程序异常就会蛋疼,时间太短,就会出现程序还没有处理完就超时了,这岂不是很尴尬?

是呀,所以需要更好的衡量这个超时时间的设置。

实践部分,公平锁(Fair Lock)代码

基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。同时还提供了异步(Async)反射式(Reactive)RxJava2标准的接口。它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson会等待5秒后继续下一个线程,也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的使用方法
fairLock.lock();

大家都知道,如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
fairLock.lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();

Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);

实践部分,红锁(RedLock)代码

基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();

大家都知道,如果负责储存某些分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);

// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

附加,开源分布式锁实现项目

基于redis的分布式锁实现客户端Redisson:

https://github.com/redisson/redisson

基于zookeeper的分布式锁实现:

http://curator.apache.org/curator-recipes/shared-reentrant-lock.html

敬请关注「搜云库技术团队」微信公众号,获取最新文章

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知我们,我们会立即删除并表示歉意。谢谢!

作者:菜蚜

来源:my.oschina.net/wnjustdoit/blog/1606215

如果对本文的内容有疑问,请在文章留言区留言,谢谢。

作者:搜云库技术团队 出处:https://www.souyunku.com 首发微信公众号:搜云库技术团队,微信号ID:souyunku 版权归原创作者所有,任何形式转载请联系作者

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-06-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
分布式锁中的王者方案 - Redisson
上篇讲解了如何用 Redis 实现分布式锁的五种方案,但我们还是有更优的王者方案,就是用 Redisson。
悟空聊架构
2022/05/13
1.4K0
分布式锁中的王者方案 - Redisson
Redisson–红锁(Redlock)–使用/原理
本文介绍为什么要使用Redis的红锁(Redlock)、什么是Redis的红锁以及Redis红锁的原理。
终有救赎
2023/10/22
4.1K0
redis 分布式锁的 5个坑 Redission的Rlock trylock方法
RLock tryLock leaseTime 在 Redission 通过续约机制,每隔一段时间去检测锁是否还在进行,如果还在运行就将对应的 key 增加一定的时间,保证在锁运行的情况下不会发生 key 到了过期时间自动删除的情况 RLock tryLock WRONGTYPE Operation against a key holding the wrong kind of value 原因:用的方法与redis服务器中存储数据的类型存在冲突。 比如:有一个key的数据存储的是list类型的,但使用redis执行数据操作的时候却使用了非list的操作方法。 RLock和Lock获取锁的方法:关键是:long leaseTime参数,自动超时时间的设置,解决finally异常导致锁未正常释放的情况。 该RLock接口主要继承了Lock接口还有其他Redisson, 并扩展了部分方法, 比如:boolean tryLock(long waitTime, long leaseTime, TimeUnit unit)新加入的leaseTime主要是用来设置锁的过期时间, 如果超过leaseTime还没有解锁的话, redis就强制解锁. leaseTime的默认时间是30s
oktokeep
2024/10/09
6060
Redis分布式锁实现Redisson 15问
在一个分布式系统中,由于涉及到多个实例同时对同一个资源加锁的问题,像传统的synchronized、ReentrantLock等单进程情况加锁的api就不再适用,需要使用分布式锁来保证多服务实例之间加锁的安全性。常见的分布式锁的实现方式有zookeeper和redis等。而由于redis分布式锁相对于比较简单,在实际的项目中,redis分布式锁被用于很多实际的业务场景中。
三友的java日记
2022/07/27
5800
Redis分布式锁实现Redisson 15问
Redis集群下的RedLock算法(真分布式锁) 实践
在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段。 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增加一点复杂的设计就可以获得更好的可靠性。 这篇文章的目的就是尝试提出一种官方权威的用Redis实现分布式锁管理器的算法,我们把这个算法称为RedLock。
搜云库技术团队
2019/10/18
1.6K0
分布式锁—4.Redisson的联锁和红锁
比如锁定一个库存 + 锁定一个订单 + 锁定一个积分,一次性锁定多个资源,这些被锁定的多个资源都不能让其他线程随意修改。然后当前线程一次性更新这些资源后,再逐一释放多个锁。
东阳马生架构
2025/05/14
1140
分布式锁用Redis还是Zookeeper?
系统 A 是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户下单。
chengcheng222e
2021/11/04
2650
redisson应用之分布式锁和同步器
Redisson的分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口,同时还支持自动过期解锁。
kl博主
2023/11/18
3290
面试官:分布式锁最终解决方案是RedLock吗?为什么?
RedLock 是 Redis 分布式锁的一种实现方案,由 Redis 的作者 Salvatore Sanfilippo 提出。
磊哥
2024/01/10
6620
面试官:分布式锁最终解决方案是RedLock吗?为什么?
分布式锁用Redis坚决不用Zookeeper?
系统 A 是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户下单。
数据和云
2019/07/30
4.3K1
Redis分布式锁存在的问题
假设有这样一个场景,在一个购票软件上买一张票,但是此时剩余票数只有一张或几张,这个时候有几十个人都在同时使用这个软件购票。在不考虑任何影响下,正常的逻辑是首先判断当前是否还有剩余的票,如果有,那么就进行购买并扣减库存数,否则就会提示票数不足,购买失败。伪代码如下:
闻说社
2022/12/28
4270
Redis分布式锁存在的问题
分布式锁用 Redis 还是 Zookeeper?
实际开发中,使用的最多还是Redis和Zookeeper,所以,本文就只聊这两种。
田维常
2022/03/22
3130
分布式锁用 Redis 还是 Zookeeper?
一起来学redis redission
redis 的客户端有jedis、lettuce、redission;我个人比较推荐的是redission,因为它的分布式锁和缓存实在是太优秀了。Redisson采用了基于NIO的Netty框架,封装了大家常用的集合类以及原子类、锁等工具。
六个核弹
2022/12/23
2.1K0
一起来学redis redission
分布式锁-这一篇全了解(Redis实现分布式锁完美方案)[通俗易懂]
在某些场景中,多个进程必须以互斥的方式独占共享资源,这时用分布式锁是最直接有效的。
全栈程序员站长
2022/07/28
1.4K0
分布式锁-这一篇全了解(Redis实现分布式锁完美方案)[通俗易懂]
如何使用Redisson实现分布式锁?
在分布式系统中,当多个线程(或进程)同时操作同一个资源时,为了保证数据一致性问题,所以就需要一种机制来确保在同一时间只有一个线程(或进程)能够对资源进行修改,这就是分布式锁的作用。
磊哥
2024/01/02
8890
如何使用Redisson实现分布式锁?
分布式锁其实很简单,6行代码教你实现redis分布式锁,千万不要再用redisTemplate写redis分布式锁代码实现
分布式锁是一种用于协调分布式系统中多个节点之间对共享资源进行访问控制的机制。它可以确保在分布式环境下,同一时间只有一个节点能够获取到锁,并且其他节点需要等待释放锁后才能获取。
小小鱼儿小小林
2024/05/25
1.6K0
分布式锁其实很简单,6行代码教你实现redis分布式锁,千万不要再用redisTemplate写redis分布式锁代码实现
Redis高并发分布式锁详解
  1.为了解决Java共享内存模型带来的线程安全问题,我们可以通过加锁来保证资源访问的单一,如JVM内置锁synchronized,类级别的锁ReentrantLock。
忧愁的chafry
2022/10/30
1.2K0
Redis高并发分布式锁详解
【高并发】你知道吗?大家都在使用Redisson实现分布式锁了!!
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:
冰河
2020/10/29
1.3K0
【高并发】你知道吗?大家都在使用Redisson实现分布式锁了!!
一个Redis分布式锁的实现引发的思考
再把视角移到 Redis 服务器来,就会发现 单点问题 的存在,此时分布式锁就无法使用了。
Java4ye
2024/06/17
1800
夜深人静了,我们来学学分布式锁
在我们的系统还没有使用分布式架构的时候,我们可以用同步锁或者Lock锁,来保证多线程并发的时候,同一时间只有一个线程修改共享变量或者执行代码块,但是当我们现在大部分系统都是分布式集群部署的,单纯的同步锁和Lock锁只能保证单个实例上的数据一致性,多实例就失去了作用。
故里
2020/12/08
2850
夜深人静了,我们来学学分布式锁
推荐阅读
相关推荐
分布式锁中的王者方案 - Redisson
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验