RedisSon高并发分布式锁实战RedisSon源码解读

news2024/11/25 12:36:40

Redis高并发分布式锁实战

1.分布式场景下的synchronized失效的问题–用redis实现分布式锁

synchronized是通过monitor实现的jvm级别的锁,如果是分布式系统,跑在不同的虚拟机上的tomcat上,会导致synchronized无法锁住对象 ----------- 需要分布式锁 redis

SET、SETEX、SETNX

SET key value
含义:

     SET KEY value  V-K
    相同的K 后写的覆盖先写的

image-20230608165040260

SETEX key seconds value
该命令相当于将下面两行操作合并为一个原子操作

SET key value
EXPIRE key seconds # 设置生存时间
含义(setex = set expire):

          SET KEY value  V-K 设置生命周期 
    相同的K 后写的覆盖先写的

image-20230608165944711

image-20230608170022805

SETNX key value
含义(setnx = SET if Not eXists):

       SET KEY value  V-K  ,key 不存在返回1 表示成功,key存在返回0

       SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

返回值:

       设置成功,返回 1 。

       设置失败,返回 0 。

image-20230608171143353

2.redis实现分布式锁

@RequestMapping("/redis-001")
public String redis001() {
    String key = "redis-001";
    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, "redis-001");
    if (!result) {
        return "error_code";
    }
    int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
    if (stock > 0) {
        int realStock = stock - 1;
        stringRedisTemplate.opsForValue().set("stock", realStock + "");
        System.out.println("扣减成功,剩余库存:" + realStock);
    } else {
        System.out.println("扣减失败,库存不足");
    }
    stringRedisTemplate.delete(key);
    return "end";
}

在 stringRedisTemplate.delete(key) 释放锁之前会有业务代码块,若出现异常抛出,则不能执行关锁的代码块

@RequestMapping("/redis-001")
public String redis001() {
    String key = "redis-001";
    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, "redis-001");
    if (!result) {
        return "error_code";
    }
    try {
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + "");
            System.out.println("扣减成功,剩余库存:" + realStock);
        } else {
            System.out.println("扣减失败,库存不足");
        }
    } finally {
        stringRedisTemplate.delete(key);
    }
    return "end";
}

在程序执行的任意时刻都有可能应为不可抗力因素突然终止,重启、宕机导致不能执行到finally代码块,所以必须要设置超时时间

    @RequestMapping("/redis-001")
    public String redis001() {
        String key = "redis-001";
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, "redis-001");
        stringRedisTemplate.expire(key,2000 ,TimeUnit.MILLISECONDS);
        if (!result) {
            return "error_code";
        }
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            stringRedisTemplate.delete(key);
        }
        return "end";
    }

redis设置的时候需要保证原子性

Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, "redis-001");
stringRedisTemplate.expire(key,2000 ,TimeUnit.MILLISECONDS);

解决方案

Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, clintId,2000 ,TimeUnit.MILLISECONDS);

若T1的锁设置失效时间20s,但是T1执行20s没有完成,此时T2可以获得锁,T1执行会在finally代码块中释放T2加的锁

    @RequestMapping("/redis-001")
    public String redis001() {
        String key = "redis-001";
        //不满足原子性
//        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, "redis-001");
//        stringRedisTemplate.expire(key,2000 ,TimeUnit.MILLISECONDS);
        String clintId = UUID.randomUUID().toString();
        //Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, "redis-001",2000 ,TimeUnit.MILLISECONDS);
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, clintId,2000 ,TimeUnit.MILLISECONDS);

        if (!result) {
            return "error_code";
        }

        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            if (clintId.equals(stringRedisTemplate.opsForValue().get(key))){
                 stringRedisTemplate.delete(key);
            }
        }

        return "end";
    }
finally {
            if (clintId.equals(stringRedisTemplate.opsForValue().get(key))){
                 stringRedisTemplate.delete(key);
            }
        }

上述代码来确定是否是自己的锁,是没有原子性的,使用redisson(lua)来解决这个问题

Redisson

image-20230613163801017

Redis 自 2.6 版本开始支持 Lua 脚本,是将所有操作打包成原子操作的一种机制。使用 Lua 脚本可以对 Redis 数据库进行复杂的操作,比如多个命令组合执行、避免分布式事务中的竞态条件等。

Lua 脚本在 Redis 中的原理是:将脚本发送到 Redis 服务器时,Redis 会先对脚本进行语法检查和编译,然后将编译后的字节码缓存起来并返回一个 SHA1 校验和。之后客户端每次需要执行这个脚本时,只需要将 SHA1 校验和发送给 Redis 服务器,Redis 通过校验和即可直接获取缓存中的字节码,避免了每次解析和编译 Lua 脚本的开销。

Lua 脚本的好处有以下几点:

  1. 原子性:Lua 脚本是 Redis 支持的最完整的事务形式,因为它们在 Redis 服务器上作为一个单独的脚本条目执行,因此能够保证所有操作的原子性。

  2. 灵活性:Lua 脚本方便对于 Redis 数据库进行复杂的操作,比如批量操作等。

  3. 性能:由于 Redis 会对 Lua 脚本进行预编译并缓存字节码,因此当相同的脚本被多次执行时,可以避免每次解析和编译脚本带来的开销。而且,Lua 脚本在 Redis 服务器中以单线程运行,相比于多线程,这样可以减少线程切换、锁等开销。

    image-20230615110608304

总之,Redis Lua 脚本具有良好的性能、灵活性和原子性,使得 Redis 支持更复杂、更安全地操作数据,提高了 Redis 在实际应用中的可靠性和稳定性。

Lua脚本语法

image-20230615143634666

image-20230615145300265

Redisson.lock()

image-20230615150628987

 <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                  "if (redis.call('exists', KEYS[1]) == 0) then " +
                      "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                      "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "return redis.call('pttl', KEYS[1]);",
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }
getName() //RLock redissonLock = redisson.getLock(lockKey);
internalLockLeaseTime //internalLockLeaseTime = unit.toMillis(leaseTime);->
          RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN); //getLockWatchdogTimeout() 默认30s 

 getLockName(threadId)  //final UUID id + threadId
  

设置锁成功如何执行看门狗机制实现续命

 RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        ttlRemainingFuture.addListener(new FutureListener<Long>() {
            @Override
            public void operationComplete(Future<Long> future) throws Exception {
                if (!future.isSuccess()) {
                    return;
                }

                Long ttlRemaining = future.getNow(); //成功是nil 即null
                // lock acquired
                if (ttlRemaining == null) {
                  	//成功一定进入的代码块
                    scheduleExpirationRenewal(threadId);
                }
            }
        });

image-20230615153223903

image-20230615160849995

没有获得锁的线程自旋等待,间歇性尝试加锁

image-20230615162117172

getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); //是阻塞等待不会占用资源

ttl时间内如果当前获得锁的线程执行完成了怎么办,订阅模式

image-20230615162952887

订阅的内容将在删除的时候更新

image-20230615163800191

image-20230615163903196

unlock使用了lua代码保证了原子操作

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

读数据压缩入门笔记05_字典转换

1. 瓶颈 1.1. 在网络带宽有限、存储昂贵的时期 1.2. 移动设备正日益成为人们访问互联网的首选的今天 1.3. 数据压缩成了缓解这些瓶颈的关键 2. 字典转换 2.1. dictionary transforms 2.2. 完全改变了人们对数据压缩的认知 2.2.1. 压缩变成了一种对各种类型的数据都有用的…

《C和指针》读书笔记(第十一章 动态内存分配)

目录 0 简介1 为什么使用动态内存分配2 malloc和free3 calloc和realloc4 使用动态分配的内存5 常见的动态内存错误6 内存分配实例6.1 排序一列整型值6.2 复制字符串6.3 变体记录的创建与销毁 7 总结 0 简介 在实际开发中&#xff08;C语言&#xff09;&#xff0c;数组的元素存…

JDK,JRE,JVM有什么区别?跨平台?跨语言?

JDK Java Development Kit&#xff08;Java开发工具包&#xff09;&#xff0c;提供了Java的开发环境和运行环境。包含Java源文件的编译器Javac&#xff0c;还有调试和分析工具。 JRE Java Runtime Environment&#xff08;Java运行环境&#xff09;包含了Java虚拟机&#xff…

WPF开发txt阅读器10:语音播报快进快退

文章目录 MySpeech类快进 文章目录 MySpeech类快进 txt阅读器系列&#xff1a; 需求分析和文件读写目录提取类&#x1f48e;列表控件与目录字体控件绑定&#x1f48e;前景/背景颜色书籍管理系统&#x1f48e;用树形图管理书籍语音播放&#x1f48e;播放进度显示 MySpeech类 …

MySQL 中有哪些锁?

数据库中锁的设计初衷处理并发问题&#xff0c;作为多用户共享资源&#xff0c;当出现并发访问的时候&#xff0c;数据库需要合理控制资源访问规则。锁就是实现这些访问规则中的重要数据。 锁的分类 根据加锁范围&#xff0c;MySQL 里面的锁可以分成全局锁、表级锁、行锁三类…

计算机视觉算法——BEV Perception算法总结

计算机视觉算法——BEV Perception算法总结 计算机视觉算法——BEV Perception算法总结1. Homograph Based——3D LaneNet2. Depth Based——LSS3. MLP Based——PON4. Transformer Based——BEVFormer5. Transformer Based——Translating Image into Maps 计算机视觉算法——…

急于生成人工智能是有风险的:如何保护数据

每天的业务用户都在尝试使用 ChatGPT 和其他生成式 AI 工具。事实上&#xff0c; Gartner 预测&#xff0c; 到 2025 年&#xff0c;30% 的营销内容将由生成式人工智能创建并由人类增强。 然而&#xff0c;像三星这样的公司已经发现&#xff0c;不了解新技术风险的用户正在成…

linux(线程控制)

目录&#xff1a; 1.线程创建 2.线程等待 3.线程终止 4.线程分离 5.线程ID -------------------------------------------------------------------------------------------------------------------------------- 1.线程创建 pthread_create pthread_t *pthread 是一个输出型…

Nucleo-F411RE (STM32F411)LL库体验 5 - 通用定时器TIM2的使用

Nucleo-F411RE &#xff08;STM32F411&#xff09;LL库体验 5 - 通用定时器TIM2的使用 1、简述 设定TIM2&#xff0c;计数周期为10KHZ&#xff0c;即计时1s需要10000次&#xff0c;通过shell命令动态修改reload值&#xff0c;来更改定时器的频率。 假定设定TIM2 counter cloc…

第五章 部署PKI与证书服务

❄️作者介绍&#xff1a;奇妙的大歪❄️ &#x1f380;个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01;&#x1f380; &#x1f43d;个人简介&#xff1a;云计算网络运维专业人员&#x1f43d; 前言 PKI&#xff08;公钥加密基础结构&#xff09;&#xff1a;通…

【C++】list的使用和模拟实现

目录 1.什么是list2.list的一些接口3.list的模拟实现3.1 迭代器3.2 list主体3.2.1 构造函数3.2.2 拷贝构造、赋值重载3.2.3 主体内引入迭代器3.2.4 insert和erase3.2.5 clear和析构函数 3.3 const迭代器的实现3.4 实现迭代器的operator-> 4.总结list迭代器的实现 1.什么是li…

领域驱动应用架构实践

一个合适的应用架构不仅能促使项目朝着好的方向发展&#xff0c;易于维护&#xff0c;也能指导团队成员有效协作。 DDD是站在领域的角度来驱动应用架构的落地&#xff0c;接下来将介绍一种落地方案。 架构分层 首先在架构层次方面&#xff0c;在遵循DDD的分层架构模式的同时&…

STM32单片机(六)TIM定时器 -> 第五节:TIM输入捕获

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

Nucleo-F411RE (STM32F411)LL库体验 4 -Letter Shell移植与调试

Nucleo-F411RE &#xff08;STM32F411&#xff09;LL库体验 4 -Letter Shell移植与使用 1、串口的初始化 Nucleo-F411RE自带st-link&#xff0c;并支持虚拟串口的功能&#xff0c;根据原理图&#xff0c;st-link的rx tx接到了Nucleo-F411RE的PA2 PA3&#xff0c;所以我们要初…

以太网MII、RMII、GMII、RGMII(三)

目录 一、MII 二、RMII 三、GMII 四、RGMII 以太网硬件主要包括OSI的最下面两层&#xff0c;物理层和数据链路层&#xff0c;前者的芯片为PHY&#xff0c;后者的芯片为MAC控制器。而MAC与PHY之间的常用的数据传输接口有MII、RMII、GMII、RGMII。 模式 时钟 位宽 速率 M…

pytorch笔记:transformer 源码

来自B站视频&#xff0c;API查阅&#xff0c;TORCH.NN seq2seq 可以是 CNN&#xff0c;RNN&#xff0c;transformer nn.Transformer 关键源码&#xff1a; encoder_layer TransformerEncoderLayer(d_model, nhead, dim_feedforward, dropout,activation, layer_norm_eps, ba…

5.vue3医疗在线问诊项目 - _极速问诊-前置准备 ==> 需求分析、枚举类型、pinia仓库的初始化

5.vue3医疗在线问诊项目 - _极速问诊-前置准备 > 需求分析、枚举类型、pinia仓库的初始化 极速问诊-需求分析{#consult-product} 极速问诊阶段流程分析 线下看病流程&#xff1a; 选择医院&#xff08;三甲、普通&#xff09;》挂号》选择科室 》选择医生&#xff08;专家…

牛客网专项练习——C语言错题集(5)

文章目录 指针的值指针与数组、函数的组合空结构体* 和 的优先级 指针的值 指针的值是一个地址&#xff0c;题目中的字符串 “girl” 应该是 *p 的值&#xff0c;即指针 p 所指地址存储的数据的值。 指针与数组、函数的组合 int *p[n] 等价于 int (*)p[n]&#xff0c;是一个…

xinput1_3.dll丢失怎么办?xinput1_3.dll丢失的修复方法

xinput1_3.dll是电脑文件中的dll文件&#xff08;动态链接库文件&#xff09;。如果计算机中丢失了某个dll文件&#xff0c;可能会导致某些软件和游戏等程序无法正常启动运行&#xff0c;并且导致电脑系统弹窗报错。 在我们打开软件或者游戏的时候&#xff0c;电脑提示xinput1_…

STM32 Proteus仿真自动刹车系统超声波测距电机控制-0042

STM32 Proteus仿真自动刹车系统超声波测距电机控制-0042 Proteus仿真小实验&#xff1a; STM32 Proteus仿真自动刹车系统超声波测距电机控制-0042 功能&#xff1a; 硬件组成&#xff1a;STM32F103C6单片机 LCD1602显示器HCSR04超声波传感器按键(加 减)电机蜂鸣器 1.单片机…