执行Lua脚本后一直查询不到Redis中的数据(附带问题详细排查过程,一波三折)
文章目录
- 执行Lua脚本后一直查询不到Redis中的数据(附带详细问题排查过程,一波三折)
- 问题背景
- 问题1:Lua脚本无法切库
- 问题2:RedisTemlate切库报错
- 问题3:序列化导致数据不一致
- 问题4:Lua脚本中单引号无法拼接字符串
- 总结
执行Lua脚本后一直查询不到Redis中的数据(附带详细问题排查过程,一波三折)
这个问题坑惨我了,估计耗费了我两个小时😫,中间走了不少弯路,好在我灵光一闪+GPT给我的灵感否则就栽在这上面了
问题背景
-
问题背景
在使用 Redis 实现接口调用次数扣减操作时,发现响应结果一直返回的是 -1,也就是查询不到数据

问题1:Lua脚本无法切库
-
问题排查过程1:
经过一段排查,加上GPT的提示信息,我快速定位到了是可能是参数的问题,但是通过 Debug 断点调试,我可以百分百肯定这个参数肯定没有问题;然后我又到 redis-cli 执行原生的 Redis 指令,发现也查不到数据!(○´・д・)ノ,懵逼了,捣鼓了半天突然想起来有没有可能是这个数据库中压根没有数据,于是我使用
keys *指令查看数据库中是否有数据,结果发现有数据,但是压根就没有我预热的数据!!!这是什么原因呢?一看 REP 就恍然大悟了,我操作的数据库1,但是由于我配置文件中使用的是数据库 0,导致我预热的数据 都在 数据库1中,而 Lua脚本默认操作的数据库0,那么问题就简单了,直接使用redis('select',1)这条指令切换一下数据库不久OK了吗(我真聪明🤭)然后我先在redis-cli上实验,发现的确是这个的原因,嘿嘿问题成功解决了?(你不会以为这就完了吧🤣) -
问题原因:Lua脚本默认使用的数据库0,我的数据在数据库1中
-
问题解决
在执行Lua脚本前,先切换一下数据库
redis('SELECT', 1)==结果发现在 Lua 脚本中添加了
redis('SELECT', 1)之后压根就没有用!==┭┮﹏┭┮只能继续排查问题,
-
问题排查过程2
在lua脚本中查询数据前,加上
redis('select',1),结果发现嘿嘿还是返回-1!这下彻底懵逼了,结果经过询问ChartGPT,发现:Lua脚本不能切换数据库,还是太天真了
既然Lua脚本无法完成切库,那么就需要使用 RedisTemplate 进行切库
-
问题解决:
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();connection.select(1);你不会以为这么快就解决了这个问题吧!
问题2:RedisTemlate切库报错
-
问题排查过程2:
redisTemplate切库引发的新问题:在添加了上面代码后,结果又报错Selecting a new database not supported due to shared connection. Use separate ConnectionFactorys to work with multiple databases.在此询问GPT,发现是由于 Redis 的共享连接限制,无法直接在同一个连接上切换到不同的数据库。如果你需要在执行 Lua 脚本之前切换 Redis 数据库,可以尝试使用不同的连接工厂来处理多个数据库
-
问题解决
-
解决方案一:直接关闭RedisTemplate的共享连接(不推荐)
详情请参考这篇文章:Springboot整合redis切库问题
在使用 Spring Data Redis 时,默认情况下是共享连接的,因为这可以提高性能和效率。然而,如果你确实需要关闭共享连接,并为每个数据库创建一个独立的连接,也是可以的。但是需要注意一些潜在的问题:
- 性能影响:共享连接可以减少连接的开销,而独立连接则需要更多的资源来维护和管理。因此,关闭共享连接可能会对系统的整体性能产生一定的影响。
- 连接池限制:Redis 连接池有着最大连接数的限制,每个连接都占用一部分连接资源。如果为每个数据库都创建独立的连接,那么连接池中可用的连接数量将被均分,这可能导致每个数据库可用连接数的减少。
- 连接管理复杂性:对于每个独立的连接,你需要额外管理和维护连接的生命周期,包括创建、销毁、异常处理等。这可能增加代码的复杂性和维护成本。
总之,关闭共享连接并为每个数据库创建独立的连接可能会带来性能和连接管理方面的一些负面影响。因此,在进行决策时,请权衡利弊,并根据具体需求和系统规模做出选择。
-
解决方案二:新建一个RedisTemplate
为了提高代码的复用性,我就打算利用 单例模式+原型模式 对IOC容器中的RedisTemplate进行一个拷贝,然后将这个新的RedisTemplate来切库,相对于方案一要更加方便
下面是代码:
package com.ghp.admin.redis;import com.ghp.common.utils.BeanConvertorUtils; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisTemplate;/*** @author ghp* @title* @description*/ public class RedisUtils {private RedisTemplate redisTemplate;private static RedisUtils instance;static {instance = new RedisUtils();}public RedisUtils getInstance(RedisTemplate redisTemplate){this.redisTemplate = redisTemplate;return instance;}/*** 创建一个新的RedisTemplate,并且切换数据库* @param databaseIndex* @return*/public RedisTemplate<String, String> createRedisTemplate(int databaseIndex) {// 进行拷贝(这里是是使用自己封装的工具类,你可以选择使用Spring框架自带的拷贝工具类)RedisTemplate newRedisTemplate = BeanConvertorUtils.copyBean(redisTemplate,RedisTemplate.class);RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();connection.select(databaseIndex);return newRedisTemplate;} }
看上去好像已经成功解决了,但是还没完,经过测试发现执行 Lua 脚本后仍然无法从Redis中查询到数据库w(゚Д゚)w
这些我又陷入了懵逼中
-
问题3:序列化导致数据不一致
-
问题排查过程3:
我开始陷入怀疑当中,因为我在 redis-cli 中经过切库后,发现执行指令是可以查询到数据的,难道是切库失败了?经过检验发现切库是成功的,因为我在 Lua 脚本中执行硬编码
local leftNum = redis.call('HGET', 'cache:interface:pathMethod:', '/api/name/user-POST')是的的确确查询到了数据的!我开始怀疑是不是我参数传递错了,但是经过
return leftNum发现是有数据的,而且数据是真确的,这是什么原因呢?
虽然返回结果是一样的,可能不可能Lua脚本中的值不一样呢?我继续抱着试一试的态度,执行下面的代码
local key1 = KEYS[1] if key1 == '/api/name/user' thenreturn 1 end return 2结果意料之外的居然返回了 2,也就是说我 传入了 ’/api/name/user‘,然后从 Lua 中 返回的是 ’/api/name/user‘,但是 Lua 中两个值竟然不相等!
我开始怀疑难道是编码的问题吗?因为之前在看微信公众号上的一篇文章,有个大佬就是因为编码(全角和半角)的问题导致查询不到数据,于是我抱着试一试的心态,测试了一下编码,有看了一下编译器使用的编码格式,发现是全局
UTF-8所以可以排除编码问题了,那会是什么原因呢,百思不得其解,突然灵光一闪会不会是序列化的问题,我全局为RedisTemplate配置了序列化转换器,但是我还是不敢相信,因为我在Lua脚本中返回的
retrun ARGV[1]和我查询使用的 key也就是/api/name/user是一摸一样的
发现仍然照样没有查询到数据,在 Lua 脚本中返回 pathJson,发现是
\"/api/name/user\",这一下又给了灵感,会不会是传入的JSON多了有个\",于是我做出如下实验local key1 = KEYS[1] if key1 == '\"/api/name/user\"' thenreturn 1 end return 2惊奇的发现这回终于返回 1,也就是说传入的 path 是 竟然是
\"/api/name/user\"(结果很震惊,因为之前我在Lua脚本中返回path时,结果就是/api/name/user,返回的结果根本没有"),我猜测应该是RedisTemplate在处理Lua的返回结果时会多做一层序列化,登录参数的传递过程中需要经过一层序列化!至此问题终于解决了(你不会以为这就成功解决了吧) -
解决方案
在接收参数后进行预处理,也就是去掉传入参数中多余的
”local key1 = string.gsub(KEYS[1], "\"", "") -- cache:interface:pathMethod: local key2 = string.gsub(KEYS[2], "\"", "") -- cache:interface:leftNum: -- 获取hashKey local path = string.gsub(ARGV[1], "\"", "") local method = string.gsub(ARGV[2], "\"", "") local userId = string.gsub(ARGV[3], "\"", "")
问题4:Lua脚本中单引号无法拼接字符串
-
问题排查4
后面我改造了代码,发现仍然不成功!!!😫 ┭┮﹏┭┮
经过测试,我发现
local key1 = path .. '-' .. method return key1发现只会返回
-前面的字符串 也就是 path,如果调换path和method的未知,那么只会返回method,我又陷入了沉思。后面我突然想起来了,是Lua中双引号和单引号的区别,使用
..运算符可以用来拼接字符串。这个运算符可以连接两个字符串(或者将其他数据类型转换为字符串后连接)。然而,..运算符只能用于连接双引号字符串。单引号字符串在 Lua 中表示字符,而不是字符串。如果你试图使用..运算符连接两个单引号字符串,会得到一个语法错误。 -
解决方法
local key1 = tostring(path) .. "-" .. tostring(method) return key1成功解决了。如果你对Lua感兴趣的可以参考这篇文章:Lua快速入门笔记_知识汲取者的博客-CSDN博客
总结
终于解决了这个问题,感觉好舒爽😄
总的来说,我遇到的问题是 Java 代码使用 RedisTemplate.execute 执行 Lua脚本的时候,由于我配置了全局序列化,所以导致 传入Lua脚本中的参数 会先被序列化,而Lua脚本的参数被传出来时同样会被反序列化,这个序列化和反序列化对我而言是透明的,所以导致我没有想到居然序列化了一遍,从而导致我走了好多弯路

这里提一嘴:前面那个Lua脚本默认使用数据库 0不完全正确,如果我们在配置文件中配置了使用哪一个数据库,那么在Lua脚本中就会使用哪一个数据库,所以我们可以不用切库,也能保障 Lua脚本能够操作数据库1,所以前面的 问题1 和 问题2 不需要,根本原因在于序列化和反序列化问题
参考资料
- ChartGPT3.5
- Springboot整合redis切库问题
相关文章:
执行Lua脚本后一直查询不到Redis中的数据(附带问题详细排查过程,一波三折)
文章目录 执行Lua脚本后一直查询不到Redis中的数据(附带详细问题排查过程,一波三折)问题背景问题1:Lua脚本无法切库问题2:RedisTemlate切库报错问题3:序列化导致数据不一致问题4:Lua脚本中单引号…...
[高光谱]PyTorch使用CNN对高光谱图像进行分类
项目原地址: Hyperspectral-Classificationhttps://github.com/eecn/Hyperspectral-ClassificationDataLoader讲解: [高光谱]使用PyTorch的dataloader加载高光谱数据https://blog.csdn.net/weixin_37878740/article/details/130929358 一、模型加载 在…...
jmeter获取mysql数据
JDBC Connection Configuration Database URL: jdbc:mysql:// 数据库地址 /库名 JDBC Driver class:com.mysql.jdbc.Driver Username:账号 Password:密码 JDBC Request 字段含义 字段含义 Variable Name Bound to Pool 数据库连接池配置…...
Dedecms V110最新版RCE---Tricks
前言 刚发现Dedecms更新了发布版本,顺便测试一下之前的day有没有修复,突然想到了新的tricks去实现RCE。 文章发布的时候估计比较晚了,一直没时间写了。 利用 /uploads/dede/article_string_mix.php /uploads/dede/article_template_rand.…...
CTFshow 限时活动 红包挑战7、红包挑战8
CTFshow红包挑战7 写不出来一点,还是等了官方wp之后才复现。 直接给了源码 <?php highlight_file(__FILE__); error_reporting(2);extract($_GET); ini_set($name,$value);system("ls ".filter($_GET[1])."" );function filter($cmd){$cmd…...
Redis使用Lua脚本和Redisson来保证库存扣减中的原子性和一致性
文章目录 前言1.使用SpringBoot Redis 原生实现方式2.使用redisson方式实现3. 使用RedisLua脚本实现3.1 lua脚本代码逻辑 3.2 与SpringBoot集成 4. Lua脚本方式和Redisson的方式对比5. 源码地址6. Redis从入门到精通系列文章7. 参考文档 前言 背景:最近有社群技术交…...
【从零开始学Kaggle竞赛】泰坦尼克之灾
目录 0.准备1.问题分析挑战流程数据集介绍结果提交 2.代码实现2.1 加载数据2.1.1 加载训练数据2.1.2 加载测试数据 2.2 数据分析2.3 模型建立与预测 3.结果提交 0.准备 注册kaggle账号后,进入titanic竞赛界面 https://www.kaggle.com/competitions/titanic 进入后界…...
输出无重复的3位数和计算无人机飞行坐标
编程题总结 题目一:输出无重复的3位数 题目描述 从{1,2,3,4,5,6,7,8,9}中随机挑选不重复的5个数字作为输入数组‘selectedDigits’,能组成多少个互不相同且无重复数字的3位数?请编写程》序,从小到大顺序,以数组形式输出这些3位…...
muduo 29 异步日志
目录 Muduo双缓冲异步日志模型: 异步日志实现: 为什么要实现非阻塞的日志...
Qt 对象序列化/反序列化
阅读本文大概需要 3 分钟 背景 日常开发过程中,避免不了对象序列化和反序列化,如果你使用 Qt 进行开发,那么有一种方法实现起来非常简单和容易。 实现 我们知道 Qt 的元对象系统非常强大,基于此属性我们可以实现对象的序列化和…...
从零学算法(非官方题库)
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) B是A的子结构, 即 A中有出现和B相同的结构和节点值。 例如: 给定的树 A:3/ \4 5/ \1 2给定的树 B:4 / 1返回 true,因为 B 与 A 的一个子树拥有相…...
Java # JVM内存管理
一、运行时数据区域 程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区、运行时常量池、直接内存 二、HotSpot虚拟机对象 对象创建: 引用检查类加载检查分配内存空间:指针碰撞、空闲列表分配空间初始化对象信息设置(对象头内࿰…...
大疆第二批笔试复盘
大疆笔试复盘(8-14) 笔试时候的状态和下来复盘的感觉完全不一样,笔试时脑子是懵的。 (1)输出无重复三位数 题目描述 从 { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } \left \{ 1,2,3,4,5,6,7,8,9 \right \...
【Linux】磁盘或内存 占用比较高要怎么排
当 Linux 磁盘空间满了时 请注意,在进行任何删除操作之前,请确保你知道哪些文件可以安全删除,并备份重要文件,以免意外丢失数据。当 Linux 磁盘空间满了时,可以按照以下步骤进行排查: 检查磁盘使用情况&…...
解决xss转义导致转码的问题
一、xss简介 人们经常将跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为XSS。跨站脚本攻击ÿ…...
numba 入门示例
一维向量求和: C A B 在有nv 近几年gpu的ubuntu 机器上, 环境预备: conda create -name numba_cuda_python3.10 python3.10 conda activate numba_cuda_python3.10conda install numba conda install cudatoolkit conda install -c nvi…...
BUUCTF 还原大师 1
题目描述: 我们得到了一串神秘字符串:TASC?O3RJMV?WDJKX?ZM,问号部分是未知大写字母,为了确定这个神秘字符串,我们通过了其他途径获得了这个字串的32位MD5码。但是我们获得它的32位MD5码也是残缺不全,E903???4D…...
自定义hook之首页数据请求动作封装 hooks
本例子实现了自定义hook之首页数据请求动作封装 hooks,具体代码如下 export type OrganData {dis: Array<{ disease: string; id: number }>;is_delete: number;name: string;organ_id: number;parent_id: number;sort: number; }; export type SwiperData …...
2023上半年京东手机行业品牌销售排行榜(京东数据平台)
后疫情时代,不少行业都迎来消费复苏,我国智能手机市场在今年上半年也实现温和的复苏,手机市场的出货量回暖。 根据鲸参谋平台的数据显示,2023年上半年,京东平台上手机的销量为2830万,环比增长约4%…...
lodash之cloneDeep()源码阅读笔记
lodash之cloneDeep()源码阅读笔记 基本上都在写业务代码,没有机会写库,还是想了解一下lodash的库源码是怎么样的,平时用的最多的就是cloneDeep()方法了,终于有空详细看看其中的源码。 本文基于lodash5.0.0版本的源码进行阅读。 /…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...
LangChain【6】之输出解析器:结构化LLM响应的关键工具
文章目录 一 LangChain输出解析器概述1.1 什么是输出解析器?1.2 主要功能与工作原理1.3 常用解析器类型 二 主要输出解析器类型2.1 Pydantic/Json输出解析器2.2 结构化输出解析器2.3 列表解析器2.4 日期解析器2.5 Json输出解析器2.6 xml输出解析器 三 高级使用技巧3…...
