手把手教你实现一个基于Redis的分布式锁

SpringBoot使用Redis实现分布式锁 一、Redis分布式锁概念篇 建议直接采用Redis的官方推荐的Redisson作为redis的分布式锁 1.1、为什么要使用分布式锁 我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的Java多线程的18般武艺进行处理,并且可以完美的运行,毫无Bug! 注意这是单机应用,也就是所有的请求都会分配到当前服务器的JVM内部,然后映射为操作系统的线程进行处理!而这个共享变量只是在这个JVM内部的一块内存空间! 后来业务发展,需要做集群,一个应用... 阅读详情

简介

分布式锁在分布式系统中非常常见,比如对公共资源进行操作,如卖车票,同一时刻只能有一个节点将某个特定座位的票卖出去;如避免缓存失效带来的大量请求访问数据库的问题

设计

这非常像一道面试题:如何实现一个分布式锁?在简介中,基本上已经对这个分布式工具提出了一些需求,你可以不着急看下面的答案,自己思考一下分布式锁应该如何实现?

首先我们需要一个简单的答题套路:需求分析、系统设计、实现方式、缺点不足

需求分析

能够在高并发的分布式的系统中应用

需要实现锁的基本特性:一旦某个锁被分配出去,那么其他的节点无法再进入这个锁所管辖范围内的资源;失效机制避免无限时长的锁与死锁

进一步实现锁的高级特性和JUC并发工具类似功能更好:可重入、阻塞与非阻塞、公平与非公平、JUC的并发工具(Semaphore, CountDownLatch, CyclicBarrier)

系统设计

转换成设计是如下几个要求:

对加锁、解锁的过程需要是高性能、原子性的

需要在某个分布式节点都能访问到的公共平台上进行锁状态的操作

所以,我们分析出系统的构成应该要有锁状态存储模块、连接存储模块的连接池模块、锁内部逻辑模块

锁状态存储模块

分布式锁的存储有三种常见实现,因为能满足实现锁的这些条件:高性能加锁解锁、操作的原子性、是分布式系统中不同节点都可以访问的公共平台:

数据库(利用主键唯一规则、MySQL行锁)

基于Redis的NX、EX参数

Zookeeper临时有序节点

由于锁常常是在高并发的情况下才会使用到的分布式控制工具,所以使用数据库实现会对数据库造成一定的压力,连接池爆满问题,所以不推荐数据库实现;我们还需要维护Zookeeper集群,实现起来还是比较复杂的。如果不是原有系统就依赖Zookeeper,同时压力不大的情况下。一般不使用Zookeeper实现分布式锁。所以缓存实现分布式锁还是比较常见的,因为缓存比较轻量、缓存的响应快、吞吐高、还有自动失效的机制保证锁一定能释放。

连接池模块

可使用JedisPool实现,如果后期性能不佳,可考虑参照HikariCP自己实现

锁内部逻辑模块

基本功能:加锁、解锁、超时释放

高级功能:可重入、阻塞与非阻塞、公平与非公平、JUC并发工具功能

实现方式

存储模块使用Redis,连接池模块暂时使用JedisPool,锁的内部逻辑将从基本功能开始,逐步实现高级功能,下面就是各种功能实现的具体思路与代码了。

加锁、超时释放

NX是Redis提供的一个原子操作,如果指定key存在,那么NX失败,如果不存在会进行set操作并返回成功。我们可以利用这个来实现一个分布式的锁,主要思路就是,set成功表示获取锁,set失败表示获取失败,失败后需要重试。再加上EX参数可以让该key在超时之后自动删除。

下面是一个阻塞锁的加锁操作,将循环去掉并返回执行结果就能写出非阻塞锁(就不粘出来了):

public void lock(String key, String request, int timeout) throws InterruptedException {

    Jedis jedis = jedisPool.getResource();

    while (timeout >= 0) {

        String result = jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, DEFAULT_EXPIRE_TIME);

        if (LOCK_MSG.equals(result)) {

            jedis.close();

            return;

        }

        Thread.sleep(DEFAULT_SLEEP_TIME);

        timeout -= DEFAULT_SLEEP_TIME;

    }

}

但超时时间这个参数会引发一个问题,如果超过超时时间但是业务还没执行完会导致并发问题,其他进程就会执行业务代码,至于如何改进,下文会讲到。

解锁

最常见的解锁代码就是直接使用jedis.del()方法删除锁,这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,即使这把锁不是它的。

比如可能存在这样的情况:客户端A加锁,一段时间之后客户端A解锁,在执行jedis.del()之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。

所以我们需要一个具有原子性的方法来解锁,并且要同时判断这把锁是不是自己的。由于Lua脚本在Redis中执行是原子性的,所以可以写成下面这样:

public boolean unlock(String key, String value) {

    Jedis jedis = jedisPool.getResource();

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

    Object result = jedis.eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(value));

    jedis.close();

    return UNLOCK_MSG.equals(result);

}

来用测试梭一把

此时我们可以来写个测试来试试有没有达到我们想要的效果,上面的代码都写在src/main/java下的RedisLock里,下面的测试代码需要写在src/test/java里,因为单元测试只是测试代码的逻辑,无法测试真实连接Redis之后的表现,也没办法体验到被锁住带来的紧张又刺激的快感,所以本项目中主要以集成测试为主,如果你想试试带Mock的单元测试,可以看看这篇文章。

那么集成测试会需要依赖一个Redis实例,为了避免你在本地去装个Redis来跑测试,我用到了一个嵌入式的Redis工具以及如下代码来帮我们New一个Redis实例,尽情去连接吧 ~ 代码可参看EmbeddedRedis类。另外,集成测试使用到了Spring,是不是倍感亲切?相当于也提供了一个集成Spring的例子。

@Configuration

public class EmbeddedRedis implements ApplicationRunner {

    private static RedisServer redisServer;

    @PreDestroy

    public void stopRedis() {

        redisServer.stop();

    }

    @Override

    public void run(ApplicationArguments applicationArguments) {

        redisServer = RedisServer.builder().setting("bind 127.0.0.1").setting("requirepass test").build();

        redisServer.start();

    }

}

对于需要考虑并发的代码下的测试是比较难且比较难以达到检测代码质量的目的的,因为测试用例会用到多线程的环境,不一定能百分百通过且难以重现,但本项目的分布式锁是一个比较简单的并发场景,所以我会尽可能保证测试是有意义的。

我第一个测试用例是想测试一下锁的互斥能力,能否在A拿到锁之后,B就无法立即拿到锁:

@Test

public void shouldWaitWhenOneUsingLockAndTheOtherOneWantToUse() throws InterruptedException {

    Thread t = new Thread(() -> {

        try {

            redisLock.lock(lock1Key, UUID.randomUUID().toString());

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    });

    t.start();

    t.join();

    long startTime = System.currentTimeMillis();

    redisLock.lock(lock1Key, UUID.randomUUID().toString(), 3000);

    assertThat(System.currentTimeMillis() - startTime).isBetween(2500L, 3500L);

}

但这仅仅测试了加锁操作时候的互斥性,但是没有测试解锁是否会成功以及解锁之后原来等待锁的进程会继续进行,所以你可以参看一下testLockAndUnlock方法是如何测试的。不要觉得写测试很简单,想清楚测试的各种情况,设计测试情景并实现并不容易。然而以后写的测试不会单独拿出来讲,毕竟本文想关注的还是分布式锁的实现嘛。

超时释放导致的并发问题

问题:如果A拿到锁之后设置了超时时长,但是业务执行的时长超过了超时时长,导致A还在执行业务但是锁已经被释放,此时其他进程就会拿到锁从而执行相同的业务,此时因为并发导致分布式锁失去了意义。

如果你说我可以通过在key快要过期的时候判断下任务有没有执行完毕,如果还没有那就自动延长过期时间,那么确实可以解决并发的问题,但是超时时长也就失去了意义,我设置超时时长就是为了想在超时的时候自动释放锁,避免其他进程被阻塞。所以个人认为最好的解决方式是在锁超时的时候通知服务器去停掉超时任务,但是结合上Redis的消息通知机制不免有些过重了;或者让业务代码自己去检查是否执行超时,但是工具不就是让业务实现人员更加关注业务吗?

所以这个问题上,分布式锁的Redis实现并不靠谱

单点故障导致的并发问题

建立主从复制架构,但是还是会由于主节点挂掉导致某些数据还没同步就已经丢失,所以推荐多主架构,有N个独立的master服务器,客户端会向所有的服务器发送获取锁的操作。

可以继续优化的地方

提供多主配置方式与加锁解锁实现

使用订阅解锁消息与Semaphore代替Thread.sleep()避免时间浪费,可参考Redisson中RedissonLock的lockInterruptibly方法

Java高架构师、分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师视频免费学习加群:835638062 点击链接加入群聊【Java高级架构】:https://jq.qq.com/?_wv=1027&k=5S3kL3v

Java基于redis实现分布式锁代码实例 主要介绍了Java基于redis实现分布式锁代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 立即下载

相关推荐

数理统计四大分布---正态分布、卡方分布、学生t分布和F分布

在统计学上,我们会遇到一些常见的分布,除了正态分布外,,如t检验对应的t分布,检验对应的分布,方差分析对应的F分布等。这些分布是统计学的基础,在假设检验、方差分析等领域都起着至关重要的作用。在此,我们对这四种分布做详细的介绍,并附上MATLAB代码。 四种分布的讲解摘自MATLAB 2016的统计学和机器学习工具箱,有兴趣的可以去查看英文原版。 另外,为了方便,上传文档为图片格式,可能有些模糊...

suncangan的博客 5万+

java.lang.RuntimeException: Can't start redis server. Check logs for details.

今天在做应用的单元测试时,遇到一个使用embedded-redis的问题,记录如下: 在启动单元测试时,本地的redisServer启动失败,异常栈信息如下: java.lang.RuntimeException: Can't start redis server. Check logs for details. at redis.embedded.AbstractRedisInsta...

yuxxz的专栏 6413

Go语言集成Ollama本地大模型:gollama库实战指南

在当今的AI应用开发中,大型语言模型(LLM)的集成已成为关键技术。其核心原理是通过API调用,将自然语言处理能力嵌入到各类应用中。从技术价值看,本地化部署的LLM解决了数据隐私、网络延迟和持续成本三大痛点,尤其适合处理敏感数据或对实时性要求高的场景。在工程实践中,开发者常使用专门的客户端库来简化与模型服务的交互,例如通过封装REST API、提供类型安全接口和流式处理支持,从而提升开发效率和代码健壮性。具体到Go语言生态,结合Ollama这一流行的本地大模型运行工具,可以实现完全离线、低延迟的AI功能集成

weixin_28728443的博客 606

使用 redis_builder 管理 redis 集群

       在 <一个 C++ redis 集群管理工具> 中主要讲述了如何使用 redis_builder 工具创建 redis 集群的过程,除此之外,该工具还具有更为强大的 redis 客户端管理功能(相对于官方提供的 redis-cli功能要强大的多)。本文主要讲解使用 redis_builder 以交互方式管理 redis 集群的过程。 一、启动 redis_build...

网络编程与服务器编程 347

避坑指南:STM32CubeMX配置USB虚拟串口时Heap Size设置的那些坑

本文深入剖析了使用STM32CubeMX配置USB虚拟串口时,因Heap Size设置不当导致设备无法识别的常见问题。文章从故障现象出发,详细解释了堆内存不足如何影响USB枚举过程,并提供了从内存布局分析、动态监控到系统化调试的完整解决方案,帮助开发者从根本上避免这一配置陷阱。

ttt77的博客 599

利用Langchain生成合成数据的实践指南

合成数据是相对于从真实世界事件中收集的数据而言的人工生成的数据。它用于模拟真实数据,同时避免隐私泄露或遇到真实数据中的局限性。:尽管合成数据有诸多优点,但应谨慎使用,因为它可能无法捕捉真实世界的复杂性。

eahba的博客 398

springboot集成测试内嵌Redis服务器

第一步上maven <dependency> <groupId>com.github.kstyrc</groupId> <artifactId>embedded-redis</artifactId> </dependency> 第二步 @Autow...

helpapa的博客 2493

YOLOv8+BiFPN双向特征金字塔:多尺度目标检测精度提升28%的实战教程

双向特征金字塔网络(BiFPN)是一种强化多尺度特征融合支持“自顶向下+自底向上”的双向特征流动,同时保留不同尺度的细节信息;引入加权融合机制,自动学习不同特征的重要性权重;裁剪冗余节点,在轻量化的同时提升融合效率。

m0_64661770的博客 493

【redis】详细介绍redis-server手动启动设置-使用环境Ubuntu

为便于大家的操作设置,在此简单声明下,本文操作使用的系统环境。 系统环境: Windows 7 旗舰版 Vmware Workstation 14 Pro Ubuntu 18.04.1 LTS Redis默认端口: 6379 手动启动 1、启动方式一  直接启动 # 命令终端执行以下命令 redis-server # 加上‘&amp;’号使redis以后台程序方式运行。强烈建议后台运行 re...

彳亍的技术博客 6536

PCB层叠结构与阻抗计算

1.Core 和 PP PCB两个重要组成部分:Core 和 Prepreg(半固态片,简称PP)。 如下图所示, Core 的两个表面都铺有铜箔,可作为信号层、电源层、地层等导电层,Core的上下表面之间填充的是固态材料; PP的表面不铺铜,在PCB中起填充作用,其材质是半固体的树脂材料,因此比Core略软一些。 在制作多层板时,需配合使用Core和PP,一般在两个Core 之间应选PP作为填...

sinat_15677011的博客 1万+

PCL 均匀采样【2026最新版】

根据输入点云数据创建一个微小的三维立方体集合,然后计算每个三维立方体的重心;由于该重心点不一定是原始点云中的点,直接用该点来表示立方体内所有的点会丢失原始点云的细小特征;所以使用k最近邻搜索寻找距离重心点最近的原始点来代替重心点。

点云侠的博客 41万+

redis并发锁 thinkphp5_手把手教你实现一个基于Redis的分布式锁

作者:王天一链接:https://www.wangtianyi.top简介分布式锁在分布式系统中非常常见,比如对公共资源进行操作,如卖车票,同一时刻只能有一个节点将某个特定座位的票卖出去;如避免缓存失效带来的大量请求访问数据库的问题设计这非常像一道面试题:如何实现一个分布式锁?在简介中,基本上已经对这个分布式工具提出了一些需求,你可以不着急看下面的答案,自己思考一下分布式锁应该如何实现?...

weixin_39997194的博客 1029

MIMO-OFDM通信系统性能仿真及MATLAB实现

将MIMO和OFDM技术结合起来,可以充分利用两者的优点,进一步提高无线通信系统的性能。在MIMO-OFDM系统中,多个天线同时发射多个子载波,称为多天线多载波(MIMO-OFDM)系统。随着无线通信技术的不断发展,MIMO-OFDM技术因其高效、可靠和适应性等优点已被广泛应用于新一代通信系统中。本篇文章旨在介绍MIMO-OFDM通信系统的基本原理及其性能评估方法,并结合MATLAB提供相关代码实现。本篇文章介绍了MIMO-OFDM通信系统的基本原理和性能评估方法,并结合MATLAB提供了相关的实现代码。

学习使你进步。 2219

另外一个进程已经为 dpkg frontend lock 加锁_手把手教你实现一个基于Redis的分布式锁...

作者:王天一链接:https://www.wangtianyi.top简介分布式锁在分布式系统中非常常见,比如对公共资源进行操作,如卖车票,同一时刻只能有一个节点将某个特定座位的票卖出去;如避免缓存失效带来的大量请求访问数据库的问题设计这非常像一道面试题:如何实现一个分布式锁?在简介中,基本上已经对这个分布式工具提出了一些需求,你可以不着急看下面的答案,自己思考一下分布式锁应该如何实现?首先我们需...

weixin_39882623的博客 684

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Redis实现分布式锁

在多线程并发的情况下,我们可以使用锁来保证一个代码块在同一时间内只能由一个线程访问。比如Java的synchronized关键字和Reentrantlock类等等。 这样子可以保证在同一个JVM进程内的多个线程同步执行。 如果在分布式的集群环境中,如何保证不同节点的线程同步执行呢? 怎么才能在分布式系统中,实现不同线程对代码和资源的同步访问? 对于单进程的并发场景,我们可以使用语言...

forever-and-ever的博客 4万+

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

如何用Redis实现分布式锁

为什么需要分布式锁 在聊分布式锁之前,有必要先解释一下,为什么需要分布式锁。 与分布式锁相对就的是单机锁,我们在写多线程程序时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来互斥以保证共享变量的正确性,其使用范围是在同一个进程中。如果换做是多个进程,需要同时操作一个共享资源,如何互斥呢?现在的业务应用通常是微服务架构,这也意味着一个应用会部署多个进程,多个进程如果需要修改MySQL中的同一行记录,为了避免操作乱序导致脏数据,此时就需要引入分布式锁了。 想要实现分布式锁,必须借助一个外部系统,所有

Hopefully Sky的博客 4万+

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

redis实现分布式锁的原理

redis实现分布式锁,从原理的实现,干货满满~~~

LYQ20010417的博客 1万+

分布式锁其实很简单,6行代码教你实现redis分布式锁,千万不要再用redisTemplate写redis分布式锁代码实现

分布式锁是一种用于协调分布式系统中多个节点之间对共享资源进行访问控制的机制。它可以确保在分布式环境下,同一时间只有一个节点能够获取到锁,并且其他节点需要等待释放锁后才能获取。以下是使用分布式锁的几个常见场景和原因:避免资源冲突:当多个节点需要同时对共享资源进行读写操作时,使用分布式锁可以确保同一时间只有一个节点能够执行写操作,避免数据冲突和一致性问题。防止重复处理:在某些业务场景中,可能会出现重复处理的问题,例如订单支付、秒杀等。

小小鱼儿小小林的博客 8461

如何用 Redis 实现一个分布式锁

场景模拟 一般电子商务网站都会遇到如团购、秒杀、特价之类的活动,而这样的活动有一个共同的特点就是访问量激增、上千甚至上万人抢购一个商品。然而,作为活动商品,库存肯定是很有限的,如何控制库存不让出现超买,以防止造成不必要的损失是众多电子商务网站程序员头疼的问题,这同时也是最基本的问题。 在秒杀系统设计中,超卖是一个经典、常见的问题,任何商品都会有数量上限,如何避免成功下订单买到商品的人数不超过商品数量的上限,这是每个抢购活动都要面临的难点。 针对大量的并发请求,我们可以通过 Redis 来抗,也就是说对于库存

个人站点:javatv.net 2742

Redis分布式锁实现

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

учёба 2424

Redis的分布式锁分析

开发同学都知道,在分布式系统中,为了保证多个机器并发操作的数据一致性,常常需要使用分布式锁机制。常用的比如数据库锁,ZK锁,Reis锁等等,今天我们就探讨下Redis分布式锁,说说他的实现原理和应用场景,并介绍如何正确使用分布式锁来解决并发问题

战斧的博客 7333
上一篇: 分布式存储的六大优点
下一篇: 浅谈Nginx之反向代理
普通网友
博客等级 码龄8年 55粉丝 197原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值