Redis执行lua脚本能保证原子性吗?
前情提要: 鼠鼠上周面试,面试官问到:你知道Redis执行lua脚本能不能保证原子性吗?
呵!我自信的回答道:Of course!
心想:网上的视频不都说了吗,当对多个命令进行操作时,我们可以将命令写到lua脚本里保证原子性
此时面试官笑着说道:那你知道lua脚本是怎么保证原子性的吗?
.....完了!!! 鼠鼠我啊,只知其然不知其所以然,楞在那里,支支吾吾半天,红着脸说不出话来
面试官似笑非笑的说道:你可以回去等通知了 于是鼠鼠拿着简历灰溜溜的走了,额...扯远了
回归正题, 在谈上面的问题之前,我们先聊聊原子性是什么
原子性:一个事务的所有命令要么全部执行成功,要么全部执行失败
所以Redis执行lua脚本就可以保证脚本里面的每一行命令要么全部执行成功要么全部都不执行对吗?
我们来看看redis官网对于lua脚本的使用说明
上面的话翻译过来的意思大概就是:Redis在执行lua脚本的命令期间,所有客户端对Redis的操作都会阻塞住,保证Redis在执行脚本的期间不会有其他客户端的命令插入执行
可以看到,官方对lua使用的定义中,是不包含原子性的,所以Redis使用lua脚本能保证原子性吗?
下面我们来做个实验,这里我先编写了一个简单的lua脚本,
可以看到我对a进行赋值,设置a=2,同时我还定义了一个字符串的变量b,并且在脚本中对变量b进行了加法运算,此时我对字符串进行了加法运算,那么脚本会出现异常,我们来看看脚本执行错误命令时会不会回滚
这里我先在Redis中设置key a,并赋值为1
接着我们来尝试执行lua脚本
可以看到脚本的命令执行出现了错误,那么如果lua可以保证原子性,此时应该会进行回滚操作,也就是我的a的值还是1
我们来尝试获取a的值
可以看到a的值是2,也就是说Redis的没有进行回滚操作
总结
由此我们可以知道Redis执行lua脚本并不能保证原子性,使用lua脚本只能保证Redis的多个命令原子执行,在这组命令的执行期间,不会有其他客户端的命令干扰
或者可以说:Redis对于原子性的定义与我们所知道的关系型数据库中ACID的原子性定义不同 Redis的原子性是指在执行一组命令期间,只要这组命令没有执行完,就不会去执行客户端发送的其他命令
诶!于是有人会问了:那这个lua脚本不是和redis的事务一样吗,都不支持回滚,那为啥还要用lua脚本
好问题!
我们来分析一下Redis事务和Redis执行lua脚本的区别就可以知道为什么推荐用lua脚本去执行多个命令了
Redis事务中, 在客户端向Redis请求执行MULTI命令后,后续客户端每一个命令请求都会被redis的服务端记录到一个事务队列里,知道客户端执行EXEC命令时,Redis才会按照先进先出的方式去执行事务队列里的命令,也就是说,在Redis事务中,客户端每一个命令都会与Redis服务端进行交互,需要多次的网络IO操作
相比之下,Redis执行lua脚本,只需要客户端发送一次请求,由Redis服务端去执行自己的脚本文件,这样执行一次网络IO就可以完成操作,显然,lua脚本这种方式比Redis事务会快很多 同时,lua脚本相比于Redis事务这种命令组合的方式,自己可以定义更复杂的业务逻辑,所以这就是为什么推荐使用lua脚本执行Redis命令保证原子性的原因
我们再来继续深入探讨一下,lua脚本一定能保证Redis执行命令期间不会受到其他客户端请求的打扰吗? 废话,你上面不是说了可以保证吗
诶,此言差矣!我只在单机架构环境下作出保证,集群架构下我可没保证
在这种一主多从的架构下,只有主节点能执行写命令,所以可以保证lua脚本里面的命令是原子的
而在这种主从集群的架构下,每个主节点被分配了一部分哈希槽,不同的key映射到不同的哈希槽上,对key的命令可能不会执行在不同的主节点上,这样就无法保证lua脚本里的命令是原子的了
总结:redis执行lua脚本里的命令是否是原子操作,要根据不同情况来讨论
呼,到这里终于结束了,有了这次的经验,下次面试鼠鼠一定要把丢失的颜面都找回来!