基于 Redis 实现分布式锁的全过程

前言

这一篇文章拖了有点久,虽然在项目中使用分布式锁的频率比较高,但整理成文章发布出来还是花了一点时间。在一些移动端、用户量大的互联网项目中,经常会使用到 Redis 分布式锁作为控制访问高并发的工具。

一、关于分布式锁

总结:分布式锁是一种在分布式系统中用于控制并发访问的机制。

在分布式系统中,多个客户端同时对一个资源进行操作时,容易影响数据的一致性。分布式锁的主要作用就是确保同一时刻只有一个客户端能够对某个资源进行操作,以避免数据不一致的问题。

主要应用场景:

  • 数据库并发控制:在分布式数据库中,多个线程同时对某张表进行操作时,可能会出现并发冲突问题,使用分布式锁可以确保同一时刻只有一个线程能够对该表进行操作,避免并发冲突。
  • 分布式缓存:在分布式缓存中,如果多个线程同时对某个缓存进行操作,可能会出现缓存数据不一致的问题。使用分布式锁可以确保同一时刻只有一个线程能够对该缓存进行操作,保证缓存数据的一致性。
  • 分布式任务调度:在分布式任务调度中,多个线程同时执行某个任务,可能出现任务被重复执行的问题,使用分布式锁可以确保同一时刻只有一个线程能够执行该任务,避免任务被重复执行。

目前主流的分布式锁实现方案是基于 Redis 来实现的,今天要分享的有 2 种实现: 基于 RedLock 红锁和基于 setIfAbsent() 方法


二、RedLock 红锁(不推荐)

RedLock 对于多节点(集群)的分布式锁算法使用了多个实例来存储锁信息,这种方式可以提高获取锁的速度和成功率,从而可以有效地防止单点故障;

但由于 RedLock 的实现比较复杂,且容易因为配置不正确而导致锁无法获取。此外,如果 Redis 服务宕机,也会导致锁无法正常使用。

RedLock 简单图示

RedLock 会对集群的每个节点进行加锁,如果大多数(N/2+1)加锁成功了,则认为获取锁成功。这个过程中可能会因为网络问题,或节点超时的问题,影响加锁的性能,故而在最新的 Redisson 版本中中已经正式宣布废弃 RedLock。

以下是一个简易的 demo 实现:

包括两部分:暴露给业务系统逻辑层使用的静态方法、锁的底层实现。思路用代码和注释说得比较清楚了,大家可以看一下:

    @Override
    public String set(String key, String value, RedisSetArgs redisSetArgs) {
        //这里引入的是 Jedis 的客户端,后来被抛弃了,Redis 推荐的是 Redission 
        try (Jedis jedis = getJedis()) {
            SetParams setParams = new SetParams();
            if (redisSetArgs.isNx()) {
                setParams.nx();
            } else if (redisSetArgs.isXx()) {
                setParams.xx();
            }
            if (Objects.nonNull(redisSetArgs.getEx())) {
                setParams.ex(redisSetArgs.getEx());
            } else if (Objects.nonNull(redisSetArgs.getPx())) {
                setParams.px(redisSetArgs.getPx());
            }
            return jedis.set(SafeEncoder.encode(buildKey(key)), SafeEncoder.encode(value), setParams);
        }
    }

    public static boolean unlock(String key, String requestId) {
        //Lua 脚本
        Object result = RedisCacheCommonFactory.getRedisCache().loose(LUA_SCRIPT, Collections.singletonList(key), Collections.singletonList(requestId));
        if (NumberUtils.LONG_ONE.equals(result)) {
            return true;
        }
        return false;
    }

    @Override
    public Object loose(String script, List<String> keys, List<String> args) {
        try (Jedis jedis = getJedis()) {
            return jedis.eval(SafeEncoder.encode(script), keys.stream().map(this::buildKey).map(SafeEncoder::encode).collect(Collectors.toList()),
                    args.stream().map(SafeEncoder::encode).collect(Collectors.toList()));
        }
    }

三、基于 redisTemplate

以下推荐的是在分布式集群环境中的最佳实践,其实无论是单机还是集群,保证原子性都是第一位的,如果能同时保证性能和高可用,那么就是一个可靠的分布式锁解决方案。

主要思路是:设置锁时,使用 redisTemplate,因为其底层实际包含了 setnx 、expire 的功能,起到了原子操作的效果。给 key 设置随机且唯一的值,并且只有在 key 不存在时才设置成功返回 True,并且设置 key 的过期时间(最好是毫秒级别)。

RedLock 简单图示

基于 setIfAbsent() 方法简单图示

以下同样给出一个简单的示例,包括两部分:暴露给业务系统逻辑层使用的静态方法、锁的底层实现。注释写得比较清楚了:

    public Boolean setIfAbsent(String key, String value, Duration timeout) {
        return this.redisTemplate.opsForValue().setIfAbsent(key, value, timeout);
    }


    public Boolean loose(String unLockKeyCheck) {
        if (unLockKeyCheck== null) {
            return Boolean.FALSE;
        }
        if (this.unLockRedisResult(redisLockKey, unLockKeyCheck)) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    private Boolean unLockRedisResult(String redisLockKey, String value) {
        //Lua 脚本
        RedisScript<Long> commonRedisLuaScript = new GeneraCommonRedisLuaScript<>(redisLuaScript, Long.class);
        Long value = redisTemplate.execute(commonRedisLuaScript, Collections.singletonList(key), value);
        boolean flag = Objects.NonNull(value) && NumberUtils.Integer_ONE.equals(value)
        return flag ;
    }

四、使用示例

下面分别给出两个使用示例分别来介绍怎么在具体的业务场景中去使用的 demo,一般来说针对数据库的并发操作和多线程的并发任务操作,会使用得比较多。

至于为什么不使用分布式锁去保证缓存数据的一致性,其实是有专门的分布式缓存方案的:什么是 Redis 缓存?它解决了什么问题?怎么使用它?-CSDN博客

    @Test
    public void testRedLock(){
        final String requestId = UUIDUtils.generateUUID();
        if (RedisRedLock.attemptLock("xxxSys.insert.xxxId(唯一)", requestId, 3L, TimeUnit.SECONDS)) {
            try {
                log.info("数据库xxx高并发写入成功!");
            } catch (Throwable e) {
                log.error("失败信息: error", e);
            } finally {
                RedisRedLock.unlock("xxxSys.insert.xxxId(唯一)", requestId);
            }
        }
    }
    @Test
    public void testDistributedLock(){
        RedisLock redisLock = RedisLockUtils.getRedisLock("xxxSys.insert.xxxId(唯一)" ,3,1);
        Assert.hasText(redisLock.getRedisLock(), "操作出错,请检查!");
        if (redisLock .loose("xxxSys.insert.xxxId(唯一)")){
            log.info("释放锁成功!");
        }
    }

五、文章小结

到这里基于 Redis 实现分布式锁的全过程就分享完了,其实基于 Redis 实现分布式锁还有许多底层和实际应用的情况没有展开来说。目前笔者虽然有较多使用,但还是感到技术的海洋深不见底:学到的越多就感觉到自己的不足越多。

最后,如果文章有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区里交流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/670406.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QT 信号和槽教程,窗体和控件对象之间的沟通一般都使用信号和槽

Qt的信号和槽&#xff08;Signals and Slots&#xff09;机制是一种强大的对象间通信方式&#xff0c;它允许对象在完全解耦的情况下相互通信。以下是关于Qt信号和槽的简明教程&#xff1a; 基本概念 信号&#xff08;Signal&#xff09;&#xff1a;信号是由Qt对象发出的通知…

OpenAI已全面开放自定义GPT以及文件上传等功能

今天&#xff0c;OpenAI兑现了前段时间做出的承诺&#xff1a;免费向所有用户开放GPT-4o。这意味着所有的免费用户都能使用自定义GPT模型、分析图表等其他GPT-4o新功能了。现在ChatGPT界面长这样&#xff1a; 可以看出&#xff0c;免费用户也能使用GPT store中定义好的模型&…

构建智慧监控系统的功能架构,保障安全与便利

智慧监控系统作为现代城市安全管理的重要工具&#xff0c;不仅能够提供有效的安防监控&#xff0c;还能为人们的生活带来更多的便利。本文将探讨智慧监控系统的功能架构&#xff0c;以实现安全和便利的双重目标。 ### 1. 智慧监控系统背景 随着城市化进程的加速&#xff0c;人…

构建高效便捷的家政平台系统——打造优质家政服务的关键

随着人们生活节奏的加快和工作压力的增大&#xff0c;家政服务的需求日益增长。为了满足这一需求&#xff0c;家政平台系统应运而生。本文将探讨家政平台系统的整体架构&#xff0c;以实现高效便捷的家政服务&#xff0c;打造优质家政体验。 ### 1. 家政平台系统背景 随着现代…

语音降噪算法库介绍

一.语音降噪技术方向介绍 软件上进行语音降噪目前主要是两个方向&#xff1a;传统降噪算法和AI降噪算法&#xff0c;他们各有千秋&#xff0c;目前看他们各有千秋&#xff0c;有各自适用场景。 推荐一个不错的人工智能学习网站&#xff0c;通俗易懂&#xff0c;内容全面&#…

vue3组件传值---vue组件通过属性,事件和provide,inject进行传值

通过属性传值&#xff08;父传子&#xff09; vue的组件具有props自建属性&#xff08;自定义名称&#xff0c;类似于class&#xff0c;id的属性&#xff09;&#xff0c;通过这个属性&#xff0c;父组件可以向子组件传递参数&#xff0c;从而实现组件之间的信息传递&#xff0…

SpringSecurity6从入门到实战之Filter过滤器回顾

SpringSecurity6从入门到实战之Filter过滤器回顾 如果没有SpringSecurity这个框架,我们应该通过什么去实现客户端向服务端发送请求时,先检查用户是否登录,登录了才能访问.否则重定向到登录页面 流程图如下 官方文档&#xff1a;https://docs.spring.io/spring-security/referen…

自动化办公01 smtplib 邮件⾃动发送

目录 一、准备需要发送邮件的邮箱账号 二、发送邮箱的基本步骤 1. 登录邮箱 2. 准备数据 3. 发送邮件 三、特殊内容的发送 1. 发送附件 2. 发送图片 3. 发送超文本内容 4.邮件模板内容 SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;即简单邮件传输协议…

单点11.2.0.3备份恢复到单点11.2.0.4

保命法则&#xff1a;先备份再操作&#xff0c;磁盘空间紧张无法备份就让满足&#xff0c;给自己留退路。 场景说明&#xff1a; 1.本文档的环境为同平台、不同版本&#xff08;操作系统版本可以不同&#xff0c;数据库小版本不同&#xff09;&#xff0c;源机器和目标机器部…

设计模式(二)工厂模式

文章目录 工厂模式简介简单工厂&#xff08;Simple Factory&#xff09;结构具体实现优缺点 工厂方法&#xff08;Factory Method&#xff09;结构具体实现优缺点 抽象工厂&#xff08;Abstract Factory&#xff09;结构具体实现优缺点 工厂模式简介 工厂模式是一种创建型模式…

[数据集][目标检测]焊接处缺陷检测数据集VOC+YOLO格式3400张8类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3400 标注数量(xml文件个数)&#xff1a;3400 标注数量(txt文件个数)&#xff1a;3400 标注…

能源SCI期刊,中科院4区,审稿快,IF=3.858

一、期刊名称 Frontiers in Energy Research 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;能源 影响因子&#xff1a;3.858 中科院分区&#xff1a;4区 三、期刊征稿范围 能源研究前沿出版了整个领域的严格同行评审研究&#xff0c;重点是可持续和环境…

YOLOv8 深度详解!一文看懂,快速上手

YOLOv8 深度详解&#xff01;一文看懂&#xff0c;快速上手 原文&#xff1a;YOLOv8 深度详解&#xff01;一文看懂&#xff0c;快速上手 - 知乎 (zhihu.com) YOLOv8 是 ultralytics 公司在 2023 年 1月 10 号开源的 YOLOv5 的下一个重大更新版本&#xff0c;目前支持图像分类…

Low Memory Killer in Android

目录 低内存管理&#xff08;Linux vs Android&#xff09; Linux内存回收 shrink_slab原理 shrink_zone原理 oom killer oom killer设计原则 OOM killer具体实现 android的lmk(Low Memory Killer) Android系统特点 oom killer在android中的不足 ​​​​​​​LMK概…

乡村振兴与农村环境整治:加强农村环境治理,改善农村人居环境,打造干净整洁、生态宜居的美丽乡村

目录 一、引言 二、农村环境整治的重要性 1、提升农民生活质量 2、促进农村经济发展 3、保护农村生态环境 三、当前农村环境面临的问题 1、垃圾处理不当 2、污水处理设施缺乏 3、农业面源污染严重 四、加强农村环境治理的措施 1、完善农村垃圾处理体系 2、加强农村…

2010-2015 年阿拉斯加北坡苔原植物功能类型连续覆盖图

ABoVE: Tundra Plant Functional Type Continuous-Cover, North Slope, Alaska, 2010-2015 2010-2015 年阿拉斯加北坡苔原植物功能类型连续覆盖图 简介 文件修订日期&#xff1a;2021-08-27 数据集版本: 1 摘要 该数据集以 30 米的分辨率提供了阿拉斯加北坡约 12.5 万平方…

CodeMirror 创建标签计算编辑器

在日常开发中对于一些数据计算场景可能会遇到标签计算的需求&#xff0c;下面关于如何使用CodeMirror实现标签计算编辑功能。 1&#xff0c;结果图 2&#xff0c;主体代码逻辑 大家只需要复制粘贴主要codeMirror使用逻辑即可 <template><el-dialogref"dialogRe…

7.2 Go 使用error类型

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

godot.bk

1.搜索godot国内镜像&#xff0c;直接安装&#xff0c;mono是csharp版本 2.直接解压&#xff0c;50m&#xff0c;无需安装&#xff0c;直接运行 3.godot里分为场景&#xff0c;节点 主场景用control场景&#xff0c;下面挂textureact放背景图片&#xff0c;右键实例化子场景把…

自然语言处理(NLP)—— 置信度(Confidence)

1. 置信度&#xff08;Confidence&#xff09;的概念 置信度&#xff08;Confidence&#xff09;在机器学习和统计中通常指一个模型对其做出的预测是正确的确信程度。在分类任务中&#xff0c;置信度通常由模型赋予特定类别的概率值来表示。例如&#xff0c;在文本分类或实体识…