RedisSon高并发分布式锁实战

news2024/12/27 13:38:24

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/650523.html

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

相关文章

01分数规划 易懂+例题讲解 (c++)

01分数规划 &#xff1a;01即取还是不取&#xff0c;分数即所求型式为&#xff0c;规划就是选取最好的方案。 一般情况题目给出n个物品&#xff0c;再给出每个物品的价值以及物重&#xff0c;选取k个物品&#xff0c;问你在所有可能选取的方案中&#xff0c;最大的单位价值为多…

通过零代码ETLCloud实现马帮ERP数据自动化同步

马帮ERP介绍 马帮ERP是一款云端跨境电商管理软件。与传统的ERP系统不同&#xff0c;马帮ERP专注于跨境电商领域&#xff0c;为电商企业提供一站式管理解决方案&#xff0c;包括财务管理、采购管理、进销存管理、订单管理等功能模块。该平台针对跨境电商行业特点&#xff0c;提…

MM32F3273G8P火龙果开发板MindSDK开发教程20 - freertos + letter shell 的移植

MM32F3273G8P火龙果开发板MindSDK开发教程20 - freertos letter shell 的移植 1、freertos下载 官网传送门 2、freertos移植 1、在工程目录device下新建freertos目录&#xff0c;将下载的源码source目录下的七个.c文件copy到新建的freertos目录。 2、将source/protable/G…

Word 2021入门指南:详细解读常用功能

软件安装&#xff1a;办公神器office2021安装教程&#xff0c;让你快速上手_正经人_____的博客-CSDN博客 一、 新建文档 打开Word 2021后&#xff0c;可以看到左上角的“文件”选项&#xff0c;点击它&#xff0c;在弹出的菜单中选择“新建”选项。然后可以选择空白文档或者使…

vue3+ts+vite+element plus中使用luckysheet(预览效果)

前言&#xff1a; 这两天一个项目&#xff0c;需要在页面中以excel的形式展示大量数据&#xff0c;喜欢偷懒的我果断扒拉了一堆适用于vue3的插件&#xff0c;下面简单说说我使用的luckysheet 使用&#xff1a; 一、准备一个vue3tsviteelement plus的项目 此处省略n个字。。。…

如何用 WampServer+快解析 搭建php文件管理器

基于网络&#xff0c;资源是大家最最基本的需求&#xff0c;许多网络爱好者不求利益&#xff0c;把自己收集的一些通过一些平台共享给大家&#xff0c;这就是资源共享。 资源共享程度越高&#xff0c;代表信息发展水平越高。现实工作中&#xff0c;由于用户提供的数据可能来自…

Linux基础IO - 软硬链接 | 动静态库

之前的文章中我们讲述了软硬链接中有关软连接的知识&#xff0c;本文中将继续讲述硬链接部分的知识&#xff0c;并且讲述一下动静态库的相关内容。 硬链接 硬链接本质上就是在当前目录建立一个新的文件名与指定文件inode的关系。 每当我们在当前目录下建立一个硬链接就会让文…

C++11多线程之条件变量

文章目录 一、关于多线程的同步二、初始条件变量三、关于条件变量的例题四、生产者消费者模型 一、关于多线程的同步 //函数被调用&#xff0c;分配相应的栈帧&#xff0c;进行现场保护void func(char c) {char filename[20] {};sprintf(filename, "test%c.txt",c)…

Fiddler Response私人订制

在客户端接口的测试中&#xff0c;我们经常会需要模拟各种返回状态或者特定的返回值&#xff0c;常见的是用Fiddler模拟各种请求返回值场景&#xff0c;如重定向AutoResponder、请求拦截修改再下发等等。小编在近期的测试中遇到的一些特殊的请求返回模拟的测试场景&#xff0c;…

《变形金刚7》票房大跳水!特效敷衍?剧情单薄?汽车人的未来在哪里?

《变形金刚&#xff1a;超能勇士崛起》 6.11&#xff08;上映第3天&#xff09; 单日票房8200万 6.12&#xff08;上映第4天&#xff09; 单日票房2173万 6.13&#xff08;上映第5天&#xff09; 单日票房1700万 说实在的&#xff0c;真没想到《变形金刚7》日票房会如此大幅…

高压放大器在铁电材料中的应用研究

铁电材料是一种具有特殊磁电性能的材料&#xff0c;包括压电陶瓷、磷酸铁钠陶瓷、氧化锌压电陶瓷等。这些材料在电力、电子、机械等领域有广泛的应用&#xff0c;如超声波发生器、声纳、压力传感等。其中&#xff0c;高压放大器在铁电材料中有着重要的应用。 一、高压放大器的基…

完美搭建一个vue3+ts项目(一篇文章搞定你的所有疑惑)

目录 一、创建vite项目 二、启动vite项目 三、处理一些配置问题 四、增加工程化插件 1、安装sass 2、安装vue-router 3、安装pinia 4、安装element-plus 5、安装axios 6、设置路径别名&#xff0c;将相对路径改为绝对路径 一、创建vite项目 1、在一个文件夹下通…

Vision Pro:为什么空间音频是AR的绝杀武器?

Apple Vision Pro&#xff0c;不仅仅是苹果全新的重磅品类&#xff0c;而且在它身上也融合了苹果过去几乎所有新技术&#xff0c;比如空间音频就是其中一个例子。 苹果表示&#xff0c;Vision Pro中空间音频可以很好的应用在&#xff1a;影视节目、游戏内容、3D空间照片、3D空…

金融风控项目实战-银行信用卡流失预测模型_基于ANN神经网络_金融培训_论文科研_毕业设计

业务背景 根据央行公布的数据显示&#xff0c;全国性银行信用卡和借贷合一卡的发卡量增速从2017年同比增速26.35%的高点逐年下降&#xff0c;截至2020年同比增速降至4.26%。银行信用卡发卡增速明显放缓的背景下&#xff0c;预防老客户流失的问题变得愈发重要。 假设一家消费信…

pytest+allure

知识点1&#xff1a; 1、测试结果信息阅读 passed表示通过&#xff0c;有个简写. failed表示失败&#xff0c;有个简写F 2、命令行参数 -h&#xff1a;帮助 -version&#xff1a;版本信息 3、测试用例命名规则&#xff1a; 测试函数必须以test开头 测试类必须以Test开头…

我为开放原子全球开源峰会助力:共建开源之梦

我为开放原子全球开源峰会助力&#xff1a;共建开源之梦 6月11日&#xff0c;以“开源赋能&#xff0c;普惠未来”为主题的2023开放原子全球开源峰会开幕式暨高峰论坛在北京成功举办。 开源的力量与魅力 开源是当今软件行业中不可忽视的力量&#xff0c;它为技术的快速发展和…

入职滴滴和字节的2 年里,我感觉忒真实了……

引言 先简单交代一下背景吧&#xff0c;某不知名985的本硕&#xff0c;17年毕业加入滴滴&#xff0c;之后跳槽到了头条&#xff0c;一直从事软件测试相关的工作。之前没有实习经历&#xff0c;算是两年半的工作经验吧。 这两年半之间完成了一次晋升&#xff0c;换了一家公司&…

Ubuntu如何安装vmtools?

虚拟机-安装VMware Tools 然后在Ubuntu中找到设备中的VMware Tools&#xff0c;将这个文件来复制到桌面上去。 选择提取到此处 可以看到桌面上多了一个VMware Tools的文件夹 使用cd命令进入桌面上的这个VMware tools的文件夹 使用sudo ./安装命令 对vmware-tools-distrib文件夹…

政企市场,「观望」AI大模型

数据安全等“刚需”下&#xff0c;私有化成为政企市场的准入门槛&#xff0c;然而私有化下&#xff0c;行业模型局限性尚未可知&#xff0c;加之信创化等因素&#xff0c;厂商仍需取长补短&#xff0c;为政企客户提供全方位的解决方案。 作者|斗斗 编辑|皮爷 出品|产业家 政…

采用UWB定位技术开发的室内定位系统源码

UWB精准定位系统源码 UWB是什么&#xff1f; UWB(Ultra Wideband)超宽带技术是一种全新的、与传统通信技术有极大差异的通信新技术。它不需要使用传统通信体制中的载波&#xff0c;而是通过发送和接收具有纳秒或纳秒级以下的极窄脉冲来传输数据&#xff0c;实现精准定位。 技术…