我们知道`Redis`的指令是单线程执行的,而现在使用了`Lua`脚本,就可以通过`Lua`脚本来实现一些业务逻辑,那么如果`Lua`脚本执行超时或者陷入了死循环,这个时候其它的指令就会被阻塞,导致`Redis`无法正常使用。这时应该如何处理呢?
#### 脚本超时
为了解决`Lua`脚本超时的问题,`Redis`提供了一个超时时间的参数`lua-time-limit`来控制`Lua`脚本执行的超时时间,单位是毫秒,默认是`5000`(即`5`秒),到达超时时间之后`Lua`会自动中断脚本。
#### 脚本陷入死循环
假如脚本陷入了死循环,这时候超时时间就不起作用了,我们来模拟一下。
首先打开客户端一,执行命令`redis-cli`连接上`Redis`服务,然后执行一个死循环的`lua`脚本:
~~~java
eval 'while(true) do end' 0
~~~
![](https://img.kancloud.cn/03/bb/03bbf5133a4c9c3d42000d613cbabb25_481x59.png)
然后打开另一个客户端二,任意执行一个命令:
~~~java
get name
~~~
这时候会返回`busy`,表示当前无法执行这个命令:
![](https://img.kancloud.cn/1d/9c/1d9cdf954985c2833115740c5fb1be28_788x88.png)
提示`busy`之后,同时`Redis`也给出了解决方案,我们只能用`script kill`或者`shutdown nosave`命令,这两个命令又是做什么用的呢?
* `script kill`:当脚本陷入死循环之后,执行这个命令可以强制`Lua`脚本中断执行。这个脚本的局限性就是当前陷入死循环的`Lua`脚本必须没有成功执行过命令。
* `shutdown nosave`:强制退出`Lua`脚本,可以解决`script kill`命令的局限性。
接下来让我们在客户端二执行命令`script kill`,然后再去看看陷入死循环的客户端一的效果:
![](https://img.kancloud.cn/ec/4a/ec4aee2dad67cd39da27738ad03ec513_872x100.png)
可以看到,客户端一的`Lua`脚本已经退出了,根据后面的提示可以知道就是因为执行了`script kill`命令而导致了`Lua`脚本的中断。
现在我们重新用客户端一执行下面这个`Lua`脚本,这个脚本和上面脚本区别是:这里执行成功了一个`Redis`命令之后才开始死循环:
~~~java
eval "redis.call('set','age','28') while true do end" 0
~~~
![](https://img.kancloud.cn/ae/25/ae25b3946e2a5445f5a55f9818ca3144_643x89.png)
这时候再去客户端二执行`script kill`命令,发现无法中止`Lua`脚本了:
![](https://img.kancloud.cn/bf/00/bf00b16bdd036fcb86ad3305b0d0bde1_1069x101.png)
这里不允许直接中断`Lua`脚本是因为在死循环前已经有`Redis`命令被成功执行了,如果直接中断,那么就会造成数据不一致问题。
在这种场景下,只能通过执行`shutdown nosave`命令来强行中断`Lua`脚本,这里因为加了`nosave`之后不会触发`Redis`的持久化,所以当重启`Redis`服务之后,可以保证数据的一致性,下图就是执行`shutdown nosave`命令之后客户端一的效果图:
![](https://img.kancloud.cn/e1/89/e18995189fc6a6ae351ef18bf6d782dc_593x71.png)
#### 为什么可以执行 script kill 命令
`Redis`当中执行命令是单线程的,那么为什么`Lua`脚本陷入死循环之后其它客户端还可以执行`script kill`命令呢?
这是因为`Lua`脚本引擎提供了钩子(hook)函数,它允许在内部虚拟机执行指令时运行钩子代码,所以`Redis`正是利用了这一原理,在执行`Lua`脚本之前设置了一个钩子,也就是说`script kill`命令是通过钩子(hook)函数来执行的。
- Redis 为什么这么快
- 什么是 Redis
- Redis 的安装
- Redis 到底有多快
- Redis 是单线程还是多线程
- Redis 为什么选择使用单线程来执行请求
- 什么是 IO 多路复用机制
- Redis 中 I/O 多路复用的应用
- 一个简单的字符串,为什么 Redis 要设计的如此特别
- Redis 的 9 种数据类型
- 二进制安全字符串
- sds 空间分配策略
- sds 和 C 语言字符串区别
- sds 是如何被存储的
- type 属性
- encoding 属性
- 通过牺牲速度来节省内存,Redis 是觉得自己太快了吗
- 什么是压缩列表
- ziplist 的存储结构
- entry 存储结构
- ziplist 数据示例
- ziplist 连锁更新问题
- 为了加快速度,Redis 都做了哪些“变态”设计
- 列表对象
- linkedlist
- linkedlist 和 ziplist 的选择
- quicklist
- 列表对象常用操作命令
- Redis 中哈希分布不均匀该怎么办
- 哈希对象
- hashtable
- ziplist
- ziplist 和 hashtable 的编码转换
- 哈希对象常用命令
- 同一份数据,Redis 为什么要存”两次”
- 五种基本类型之集合对象
- intset 编码
- 集合对象常用命令
- 五种基本类型之有序集合对象
- skiplist 编码
- ziplist 编码
- ziplist 和 skiplist 编码转换
- 有序集合对象常用命令
- 要想用活 Redis,Lua 脚本是绕不过去的坎
- 发布与订阅
- 基于频道的实现
- 基于模式的实现
- Lua 脚本
- Lua 脚本的调用
- Lua 脚本中执行 Redis 命令
- Lua 脚本摘要
- Lua 脚本文件
- 脚本异常
- 作为一款内存数据库,为什么断电后 Redis 数据不会丢失
- Redis 持久化机制
- RDB 持久化机制
- AOF 持久化机制
- 内存耗尽后 Redis 会发生什么
- 内存回收
- 过期策略
- 8 种淘汰策略
- LRU 算法
- LFU 算法
- 不能回滚的 Redis 事务还能用吗
- Redis 有事务吗
- Redis 事务实现原理
- Redis 事务 ACID 特性
- watch 命令
- watch 命令的作用
- watch 原理分析
- Redis 为什么不直接用 master-slave 集群
- Redis 集群方案
- 主从复制
- 配置一主两从 master-slave 集群
- 主从复制原理分析
- 主从服务的不足之处
- Sentinel(哨兵)机制为什么从神坛滑落
- 哨兵 Sentinel 机制
- Sentinel 原理分析
- 配置 Sentinel 集群
- Sentinel 机制实战
- Sentinel 机制的不足之处
- Redis Cluster 集群凭什么成为了最终的胜利者
- Redis 分布式集群方案
- 客户端实现分片
- 中间代理服务实现分片
- Redis Cluster 方案
- 手动配置一个 Redis Cluster 集群
- Redis Cluster 集群常用命令
- 客户端如何使用 Redis Cluster 集群
- Redis Cluster 的不足
- 如何从 10 亿数据中快速判断是否存在某一个元素
- 缓存雪崩
- 缓存击穿
- 缓存穿透
- 布隆过滤器(Bloom Filter)
- 布隆过滤器的 2 大特点
- 布隆过滤器的实现(Guava)
- 布隆过滤器的如何删除