Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

上一篇博客说到了 redis 的事务机制,知道了 redis 的事务机制可以保证一组命令可以原子执行,但是不保证这组命令全部执行成功。redis 中也使用了 LUA 脚本保证原子性。

先来看一下 redis 官方对LUA脚本做的解释:LUA是一种脚本语言,通过EVAL命令执行LUA脚本,且LUA脚本具备原子性

image-20250426194856318

执行脚本

EVAL

EVAL 的语法

1
EVAL script numkeys [key [key ...]] [arg [arg ...]]

EVAL:执行LUA脚本

script:LUA 脚本源码——必填

numberkeys:输入键名的数量——必填

key[key….] : 脚本中使用的 redis 键名——选填

arg[arg…]:传递给脚本的附加参数——选填

image-20250426201919230

EVAL :执行LUA脚本

“return ARGV[1]”:LUA脚本,返回第一个附加参数ARGV[1]的值

0:键名数量为0,表示不操作任何 redis 键

hello:作为参数传递给脚本,存储在 ARGV[1]中

当然,我们也可以直接将参数放在LUA脚本中,不需要额外传递参数

image-20250426202847532

也可以指定key值和参数

image-20250426203057865

注意:无论是那种写法,为了保证集群模式下能够正确根据键名计算出数据分片的位置,必须要显示指定 键值,不可以通过动态获取的方式获取键名。

动态获取键名的方式不利于维护、不利于路由、不利于原子性的保证

脚本和Redis交互

redis.call()

在LUA脚本中执行命令,如果命令出错,直接抛出错误,中断脚本的执行

适用场景:需要严格保证命令成功的场景,如扣减库存等

image-20250426204930144

redis.pcall()

在 LUA 脚本中执行命令,出错时脚本可以继续执行

适用场景:需要容错处理的场景

Redis的部署方式

单机部署

多个客户端共用一个redis,所以单机部署的情况下,LUA脚本是一定能保证原子性的。

redis单机部署-第 1 页.drawio

主从部署

redis的主从部署采用了读写分离的方式,只对主服务器读写,对从服务器读,从服务器只接受主服务器的命令,所以主从部署也能保证LUA脚本的原子性

redis单机部署-第 2 页.drawio

集群部署

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值可以映射到同一个哈希槽中

redis单机部署-第 3 页.drawio

LUA脚本一定能保证原子性吗

不能

**原因:**执行LUA脚本是一个原子操作,但是执行结果是否是原子性还取决于Redis的部署方式,Redis的部署方式如果是单机或者主从,那么一定可以保证原子性,如果Redis的部署方式是集群分片的部署方式,那么可能不会原子性。因为集群分片的方式放每个redis的管理一部分哈希槽,Redis集群的哈希槽是固定的,为16384, 而key的值是可以大于 16384的,导致不同的 key 值哈希映射到了相同的槽,这样就不能保证原子性了。

解决方案:保证LUA脚本中涉及的key值都属于同一个哈希槽。

评论