java Redission执行 Redis lua脚本实践

lua redisson执行lua脚本 阅读详情

一、业务场景

一个行业网盘产品,用户分为主账号和子账户,多个子账户都可以操作公司主账号下的网盘,在多个子账户操作文件结构的时候,存在并发问题,如果简单的串行处理又明显影响用户体验

举例:上传下载的时候肯定需要多线程上传文件,你串行化了怎么办。

两个人同时上传文件 你也不能串行化。

二、逻辑锁的设计

举例:

同一个文件夹 支持并发写入

同一个文件夹 支持并发删除

三、技术选型

3.1 redis

由于本身的并发并不高,锁的范围是主账户下,多个子账户操作,理论上可以使用java逻辑、数据库来实现并发逻辑控制,

系统本身是分布式的,java 中的逻辑不合适,总不能单独部署一个服务提供锁的逻辑,数据库到是可以支持,利用表字段逻辑 去实现锁的控制
但是数据库 Mysql 是MVVC 版本控制,在你访问数据库的时候 你怎么保证没有其他子账户向数据库中写入数据。

redis 本身支持分布式锁,性能也合适,redis 中的key 有过期时间, 悲观的认为假如出现了死锁 用户等待一段时锁就释放了。

事实上lua 封装的逻辑不可能出现死锁的

3.2 redision

锁的存储选择了redis 那么java 访问redis 选择哪个驱动呢,个人推荐redisson

redisson 本身利用lua 脚本实现了很多功能,适合java在redis 中执行lua 脚本。

3.3 lua 脚本

选择lua 脚本的原因就是为了 当一个子账户要进行一个操作的时候 需要分两步

1.检查这个操作有没有被其它子账户 锁住

举例:B 账户在删除 文件架 00100

A 账户需要向文件夹00100 中上传一个文件,需要先检查文件00100 的状态,在向文件00100 上加一个上传锁

这个过程 需要保证原子性

如果利用java实现 那么就破坏了操作的原子性。

3.4 lua 脚本示例

local lockKeys = {'001_delete','15_delete','001_17_delete'};

local supUserKeys = {'001_1_add_9','001_5_add_9','001_7_add_9'};

local checkKeys= {'001_1_delete','001_5_delete'};


local function join_and_filter(checkKeys, lockKeys)
    local f=false;
    for i = 1, #checkKeys
    do for j=1 ,#lockKeys
    do local tstring=string.match(lockKeys[j], checkKeys[i]);
        if(not tstring)
        then
        else f = true
            break
            return f
        end;
    end;
    end;
    if(f)
    then
        return f
    end;

end

----keys[1] 用户持有的锁
local keys = redis.call('keys', KEYS[1]); local keyValuePairs = {}; for i = 1, #keys do keyValuePairs[i] = keys[i]  end;

---- keys[2]  需要检查的锁
local checkKeys=redis.call("sismember",KEYS[2]);
---- keys[3]  目标锁
local supUserKeys=redis.call("sismember",KEYS[3]);

if(join_and_filter(checkKeys, keyValuePairs))

then
    return false
else
    for k = 1, #supUserKeys
    do  redis.call("set",supUserKeys[k],'1')
        redis.call('expire', supUserKeys[k], 30)
    end
    return true
end

这个是lua 脚本demo

不需要外部入参

通过循环检查 用户的某个操作是否被锁 如果没有加锁lua 脚本加锁

返回 true 或者false

四、 java通过redisson 执行lua 遇到的问题

正式开发之前先写了一个demo ,整个demo 大概花费了2周时间,
首先lua 现学现卖,学下来发现lua 真小众,还好自己会python ,lua 学起来不难, 但是lua 应用范围很窄,写业务不太有需要,大多数都是游戏脚本,待遇一般,稍微有真实需求的是 C 语言开发的网关顺带使用lua 写逻辑,或者nginx 、redis 这种 使用lua.

4.1 redisson 中入参和出参的类型

这里说明一下redisson 官方的文档很简单,需要执行复杂的入参和返回值的时候就有点蒙了,总不至于肯源码吧,搜索了github 上

有个国外的同行提交了redisson的各种测试用例,感觉没有半个月搞不清楚,感谢开源。同时国内的资料大部分抄一下官网的示例,小部分没有规划,只是集中在某些点上。

这里列举部分操作

有需要的可以联系我 提供GitHub地址

获取List 对象
获取boolean 对象

    @Test
    public void testEval() {
        RScript script = redisson.getScript();
        List<Object> res = script.eval("return {1,2,3.3333,'foo',nil,'bar'}", RScript.ReturnType.MULTI, Collections.emptyList());
        MatcherAssert.assertThat(res, Matchers.<Object>contains(1L, 2L, 3L, "foo"));
    }

    @Test
    public void testEvalAsync() {
        RScript script = redisson.getScript();
        Future<List<Object>> res = script.evalAsync("return {1,2,3.3333,'foo',nil,'bar'}", RScript.ReturnType.MULTI, Collections.emptyList());
        MatcherAssert.assertThat(res.awaitUninterruptibly().getNow(), Matchers.<Object>contains(1L, 2L, 3L, "foo"));
    }

    @Test
    public void testScriptExists() {
        RScript s = redisson.getScript();
        String r = s.scriptLoad("return redis.call('get', 'foo')");
        Assert.assertEquals("282297a0228f48cd3fc6a55de6316f31422f5d17", r);
        
        List<Boolean> r1 = s.scriptExists(r);
        Assert.assertEquals(1, r1.size());
        Assert.assertTrue(r1.get(0));

        s.scriptFlush();
        
        List<Boolean> r2 = s.scriptExists(r);
        Assert.assertEquals(1, r2.size());
        Assert.assertFalse(r2.get(0));
    }

4.2 redisson 编码问题

使用redisson 主要编码,不同的编码在redis 中存储的数据格式不一样,我就遇到 key 无缘无故加了双引号

推荐使用String 编码

 RSet<String> set = redissonClient.getSet(userID + "ok" + fileId, StringCodec.INSTANCE);
 
   RScript script = redissonClient.getScript(StringCodec.INSTANCE);

4.3 lua 脚本redis.call(“set”,supUserKeys[k],‘1’) 权限问题

本地测试通过之后 发到测试环境发现 竟然不允许lua 脚本写入数据,

问了运维 测试环境的redis 版本是3.0 的

查了资料发现redis版本需要3.2 以上, 很多资料写了最低要求4.0

这里又卡了半天。

重点

lua 脚本报错:

Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.

需要:

-- 开启单命令复制模式
redis.replicate_commands();

redis版本低于3.2 的时候会有报错,当你执行redis.replicate_commands(); 的时候又会包命令不存在,导致死循环。

这里就是升级你的redis版本

在执行Lua时Redis默认不允许动态的不确定性的变量存在. 如果存在, 则需要开启命令复制模式, 即只复制Lua脚本里包含的写命令, 所有的写命令会被包装在MULTI … EXEC里.
Redis官方, 把默认的复制整个脚本内容的模式定义为whole scripts replication, 把只复制脚本里写命令的模式定义为 script effects replication.
在命令复制模式下, 还可以选择是否对某个命令进行目标复制, 即是否需要复制到‘从服务器’和‘AOF文件’ , 如下:

redis.set_repl(redis.REPL_ALL) -- Replicate to AOF and slaves.
redis.set_repl(redis.REPL_AOF) -- Replicate only to AOF.
redis.set_repl(redis.REPL_SLAVE) -- Replicate only to slaves.
redis.set_repl(redis.REPL_NONE) -- Don't replicate at all.

举个例子:

redis.replicate_commands() -- 开启命令复制模式
redis.call('set','A','1')
redis.set_repl(redis.REPL_NONE) -- 不进行复制
redis.call('set','B','2')
redis.set_repl(redis.REPL_ALL) -- 复制到所有,即从服务器和AOF文件
redis.call('set','C','3')

参考资料:https://www.fancv.com/article/1645542706

Redisson进阶:Lua脚本与API深度整合实践 Redisson通过RScript接口管理Lua脚本。脚本首次加载时会被编译并生成SHA摘要,后续通过摘要执行,减少网络传输开销。// 示例:加载Lua脚本// 读取脚本内容// 生成SHA摘要原子性保障:复杂操作在Redis服务端原子执行。性能提升:减少网络往返次数,SHA复用降低带宽消耗。业务解耦:将核心逻辑下沉至Lua,Java层专注流程控制。Redisson+Lua的组合为分布式系统提供了灵活、高效的解决方案,是应对高并发场景的利器。 阅读详情

相关推荐

Redisson lua脚本解读

Redisson lua脚本解读

codeFarmer_666的博客 2559

Redis下Lua脚本的复制模式

  假设我们的Redis选择了主从架构, 和AOF持久化方式.      当我们执行一条写命令时, 该条命令会被发送到从服务器, 和追加到AOF文件中.      当我们执行的不是一条命令, 而是Lua脚本时, 默认情况下, 整个Lua脚本的内容会进行复制, 但是存在一些特殊情况, 我们来看一个例子, Lua脚本内容如下:      -- 当前时间      local now_time = re...

li123128的博客 1454

Redisson和Redis+Lua解决超卖问题比较

Redisson 是 Redis 的 Java 客户端,提供分布式锁、原子操作、信号量等高级功能,通过封装 Redis 命令简化分布式场景下的并发控制。用 Lua 脚本处理核心扣减逻辑,用 Redisson 管理分布式锁控制外围流程(如订单创建)。通过编写 Lua 脚本在 Redis 服务端原子化执行库存扣减逻辑,避免客户端并发问题。超卖问题的本质是保证操作的原子性,两种方案均可实现,但需根据业务需求和团队能力权衡选择。Redis + Lua 适合高性能、高定制化场景,通过服务端原子操作避免竞争。

weixin_39304656的博客 821

Redis研究(十九)—命令属性

Redis的不同命令拥有不同的属性,如是否是只读命令,是否是管理员命令等,一个命令可以拥有多个属性。在一些特殊情况下不同属性的命令会有不同的表现,下面来逐一介绍。 1、 REDIS_CMD_WRITE       拥有REDIS_CMD_WRITE属性的命令的表现是会修改Redis数据库的数据。一个只读的从数据库会拒绝执行拥有REDIS_CMD_WRI TE属性的命令,另外在Lua脚本

小地盘的诺克萨斯 5670

黑马点评项目实战:Redission分布式锁与Lua脚本优化异步秒杀

本文以黑马点评项目为例,深入解析了在集群环境下秒杀系统面临的库存超卖与一人多单问题。通过对比单体锁的局限,详细介绍了如何利用Redisson实现高可用的分布式锁,并结合Lua脚本确保库存扣减等核心操作的原子性。文章进一步探讨了基于Redis Stream的异步秒杀架构,将资格校验与订单创建解耦,从而将响应时间降至毫秒级,有效提升系统吞吐量与可靠性。

weixin_29264201的博客 544

阿里云redis集群使用lua脚本

redis集群对lua集群的支持有限,阿里的文档描述也比较简单,没有demo,研究了好久才把单例的lua脚本修改成集群版 单例模式的lua脚本local strs = {}; local result = {}; local resultIndex = 2; -- ARGV[1]是操作集合的指令,这里对应的是正序还是倒序zrange、zrevrange -- KEYS[1]是zset的key --

mushuntaosama的博客 1万+

Redis篇-18--分布式锁2-实战篇(RedisTemplate实现,Redission实现)

上一篇我们重点介绍了Redis实现分布式锁的相关原理以及方式。本篇以Java中两个常用的Redis客户端RedisTemplate和Redission实现一下分布式锁的功能。

weisian的博客 3万+

HFSS螺旋线圈的优化设计与高效仿真实践

本文详细介绍了HFSS螺旋线圈的优化设计与高效仿真实践,涵盖核心参数设置、参数化建模、仿真加速技巧及结果分析。通过HFSS软件实现螺旋线圈的高效仿真,特别适用于无线充电和RFID系统设计,提升设计效率与性能验证准确性。

weixin_30235225的博客 443

Redis分布式锁中Redission底层实现

Redis分布式锁的Redission实现原理与应用 摘要:本文深入分析了Redis分布式锁中Redission客户端的底层实现机制。Redission通过SET NX PX命令获取锁,并引入看门狗机制定期续期,确保锁的可靠性。其核心实现包含Lua脚本保证原子性、可重入锁设计以及完善的异常处理。与简单SETNX相比,Redission提供了更完备的分布式锁功能,包括锁等待、自动续期和释放通知等特性。文章详细介绍了Redission分布式锁的使用方法、技术原理和最佳实践,同时对比了与其他分布式锁方案的差异。R

从不水文,坚持创作,加油 1346

【无人机对抗】基于matlab无人机红蓝对抗仿真【含Matlab源码 4436期】

无人机红蓝对抗仿真 完整代码,包运行;可提供运行操作视频!适合小白!

Matlab3024528700的博客 1378

基于Sentinel-2(哨兵2)遥感卫星的植被覆盖度提取

基于Sentinel-2(哨兵2)遥感卫星的植被覆盖度提取 一、Sentinel-2简单介绍 哨兵-2系列卫星的主要载荷多光谱成像仪拥有13个谱段,从可见光到近红外再到短波红外,空间分辨率从10m到60m,能实现全所未有的陆地海洋监测水平。其幅宽高达290KM,且A、B双星组合,覆盖能力比美国Landsat8卫星强很多。目前哨兵-2A和哨兵-2B已经实现了5天的重访时间,即使在之前仅有单颗卫星时,...

weixin_44143671的博客 2万+

lua脚本+Redission实现分布式锁

基于Redis不论是本地锁还是分布式锁,核心都在于“互斥”。在 Redis 中,SETNX命令是可以帮助我们实现互斥。SETNX即(对应 Java 中的方法),如果 key 不存在的话,才会设置 key 的值。如果 key 已经存在,SETNX啥也不做。释放锁的话,直接通过DEL命令删除对应的 key 即可。为了防止误删到其他的锁,这里我们建议使用 Lua 脚本通过 key 对应的 value(唯一值)来判断。

hello77的blogggg 祝你得偿所愿 1277

噪声整形与过采样实战:从N-bit ADC到DSM的SQNR优化指南

本文深入探讨了噪声整形与过采样技术在DSM(Delta-Sigma调制器)中的应用,重点分析了量化器位数(N)、滤波器阶数(L)和过采样率(OSR)对SQNR优化的影响。通过实战案例和参数协同优化策略,帮助工程师在音频处理和通信系统设计中实现更优的信号质量。

weixin_42561040的博客 339

Redis——Redis实战:5 Redission

对Redission做了介绍、讲解了Redission可重入锁原理、Redission锁重试和WatchDog机制和Redission锁的MutiLock原理。

念头通达 1320

Unity小游戏接入抖音敏感词检测

摘要: 本文介绍了Unity小游戏接入抖音敏感词检测的方法。抖音官方提供了两种API:SensitiveWordCheck用于检测敏感词,ReplaceSensitiveWords用于替换敏感词。此外,若使用键盘输入事件,可通过tt.onKeyboardComplete监听回调,平台会自动屏蔽敏感词(替换为*)。接入官方字库可有效避免审核驳回,适用于小游戏中的用户输入功能(如起名系统)。附官方文档链接及参数说明。

类人_猿的博客 4593

带有HSE组件的S32系列芯片中各子系统如何依次启动?

仔细阐述了上电后各个子系统的启动顺序,并给出了详细的启动步骤流程图。

车载系统攻城狮 1701

在SpringBoot项目中使用Redission实现分布式锁(什么是Redission、为什么要使用分布式锁、分布式锁的应用场景、Redission的读锁和写锁、可重入锁的原理)

Redission是一个在Redis基础上实现的Java客户端,它不仅提供了对Redis各种数据结构的访问接口,还封装了一系列的分布式系统常用的高级功能,比如分布式锁、原子操作、分布式集合、发布订阅消息队列等Redission旨在简化Java应用与Redis服务之间的交互,使得Java开发者能够更加方便地使用Redis提供的各种功能基础与扩展:Redis是基础的数据存储服务,而Redission是在此基础上提供的一系列扩展功能,使Redis在Java应用中的使用更加便捷语言绑定。

web13595609705的博客 3074

[关系图谱] 二.Gephi导入共线矩阵构建作者关系图谱

本文主要讲解Gephi绘制作者间的关系图谱,该软件可以广泛应用于社交网络、知识图谱分析,推荐读者使用。这是非常基础的一篇文章,重点讲解Gephi使用方法,希望对大家有所帮助。

杨秀璋的专栏 1万+

Redission实现分布式锁完美方案 以及 Lua 脚本浅谈

Redission实现分布式锁完美方案 以及 Lua 脚本浅谈 文章目录Redission实现分布式锁完美方案 以及 Lua 脚本浅谈前言常见分布式锁方案对比分布式锁需满足四个条件Redisson分布式锁的实现Redisson 分布式重入锁用法加锁&解锁Lua脚本加锁Lua脚本解锁Lua脚本 前言 在之前的文章中我有提到过redis 分布式锁的实现(Redission)以及lua 脚本相关知识 在某些场景中,多个进程必须以互斥的方式独占共享资源,这时用分布式锁是最直接有效的。 随着技术快速发展

m0_47679010的博客 3193

软考高级信息系统项目管理师系列论文之:百篇软考高级信息系统项目管理师论文范文

软考高级信息系统项目管理师系列论文之:百篇软考高级信息系统项目管理师论文范文

zhengzaifeidelushang的博客 3579

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Redis(四)——Redis 处理接口幂等性(代码实现、两种方式拦截令牌)、Redis 分布式锁(两种引入 lua 脚本、Redission实现分布式锁)、Redis做限流工具(简单限流,限流插件)

Redis(四)——Redis 处理接口幂等性(代码实现、两种方式拦截令牌)、Redis 分布式锁(两种引入 lua 脚本、通过 Redission 实现分布式锁)、Redis做限流工具(简单限流,限流插件——Redis-Cell模块(使用了漏斗算法)、java 代码操作(使用 Lettuce 扩展))

qq_41824825的博客 2619

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

使用Lua脚本解决Redis锁原子性问题

假设有两个线程A和B,线程A先获取到锁,然后执行业务,但是在执行过程中发生了阻塞,导致锁设置的TTL到期了,这时候线程B过来,就能够获取到锁,B在执行的过程中,为了防止线程A执行完释放锁的逻辑把B获取到的锁给释放了,我们需要在释放锁的逻辑中这样写。锁,而并发的线程也都落在了不同的服务上,这个锁就不太靠谱了,因此我们需要的锁是多个服务都能够访问到的一个锁,并且这个锁对象对于多个服务是唯一的。但是在高并发的场景下,可能同时有多个线程都查询到这里,还没来得及执行抢券的逻辑,导致多个线程都查到的结果是可以抢。

qq_73561467的博客 439

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Redis(十) - Redission原理与实践

Redis(十) - Redission原理与实践

及时当勉励,岁月不待人 5291

【Redis】分布式锁的应用以及Redission看门狗机制和MultiLock的源码深入解析

利用 「set nx ex」 获取锁,并设置过期时间,保存线程标识释放锁时先判断线程标识是否与自己一致,一致则删除锁特性:利用 「set nx」 满足互斥性利用 「set ex」设置过期时间,保证故障时锁依然能释放,避免死锁,提高安全性利用 Redis 集群保证高可用和高并发特性。

小新的博客 3069
上一篇: Java8 优雅的判断对象是否为空
下一篇: Redis lua脚本 采坑实践
hamish-wu
博客等级 码龄15年 2814粉丝 162原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值