Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > redis lua执行

拦截Redis命令导致的Lua脚本执行失败的问题解决

作者:CodeFox

本文主要介绍了拦截Redis命令导致的Lua脚本执行失败的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

大家好,今天分享一个在使用 redis lua 脚本过程中遇到的一个问题,问题不难,但是容易踩坑。

lua 脚本使用方式

   // 定义脚本资源
   DefaultRedisScript redisScript = new DefaultRedisScript<>();
   redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(SCRIPT_PATH + scriptName)));
   redisScript.setResultType(List.class);
   // 预加载脚本(可选)
   redisTemplate.getConnectionFactory().getClusterConnection().scriptLoad(redisScript.getScriptAsString().getBytes());
   // 执行脚本
   stringRedisTemplate.execute(redisScript, keys, args);

redis script 相关命令说明

execute 方法执行过程

reidsTemplate 里持有一个 ScriptExecutor,最终的执行都代理给 ScriptExecutor,ScriptExecutor 会通过 evalsha 命令去 redis server 端执行脚本。

如果之前已经通过 script load 命令预加载了 lua 脚本,则 evalsha 会正常执行;如果没有事先加载脚本且第一次执行该脚本,则 evalsha 会返回 "NOSCRIPT No matching script. Please use EVAL." 异常,该异常会封装成 RedisSystemException 抛出。

捕获异常后,判断如果异常类型是 NonTransientDataAccessException,且异常信息里包含 "NOSCRIPT" 关键词,则再通过 eval 命令传递完整的脚本来执行一次,执行完之后会缓存脚本,以后每次调用只需通过 evalsha 命令传递 sha1 即可执行。

项目中遇到的问题

负责的项目中有一段 lua 脚本用来做短信发送频率的限流处理,服务部署到全新的一套环境后发现请求报错 "NOSCRIPT No matching script. Please use EVAL.",根据上述介绍,该错误表示 redis server 通过传递的 sha1 找不到相应的脚本。

该服务目前做法是没事先通过 script load 预加载脚本,是通过懒加载方式,由第一个请求去做加载操作。因为新的这套环境 redis 集群也是新搭建的,所以肯定是没缓存此脚本的,但是按照上述分析,第一个请求 evalsha 失败后是会执行 eval 的。

所以可以推断是异常类型不是 NonTransientDataAccessException,或者异常信息里没有包含 "NOSCRIPT" 关键词,导致异常直接抛出去了。

经过排查发现是前两周是接入了 sentinel-redis 流控功能引起的问题。

<dependency>
     <groupId>com.xxx</groupId>
     <artifactId>xxx-sentinel-spring-boot-starter-redis</artifactId>
     <version>${xxx-sentinel.version}</version>
</dependency>

该模块会对每一个 redis 命令做拦截,然后通过 sentinel 流控 api 进行包裹。

实际命令是通过 method.invoke() 反射执行的。如果执行内部有异常,会抛出 InvocationTargetException。

综上,第一次执行 evalsha 命令抛出的 "NOSCRIPT" RedisSystemException 被包装成了 InvocationTargetException 异常,所以在此判断直接返回 false,导致异常直接抛出了,并没有执行后续的 eval 命令。

怎么拦截 redis 命令

我们知道 redis 命令都是通过 RedisConnection 对象执行的,RedisConnection 是从 RedisConnectionFactory 中 get 的。

RedisConnectionFactory 一般有 jedis、lettuce 这两种实现。

通过 SpringBoot 自动装配装载进 Spring 容器的就是具体的 RedisConnectionFactory 实现。

所以我们可以通过拦截 RedisConnectionFactory 的 getConnection 方法得到 RedisConnection,然后对 RedisConnection 在进行一次代理,这样所有的 redis 命令就都能走到我们自己的拦截器里了。

解决办法

回到主题,我们要怎么解决这个问题呢?

总结

到此这篇关于拦截Redis命令导致的Lua脚本执行失败的问题解决的文章就介绍到这了,更多相关redis lua执行内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文