Redis lua脚本简要学习

redis,lua,脚本,简要,学习 · 浏览次数 : 43

小编点评

**分析思路:** 1. **错误提示:**“BUSY Redis is busy running a script.” 这提示表明 Redis 服务器正处于执行脚本时处于阻塞状态。 2. **慢命令分析:**错误提示显示最近十条 Redis 命令中所有 ID 为 `7ae4b3e7f4bf3b0c3470baf33dd1b4941599b520` 的 lua脚本导致阻塞。 3. **slowlog 分析:**slowlog 显示执行 lua脚本所需的执行时间为 58 毫秒,远远超过预期 0.5 秒的 Redis 服务器默认脚本执行时间。 4. **脚本性能分析:**由于 slowlog 的结果表明,执行 lua脚本会导致阻塞,因此导致其他客户端无法获取结果集。 **缓解方式:** 1. **使用 `SCRIPT KILL` 命令强制结束脚本:`script KILL` 命令可以尝试强制终止脚本,但由于该方法不安全,建议谨慎使用。 2. **分组处理脚本:**可以使用 `SCRIPT` 的 `for` 循环进行处理,以处理不超过 1 万或 1000 个参数的脚本。 3. **使用高效脚本引擎:**如果需要,可以考虑使用其他性能更高的 Redis 脚本引擎,例如 `redisson` 或 `aioredis`。 **其他建议:** * 确保 `redis` 服务器的资源充足,特别是 CPU 和内存。 * 优化 Redis 服务器的配置,例如设置 `slowlog` 的最大记录数。 * 如果可能,尝试使用其他工具或框架来执行脚本,以避免阻塞问题。

正文

Redis lua脚本简要学习


背景

上周督促客户从Windows平台升级到了Linux平台.
redis一周相安无事. 
但是这周一突然又出现了卡断和慢的情况.
只能继续进行分析.

分析思路

现场日志里面出现了大量的错误提示:
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

不管用chatGPT后者是其他工具都可以查询出来:
Redis执行lua脚本时间太长了. 导致其他客户端无法获取结果集. 

然后计划通过redis的slowlog 查询验证一下自己的想法

分析过程


我在自己的测试机器上面执行了 slowlog get 获取如下:

10) 1) (integer) 996
    2) (integer) 1685314080
    3) (integer) 10513
    4)  1) "EVALSHA"
        2) "7ae4b3e7f4bf3b0c3470baf33dd1b4941599b520"
        3) "879"

发现最近十条慢命令全是id为: 7ae4b3e7f4bf3b0c3470baf33dd1b4941599b520 的lua脚本导致的.

督促现场抓紧获取指令: 
现场反馈的图片与我这边一致
但是不太一样的是:
现场有四万个键值对耗时 接近 9 秒钟. 
比我这边有非常大的差距. 

我这边又让研发同事去另外一个大项目查询了下: 2万个参数. 执行时间为:58毫秒左右

389654
1685325926
57581
EVALSHA
7ae4b3e7f4bf3b0c3470baf33dd1b4941599b520
20855

基本上明确是此lua脚本导致的卡顿

问题再现以及缓解

网上查了一点简单资料, 准备做一下实验
在一个redis的客户端上面你执行命令
eval "while 0==0 do end" 0
然后在另外的redis客户端验证
redis-cli
get 1 
get 2 
会提示: "BUSY Redis is busy running a script."
然后执行: script KILL
再执行
get 1 就可以正常给出结果了.

说明问题基本上可以再现. 

缓解方式: 如果不是一个很需要数据一致性的场景, 可以进行一下SCRIPT KILL
后期解决方式, 应该可以分组,每组不超过1万或者是1000 进行处理, 循环进行清理操作. 

未解决的问题

翻找了一早上资料, 没有解决如何根据evalsha 的sha1值反向获取具体的lua脚本.
chatgpt给的结果就是垃圾, 一个get 一个list命令 script 根本没有这些参数. 

没办法只能求助于开发:
    private final static DefaultRedisScript SessionExistRedisScript = new DefaultRedisScript<>(
            "local expires = ''\n" +
                    "for i=1, #KEYS do\n" +
                    "  if(redis.call('EXISTS','caf-session:sessions:'..KEYS[i]) == 0)\n" +
                    "  then\n" +
                    "    expires = expires..';'..KEYS[i]\n" +
                    "  end\n" +
                    "end\n" +
                    "return expires", String.class
    );

问题思考

前端时间刚整理了因为Redis的scan导致机器卡顿宕机的问题
今天早上在看ergonomics导致full GC无法提供服务的现象.

我突然感觉redis因为单线程的处理逻辑, 如果大家都需要同步的获取redis的结果
那么每次redis执行吗命令都相当于在做一次minor GC
如果是一个slowlog. 那么就相当于所有连接着一个redis的所有应用服务器一起在做STW.的full GC
所以从时间链上分析. 任何single point 都需要非常严格的重视. 

所以研发写的那一套lua脚本非常值得研究与修改. 

lua脚本的优点

(1) 减少网络开销: 在Redis操作需求需要向Redis发送5次请求,
    而使用脚本功能完成同样的操作只需要发送一个请求即可,减少了网络往返时延。
(2) 原子操作: Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。
    换句话说在编写脚本的过程中无需担心会出现竞态条件,也就无需使用事务。事务可以完成的所有功能都可以用脚本来实现。
(3) 复用: 客户端发送的脚本会永久存储在Redis中,
    这就意味着其他客户端(可以是其他语言开发的项目)可以复用这一脚本而不需要使用代码完成同样的逻辑。
(4) 速度快:见 与其它语言的性能比较, 还有一个 JIT编译器可以显著地提高多数任务的性能; 
    对于那些仍然对性能不满意的人, 可以把关键部分使用C实现, 然后与其集成, 这样还可以享受其它方面的好处。

关于lua脚本的继续学习

前端时间想学习 openresty 使用lua脚本.
但是非常可耻的立了flag没有学习深入下去. 
所以发现还是遇到问题督促学习更好.
需要拥抱问题, 不能害怕问题, 通过问题来学习和提高自己. 

lua脚本的基础知识
lua脚本是解释性的语言, 跟python和shell 都比较类似. 
print("hello 济南小老虎!")
就是一个最简单的 hello world的脚本.

数据类型: 
nil,布尔,浮点数,字符串,函数,线程,关联数组(table等)

循环判断
while( true )
do
    print("测试循环")
    os.execute("sleep " .. 2)
end


Redis里面eval等学习

eval 其实一个非常不安全的命令
很多一句话木马基本上都是基于这个吗精灵来做的
在js,php,等语言里面都有存在.
redis 里面 eval 是用于调用lua脚本的命令. 

eval以及script脚本可以实现很多lua脚本的调用和使用. 
学习和举例如下:
script load "while (true) do end"
会返回一个hash值如下:

127.0.0.1:6379> script load "while (true) do end"
"f926d8b19784f2c8c63b04288b32cb8de66070ba"

我理解此处就像是数据库绑定变量后可以避免hard parse 的方式一样. 
下次执行时可以使用

evalsha f926d8b19784f2c8c63b04288b32cb8de66070ba 0 

注意需要加最后一个0 用于指代参数个数
此时就可以执行命令了
等效率执行
eval "while (true) do end" 0

# 注意执行此命令会阻塞产品的使用, 必须尽早执行kill处理.
# 因为redis默认的lua脚本超时时间是 5 seconds 所以只会kill 5s 以上的script. 
# 举例如下
# 我执行删除操作. 其实一直没有返回, 因为还没到五秒
127.0.0.1:6379> script kill
OK
(2.23s)
# 执行 lua脚本的返回情况如下:
(error) ERR Error running script (call to f_f926d8b19784f2c8c63b04288b32cb8de66070ba): @user_script:1: Script killed by user with SCRIPT KILL...
(5.00s)

与Redis lua脚本简要学习相似的内容:

Redis lua脚本简要学习

# Redis lua脚本简要学习 ## 背景 ``` 上周督促客户从Windows平台升级到了Linux平台. redis一周相安无事. 但是这周一突然又出现了卡断和慢的情况. 只能继续进行分析. ``` ## 分析思路 ``` 现场日志里面出现了大量的错误提示: BUSY Redis is bu

[转帖]Redis中的Lua脚本

最近琢磨分布式锁时接触到的知识点,简单记一下。 文章目录 1. Redis中的Lua2. 利用Lua操作Redis3. Lua脚本的原子性4. 关于 EVALSHA5. 常用`SCRIPT` 命令6. 脚本本地化 1. Redis中的Lua Redis支持Lua,代码直接发送完整脚本即可。基本语法(

[转帖]【Redis】Redis中使用Lua脚本

Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。 Lua具体语法参考:https://www.runoob.com/lua/lua-tutorial.html 脚本的原子性 Redis使用单个Lua解释器去运

[转帖]Redis里使用Lua

http://me.52fhy.com/lua-book/chapter11.html 版本:自2.6.0起可用。 时间复杂度:取决于执行的脚本。 使用Lua脚本的好处: 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。 原子操作。redis会将整个脚本作为一个整体执行,中间不会被

[转帖]Redis进阶实践之七Redis和Lua初步整合使用

https://www.cnblogs.com/PatrickLiu/p/8391829.html 一、引言 Redis学了一段时间了,基本的东西都没问题了。从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运行在任何平台上,也可以嵌入到大多数语言当中,来扩展其功

redis分布式锁,setnx+lua脚本的java实现

本文是基于redis缓存实现分布式锁,其中使用了setnx命令加锁,expire命令设置过期时间并lua脚本保证事务一致性。Java实现部分基于JIMDB提供的接口。

Python学习之十九_程序运行时间的验证

# Python学习之十九_程序运行时间的验证 ## 背景 ``` 最近一段时间比较忙. 而且还遇到了一个lua脚本优化redis访问的场景. 想着自己还在学习python(时断时续) 所以想借着这个场景,学习一下python连接redis,以及验证lua脚本和原生redis命令的效率问题. 虽然方

详解事务模式和Lua脚本,带你吃透Redis 事务

摘要:Redis事务包含两种模式:事务模式和Lua脚本。 本文分享自华为云社区《一文讲透 Redis 事务》,作者: 勇哥java实战分享。 准确的讲,Redis事务包含两种模式:事务模式和Lua脚本。 先说结论: Redis的事务模式具备如下特点: 保证隔离性; 无法保证持久性; 具备了一定的原子

[转帖]【Redis学习06】分布式锁及其优化

文章目录 前言1. 什么是分布式锁2. 分布式锁的实现2.1 基于Redis的分布式锁实现方法2.2 基于redis实现分布式锁的初级版本2.3 改进分布式锁2.4 基于Lua脚本改善分布式锁 前言 上一篇博客我们讲到秒杀问题的一人一单在单机模式下使用synchronized添加悲观锁能解决并发问题

[转帖]【Redis学习06】分布式锁及其优化

文章目录 前言1. 什么是分布式锁2. 分布式锁的实现2.1 基于Redis的分布式锁实现方法2.2 基于redis实现分布式锁的初级版本2.3 改进分布式锁2.4 基于Lua脚本改善分布式锁 前言 上一篇博客我们讲到秒杀问题的一人一单在单机模式下使用synchronized添加悲观锁能解决并发问题