Redis实战-利用Lua解决批量插入防重方案

需求场景

一个最简单的插入需求,但是因为考虑写入性能,采用批量插入Mysql的方式,但是这引申了一个并发问题,假如网络抖动等其它原因造成了接口重复请求,批量插入情况下如何对一条条数据做好防重处理。

防重 OR 幂等

在说解决方案之前先偏题一下,因为我在起标题的时候在想防重和幂等的区别,标题用词也得精准。在查阅网上资料后,了解到 防重和幂等最主要的作用都是为了避免因为网络等原因产生重复或不符合预期的错误数据,然而两者最主要的区别:

  • 防重设计主要为了避免产生重复数据,对接口返回没有太多要求。
  • 幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。

这么说的话,幂等会让调用者无感知后端对防重逻辑的处理,我们这里因为会返回"此设备重复注册"相关提示语,所以应该属于防重方案

Scripting with Lua

这段开始介绍我的防重方案,这里先介绍一下为什么用Lua,我引用了文档原文,Lua的好处简单来说在于可以做到redis命令的组合执行,但是仅仅是组合执行还不够,意味着会有Java类似的并发问题,然后强大的来了,它不仅支持命令组合执行,而且能做到原子性 ,当执行一段Lua脚本,所有Redis活动在脚本运行时期间都会被阻塞。这就很好的解决了并发问题,所以防重方案使用Lua脚本作为基础是没有问题的。其余的使用Lua能节省带宽等好处就不说了。

Redis guarantees the script's atomic execution. While executing the script, all server activities are blocked during its entire runtime. These semantics mean that all of the script's effects either have yet to happen or had already happened.

Scripting offers several properties that can be valuable in many cases. These include:

  • Providing locality by executing logic where data lives. Data locality reduces overall latency and saves networking resources.
  • Blocking semantics that ensure the script's atomic execution.
  • Enabling the composition of simple capabilities that are either missing from Redis or are too niche to be a part of it.

然后贴上核心代码:

  • deviceKeyList是当前要插入的列表,也是需要做防重方案的Key
  • Lua脚本有如下逻辑:
    • 批量对传入的Key执行保存操作(SETNX命令)。
    • 如果之前已经存在,则存入existing_keys 待返回。
    • 如果之前不存在,则保存后以后再设置过期时间(EXPIRE命令)。
    • 返回已经存在的Key列表。

等执行完后你会拿到existingKeys,里面就保存着之前已经处理过的Key,那么在操作数据库前,只需要再判断下 existingKeys 有没有这个Key,就能做好防重方案。

当然,这段代码最好能try-catch 捕捉下异常,即便Redis挂了,也不能影响主流程的执行。

text-x-java 复制代码
private String duplicateScript = "local existing_keys = {} " +
                "for i = 1, #KEYS do " +
                "    local key = KEYS[i] " +
                "    if redis.call('SETNX', key, '') == 0 then " +
                "        table.insert(existing_keys, key) " +
                "    else " +
                "        redis.call('EXPIRE', key, " + commonProperties.getDuplicateScriptSeconds() + ") " +
                "    end " +
                "end " +
                "return existing_keys";
                
// 执行 Lua 脚本
List<String> existingKeys = redissonClient.getScript(new StringCodec()).eval(
                    RScript.Mode.READ_WRITE,
                    duplicateScript,
                    RScript.ReturnType.MULTI,
                    deviceKeyList
            );

实际在生产环境使用完全没有问题,并且也兼顾了执行性能,Lua脚本更具体的使用语法可以多参照文档。

参考资料

Redis-Scripting with Lua

相关推荐
一个天蝎座 白勺 程序猿3 分钟前
KingbaseES 处理 PL/SQL 运行时错误全解析:从异常捕获到异常处理的实践指南
数据库·sql·oracle·kingbasees
leo_23211 分钟前
表&表结构--SMP(软件制作平台)语言基础知识之三十三
数据库·开发工具·表结构·smp(软件制作平台)·应用系统
C***115021 分钟前
Spring TransactionTemplate 深入解析与高级用法
java·数据库·spring
indexsunny26 分钟前
互联网大厂Java面试实战:Spring Boot与微服务在电商场景的应用解析
java·spring boot·redis·微服务·kafka·gradle·maven
+VX:Fegn089537 分钟前
计算机毕业设计|基于springboot + vue建筑材料管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
2301_8002561139 分钟前
B+树:数据库的基石 R树:空间数据的索引专家 四叉树:空间划分的网格大师
数据结构·数据库·b树·机器学习·postgresql·r-tree
大厂技术总监下海1 小时前
用户行为分析怎么做?ClickHouse + 嵌套数据结构,轻松处理复杂事件
大数据·数据结构·数据库
alonewolf_991 小时前
深入理解MySQL事务与锁机制:从原理到实践
android·数据库·mysql
朝依飞1 小时前
fastapi+SQLModel + SQLAlchemy2.x+mysql
数据库·mysql·fastapi
3***g2052 小时前
redis连接服务
数据库·redis·bootstrap