执行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' then
        return 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\"' then
        return 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切库问题

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

windows系统丢失mfc120u.dll的解决方法

1.mfc120u.dll是什么 mfc120u.dll是Windows操作系统中的一个动态链接库&#xff08;Dynamic Link Library&#xff0c;简称DLL&#xff09;文件。它包含了一些用于运行C程序的函数和其他资源。这个特定的DLL文件是Microsoft Foundation Classes&#xff08;MFC&#xff09;库的…

wireshark界面内容含义

网络分析工具——WireShark的使用&#xff08;超详细&#xff09;_世间繁华梦一出的博客-CSDN博客 wireshark抓包数据&#xff1a;理解与分析_wireshark里面length_ 佚名的博客-CSDN博客

app 自动化测试 - 多设备并发 -appium+pytest+ 多线程

1、appiumpython 实现单设备的 app 自动化测试 启动 appium server&#xff0c;占用端口 4723电脑与一个设备连接&#xff0c;通过 adb devices 获取已连接的设备在 python 代码当中&#xff0c;编写启动参数&#xff0c;通过 pytest 编写测试用例&#xff0c;来进行自动化测试…

python优雅地爬虫!

背景 我需要获得新闻&#xff0c;然后tts&#xff0c;在每天上班的路上可以听一下。具体的方案后期我也会做一次分享。先看我喜欢的万能的老路&#xff1a;获得html内容-> python的工具库解析&#xff0c;获得元素中的内容&#xff0c;完成。 好家伙&#xff0c;我知道我爬…

Data Abstract for .NET and Delphi Crack

Data Abstract for .NET and Delphi Crack .NET和Delphi的数据摘要是一套或RAD工具&#xff0c;用于在.NET、Delphi和Mono中编写多层解决方案。NET和Delphi的数据摘要是一个套件&#xff0c;包括RemObjects.NET和Delphi版本的数据摘要。RemObjects Data Abstract允许您创建访问…

Vue使用jspdf和html2canvas组件库结合导出PDF文件

效果图&#xff1a; 1、安装依赖&#xff1a; npm install html2canvas --save npm install jspdf --save 或 yarn add html2canvas --save yarn add jspdf --save 2、封装全局调用方法&#xff1a;this.$exportPDF(#id,文件名) 新建js文件&#xff1a;/utils/html2Pdf.js&am…

Mysql性能优化:什么是索引下推?

导读 索引下推&#xff08;index condition pushdown &#xff09;简称ICP&#xff0c;在Mysql5.6的版本上推出&#xff0c;用于优化查询。 在不使用ICP的情况下&#xff0c;在使用非主键索引&#xff08;又叫普通索引或者二级索引&#xff09;进行查询时&#xff0c;存储引擎…

QtCreator中设置自定义注释格式

QtCreator--工具--选项--文本编辑器--片段--组:C--添加 在其中添加一个key为&#xff1a;header&#xff0c;value如下图的组合&#xff1a; /*! ProjName : %{CurrentProject:Name}* FileName : %{CurrentDocument:FileName}* Brief : * Details : * Aut…

(三) 搞定SOME/IP通信之CommonAPI库

本章主要介绍在SOME/IP通信过程中的另外一个IPC通信利剑&#xff0c;CommonAPI库&#xff0c;文章将从如下几个角度让读者了解什么是CommonAPI, 以及库在实际工作中的作用 文中资源&#xff1a;vsomeipcommonapi指导文档与demo源码 SOME/IP通信之CommonAPI CommonAPI库是什么C…

Java虚拟机(JVM):堆溢出

一、概念 Java堆溢出&#xff08;Java Heap Overflow&#xff09;是指在Java程序中&#xff0c;当创建对象时&#xff0c;无法分配足够的内存空间来存储对象&#xff0c;导致堆内存溢出的情况。 Java堆是Java虚拟机中用于存储对象的一块内存区域。当程序创建对象时&#xff0c…

设计模式之简单工厂模式

一、概述 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类。 简单工厂模式&#xff1a;又叫做静态工厂方法模式&#xff0c;是由一个工厂对象决定创建出哪一种产品类的实例。 二、适用性 1.当一个类不知道它所必须…

MySQL 账号权限

mysql 在安装好后&#xff0c;默认是没有远端管理账号。 一、账号管理 1. 查看账号列表 MySQL用户账号和信息存储在名为 mysql 的数据库中。一般不需要直接访问 mysql 数据库和表&#xff0c;但有时需要直接访问。例如&#xff0c;查看数据库所有用户账号列表时。 USE mysql; …

Matplotlib数据可视化(二)

目录 1.rc参数设置 1.1 lines.linestype取值 1.2 lines.marker参数的取值 1.3 绘图中文预设 1.4 示例 1.4.1 示例1 1.4.2 示例2 1.rc参数设置 利用matplotlib绘图时为了让绘制出的图形更加好看&#xff0c;需要对参数进行设置rc参数设置。可以通过以下代码查看matplotli…

揭秘!体育比赛是如何快人一步购票的

最近&#xff0c;各类体育赛事正如火如荼的进行中&#xff0c;作为资深体育迷&#xff0c;看着赛场上的英雄们正在为荣誉和胜利而拼搏&#xff0c;内心也跟着激情澎湃起来。 为了享受精彩纷呈的赛事&#xff0c;越来越多体育迷选择亲临现场&#xff0c;感受更真实的比赛氛围&a…

VR仿真实训系统编辑平台赋予老师更多自由和灵活性

为了降低院校教师在VR虚拟现实方面应用的门槛&#xff0c;VR公司深圳华锐视点融合多年的VR虚拟仿真实训系统制作经验&#xff0c;制作了VR动物课件编辑器&#xff0c;正在逐渐受到师生们的关注和应用。 简单来说&#xff0c;VR畜牧专业课件编辑器是一种可以制作虚拟现实动物教学…

【WPF】 本地化的最佳做法

【WPF】 本地化的最佳做法 资源文件英文资源文件 en-US.xaml中文资源文件 zh-CN.xaml 资源使用App.xaml主界面布局cs代码 App.config辅助类语言切换操作类资源 binding 解析类 实现效果 应用程序本地化有很多种方式&#xff0c;选择合适的才是最好的。这里只讨论一种方式&#…

HTTP响应状态码大全:从100到511,全面解析HTTP请求的各种情况

文章目录 前言一、认识响应状态码1. 什么是HTTP响应状态码2. Http响应状态码的作用3. 优化和调试HTTP请求的建议 二、1xx 信息响应1. 认识http信息响应2. 常见的信息响应状态码 三、2xx 成功响应1. 认识HTTP成功响应2. 常见的成功响应状态码 四、3xx 重定向1. 认识http重定向2.…

WS2812B————动/静态显示

一&#xff0c;系统架构 二&#xff0c;芯片介绍 1.管脚说明 2.数据传输时间 3.时序波形 4.数据传输方法 5.常用电路连接 三&#xff0c;代码展示及说明 驱动模块 在驱动模块首先选择使用状态机&#xff0c;其中包括&#xff0c;空闲状态&#xff0c;复位清空状态&#xff0c…

LeetCode150道面试经典题-- 合并两个有序链表(简单)

1.题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 2.示例 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输…

STM32 FLASH 读写数据

1. 《STM32 中文参考手册》&#xff0c;需要查看芯片数据手册&#xff0c;代码起始地址一般都是0x8000 0000&#xff0c;这是存放整个项目代码的起始地址 2. 编译信息查看代码大小&#xff0c;修改代码后第一次编译后会有这个提示信息 2.1 修改代码后编译&#xff0c;会有提示…