上一篇博客说到了 redis 的事务机制,知道了 redis 的事务机制可以保证一组命令可以原子执行,但是不保证这组命令全部执行成功。redis 中也使用了 LUA 脚本保证原子性。
先来看一下 redis 官方对LUA脚本做的解释:LUA是一种脚本语言,通过EVAL命令执行LUA脚本,且LUA脚本具备原子性

执行脚本
EVAL
EVAL 的语法
1 | EVAL script numkeys [key [key ...]] [arg [arg ...]] |
EVAL:执行LUA脚本
script:LUA 脚本源码——必填
numberkeys:输入键名的数量——必填
key[key….] : 脚本中使用的 redis 键名——选填
arg[arg…]:传递给脚本的附加参数——选填

EVAL :执行LUA脚本
“return ARGV[1]”:LUA脚本,返回第一个附加参数ARGV[1]的值
0:键名数量为0,表示不操作任何 redis 键
hello:作为参数传递给脚本,存储在 ARGV[1]中
当然,我们也可以直接将参数放在LUA脚本中,不需要额外传递参数

也可以指定key值和参数

注意:无论是那种写法,为了保证集群模式下能够正确根据键名计算出数据分片的位置,必须要显示指定 键值,不可以通过动态获取的方式获取键名。
动态获取键名的方式不利于维护、不利于路由、不利于原子性的保证
脚本和Redis交互
redis.call()
在LUA脚本中执行命令,如果命令出错,直接抛出错误,中断脚本的执行
适用场景:需要严格保证命令成功的场景,如扣减库存等

redis.pcall()
在 LUA 脚本中执行命令,出错时脚本可以继续执行
适用场景:需要容错处理的场景
Redis的部署方式
单机部署
多个客户端共用一个redis,所以单机部署的情况下,LUA脚本是一定能保证原子性的。
主从部署
redis的主从部署采用了读写分离的方式,只对主服务器读写,对从服务器读,从服务器只接受主服务器的命令,所以主从部署也能保证LUA脚本的原子性
集群部署
Redis的集群部署,是将数据分片到不同的redis服务器上,也就是说集群中的每个Redis对客户端来说是既可以读也可以写的,客户端根据操作的键值,通过哈希映射到不同的redis中,所以操作不同的键值可能映射到相同的槽中,这样就不一定能保证原子性了。所以一定要让LUA脚本操作同一个key
注意:redis的集群使用的是数据分片而不是一致性哈希,redis集群的哈希槽一共有 16384个,这是固定的,集群中每个Redis结点都负责一部分哈希槽。CRC16(key) % 16384 来计算键 key 属于哪个槽
例如: redis结点A负责0~5500号的哈希槽
redis结点B负责5501~11000号哈希槽
redis结点C负责11001~16384 号哈希槽
而 key 值是可以大于16384的,所以存在不同的key值可以映射到同一个哈希槽中
LUA脚本一定能保证原子性吗
不能
**原因:**执行LUA脚本是一个原子操作,但是执行结果是否是原子性还取决于Redis的部署方式,Redis的部署方式如果是单机或者主从,那么一定可以保证原子性,如果Redis的部署方式是集群分片的部署方式,那么可能不会原子性。因为集群分片的方式放每个redis的管理一部分哈希槽,Redis集群的哈希槽是固定的,为16384, 而key的值是可以大于 16384的,导致不同的 key 值哈希映射到了相同的槽,这样就不能保证原子性了。
解决方案:保证LUA脚本中涉及的key值都属于同一个哈希槽。