执行Lua脚本后一直查询不到Redis中的数据(附带问题详细排查过程,一波三折)

文章目录

  • 执行Lua脚本后一直查询不到Redis中的数据(附带详细问题排查过程,一波三折)
    • 问题背景
    • 问题1:Lua脚本无法切库
    • 问题2:RedisTemlate切库报错
    • 问题3:序列化导致数据不一致
    • 问题4:Lua脚本中单引号无法拼接字符串
    • 总结

执行Lua脚本后一直查询不到Redis中的数据(附带详细问题排查过程,一波三折)

这个问题坑惨我了,估计耗费了我两个小时😫,中间走了不少弯路,好在我灵光一闪+GPT给我的灵感否则就栽在这上面了

问题背景

  • 问题背景

    在使用 Redis 实现接口调用次数扣减操作时,发现响应结果一直返回的是 -1,也就是查询不到数据

    image-20230813222603833

问题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脚本不能切换数据库,还是太天真了

    image-20230813223736240

    既然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 时,默认情况下是共享连接的,因为这可以提高性能和效率。然而,如果你确实需要关闭共享连接,并为每个数据库创建一个独立的连接,也是可以的。但是需要注意一些潜在的问题:

      1. 性能影响:共享连接可以减少连接的开销,而独立连接则需要更多的资源来维护和管理。因此,关闭共享连接可能会对系统的整体性能产生一定的影响。
      2. 连接池限制:Redis 连接池有着最大连接数的限制,每个连接都占用一部分连接资源。如果为每个数据库都创建独立的连接,那么连接池中可用的连接数量将被均分,这可能导致每个数据库可用连接数的减少。
      3. 连接管理复杂性:对于每个独立的连接,你需要额外管理和维护连接的生命周期,包括创建、销毁、异常处理等。这可能增加代码的复杂性和维护成本。

      总之,关闭共享连接并为每个数据库创建独立的连接可能会带来性能和连接管理方面的一些负面影响。因此,在进行决策时,请权衡利弊,并根据具体需求和系统规模做出选择。

    • 解决方案二:新建一个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发现是有数据的,而且数据是真确的,这是什么原因呢?

    image-20230814104711299

    虽然返回结果是一样的,可能不可能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是一摸一样的

    image-20230814135211981

    发现仍然照样没有查询到数据,在 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脚本的参数被传出来时同样会被反序列化,这个序列化和反序列化对我而言是透明的,所以导致我没有想到居然序列化了一遍,从而导致我走了好多弯路

image-20230814141308247

这里提一嘴:前面那个Lua脚本默认使用数据库 0不完全正确,如果我们在配置文件中配置了使用哪一个数据库,那么在Lua脚本中就会使用哪一个数据库,所以我们可以不用切库,也能保障 Lua脚本能够操作数据库1,所以前面的 问题1 和 问题2 不需要,根本原因在于序列化和反序列化问题

参考资料

  • ChartGPT3.5
  • Springboot整合redis切库问题

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务08-多级缓存

1.什么是多级缓存 传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图: 存在下面的问题: •请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 •Redis缓存失效时,会对数据库产生冲击 多级缓存就是充分利用请求处理的每个环节,分…

管理类联考——逻辑——真题篇——按知识分类——汇总篇——一、形式逻辑——三段论——第二节 规则

文章目录 第二节 三段论-规则题-三段论-规则-补前提-“特推特&#xff0c;否推否&#xff0c;两特两否无结论”得前提是“有的/所有”→所有概念出现两次→中项要周延。题-三段论-规则-补前提-“特推特&#xff0c;否推否&#xff0c;两特两否无结论”得前提是“有的/所有”→…

在Gazebo中添加悬浮模型后,利用键盘控制其移动方法

前段时间写了文章&#xff0c;通过修改sdf、urdf模型的方法&#xff0c;在Gazebo中添加悬浮模型方法 / Gazebo中模型如何不因重力下落&#xff1a;在Gazebo中添加悬浮模型方法 / Gazebo中模型如何不因重力下落&#xff1a;修改sdf、urdf模型_sagima_sdu的博客-CSDN博客 今天讲…

Mr. Cappuccino的第62杯咖啡——Spring之Bean的生命周期

Spring之Bean的生命周期 Aware接口项目结构项目代码运行结果源代码使用场景 InitializingBean接口项目结构项目代码运行结果源代码 BeanFactoryPostProcessor接口项目结构项目代码运行结果源代码 Bean的生命周期项目结构项目代码运行结果源代码 Aware接口 实现Aware接口是为了…

【【萌新的STM32学习-9】】

萌新的STM32学习-9 我们在使用某个外设&#xff0c;必须线使能该外设时钟 SYSTEM 文件夹里面的代码由正点原子提供&#xff0c;是 STM32F1xx 系列的底层核心驱动函数&#xff0c; 可以用在 STM32F1xx 系列的各个型号上面&#xff0c;方便大家快速构建自己的工程。本章&#xf…

什么是网络地址转换 (NAT)

网络地址转换&#xff08;NAT&#xff09;是更改源和目标 IP 地址和端口的过程&#xff0c;地址转换减少了对 IPv4 公共地址的需求&#xff0c;并隐藏了专用网络地址范围&#xff0c;该过程通常由路由器或防火墙完成。 NAT是如何工作的 NAT 允许单个设备&#xff08;如路由器…

Windows - UWP - 为UWP应用创建桌面快捷方式

Windows - UWP - 为UWP应用创建桌面快捷方式 前言 这是一个较为简单的方式&#xff0c;不需要过多的命令行。 How 首先Win R -> shell:AppsFolder -> 回车&#xff0c; 这将显示电脑上的已安装应用&#xff08;Win32 & UWP&#xff09;&#xff1a; 找到想要创建…

nginx反向代理流程

一、nginx反向代理流程 反向代理&#xff1a;使用代理服务器来接受internet上的连接请求&#xff0c;然后将请求转发给内部网络中的上游服务器&#xff0c;并将上游服务器得到的结果返回给请求连接的客户端&#xff0c;代理服务器对外表现就是一个web服务器。Nginx就经常拿来做…

go 中自定义包以及import

目录结构如下 注意ellis这个文件夹是在工作区的src目录下 testpackage.go package testpackageimport ("fmt" )func Test() {fmt.Println("test") }main.go package mainimport ("ellis/testpackage""fmt" )type Name struct {Fir…

Linux与bash(基础内容一)

一、常见的linux命令&#xff1a; 1、文件&#xff1a; &#xff08;1&#xff09;常见的文件命令&#xff1a; &#xff08;2&#xff09;文件属性&#xff1a; &#xff08;3&#xff09;修改文件属性&#xff1a; 查看文件的属性&#xff1a; ls -l 查看文件的属性 ls …

81-基于stm32单片机DHT11温湿度MQ4可燃气体天然气浓度检测系统自动散热加湿排气Proteus仿真+源码...

资料编号&#xff1a;081 一&#xff1a;功能介绍&#xff1a; 1、采用stm32单片机OLED显示屏MQ4可燃气体浓度检测DHT11温湿度电机按键&#xff0c;制作一个温湿度采集、MQ4可燃气体浓度采集&#xff0c;OLED显示相关数据&#xff0c; 2、通过按键设置温度上限、湿度下限、可燃…

Stable Diffusion核心算法DDPM解析

DDPM&#xff1a;Denoising Diffusion Probabilistic Model&#xff0c;去噪扩散概率模型 本文参考&#xff1a;一个视频看懂扩散模型DDPM原理推导|AI绘画底层模型_哔哩哔哩_bilibili 1、大概原理 从右往左为正向加噪过程&#xff0c;从左往右为逆向降噪过程。 在正向过程中不…

Ubuntu发布java版本

1、连接服务器 2、进入目录 cd /usr/safety/app/3、上传jar文件 4、杀掉原java进程 1. 查看当前java进程 2. ps -ef|grep java 3. ycmachine:/usr/safety/app$ ps -ef|grep java root 430007 1 6 01:11 pts/0 00:02:45 /usr/local/java/jdk1.8.0_341/bin/j…

【LeetCode】205. 同构字符串

205. 同构字符串&#xff08;简单&#xff09; 方法&#xff1a;哈希映射 思路 判断两个字符串是不是同构字符串&#xff0c;只需要判断对应的字符是不是存在映射关系&#xff0c;我们可以使用 map 来保存字符间的映射关系。由于 “不同字符不能映射到同一个字符上&#xff0…

Error: EACCES: permission denied, rename ‘/usr/local/lib/node_modules/appium‘

在使用npm uninstall -g appium卸载appium的过程中报错 Error: EACCES: permission denied, rename /usr/local/lib/node_modules/appium -> /usr/local/lib/node_modules/.appium-cfBVovI6 npm ERR! code EACCES npm ERR! syscall rename npm ERR! path /usr/local/lib/n…

智能报警系统:利用人工智能保障安全和及时应对危险

引言&#xff1a;随着人工智能的快速发展&#xff0c;智能报警系统成为了一种高效、及时应对危险和保障安全的重要工具。通过分析监控视频中的图像、声音以及其他传感器数据&#xff0c;人工智能可以自动检测和识别火灾、破坏、烟雾、异常温度等情况&#xff0c;并及时触发报警…

Python学习笔记_基础篇(十一)_socket编程

python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和软件组成的。硬件中的CPU是计算机的核心&#xff0c;它承担计算机的所有任务。 操作系统是运行在硬件之上的软件&#xff0c;是计算机的管理者&#xff0c;它负责资源的管理和分配、任务的调度。 程序是运行…

虚拟拍摄,如何用stable diffusion制作自己的形象照?

最近收到了某活动的嘉宾邀请&#xff0c;我将分享&#xff1a; 主题&#xff1a;生成式人工智能的创新实践 简要描述&#xff1a;从品牌营销、智能体、数字内容创作、下一代社区范式等方面&#xff0c;分享LLM与图像等生成式模型的落地应用与实践经验。 领域/研究方向&#xff…

Zookeeper进阶篇 - Paxos协议算法、ZBA协议算法原理、Leader选举原理

Zookeeper底层原理篇&#xff0c;​让学习绚丽多彩起来&#xff01;&#xff01;&#xff01;

Go语言GIN框架安装与入门

Go语言GIN框架安装与入门 文章目录 Go语言GIN框架安装与入门1. 创建配置环境2. 配置环境3. 下载最新版本Gin4. 编写第一个接口5. 静态页面和资源文件加载6. 各种传参方式6.1 URL传参6.2 路由形式传参6.3 前端给后端传递JSON格式6.4 表单形式传参 7. 路由和路由组8. 项目代码mai…