在使用 Redis 的过程中,如果需要打包执行多个命令,可以使用事务、Pipeline、Lua 脚本等方式。这三种方式适用的场景也不相同。
事务
在非关系型数据库 Redis 中也有事务的概念,Redis 的事务可以保证多个命令执行的原子性,多个命令在一个事务中具有原子性不可分割的执行,不会被打断。但 Redis 的事务与传统的关系型数据库的事务不同,Redis 事务并不支持回滚,如果在事务执行中某个命令发生了错误,Redis 会继续执行剩余的命令。在执行事务时,每一条命令都需要和 Redis 进行一次网络交互,Redis 在收到命令后会将这些命令后并不会立即执行而是将命令缓存起来,直到发送 EXEC
确定执行命令后才会开始执行。
在积累命令的缓存阶段发生如语法错误、内存不足等问题,在调用 EXEC
命令后 Redis 会直接拒绝执行事务。而在执行事务的阶段发生如操作类型不一致(对zset类型使用string操作)等问题,Redis 会继续执行剩余命令,不支持回滚。
Redis 事务相关的命令有以下几个:
- MULTI: 标记事务块的开始。
- DISCARD: 放弃执行事务块内的所有命令。
- EXEC: 执行事务块内的所有命令。
- WATCH: 监听一个或多个key。可以用于在事务执行前监听到 key 被修改后打断事务。
- UNWATCH: 取消监听 key。
Pipeline
Pipeline 是用于优化网络延迟的方案,主要用于在单个请求/响应周期内执行多个命令,允许客户端在不等待每个命令的响应的前提下一次性发送多个命令到 Redis 服务端,服务端在收到这批命令后依次执行,在执行完成后返回结果。在网络延迟较高(如跨地域机房)的环境中可以显著地减少等待时间。但 Pipeline 并不保证原子性,多个命令之间是非原子操作的独立执行,在并发请求下,多条命令之间可能执行来自其他请求的命令。和事务一样,多个命令中即使有执行失败的命令,其他命令还是会被执行。
1 | public class RedisPipelineTest { |
Lua脚本
Redis 从 2.6.0 开始支持 Lua 脚本,Redis 会将 Lua 脚本封装为一个单独的事务,如果有其他请求要执行命令时,Redis 会将这些命令暂存,等到 Lua 脚本处理完成后才恢复执行,因此 Lua 脚本可以保证原子性,同时在命令执行过程中如果执行失败了,会影响后续命令的执行。借助 Lua 脚本,可以实现诸如分支流程控制、运算操作、前后命令执行结果依赖等功能,Redis 事务本身是不支持这些操作的。
1 | EVAL "lua script" KEYS1 KEYS2 ... |
这里举例一个秒杀场景库存扣减使用的 Lua 脚本:
1 | local key = KEYS[1] -- 商品的键名 |
Redis Cluster 执行事务、Lua脚本
在 Redis 集群模式下,数据存储根据 slot 分片存储在不同节点下。默认情况下,Redis 会根据 key 的哈希值来决定数据存储在哪个节点上,如果需要干预哈希值的计算,可以使用 Redis 提供的 hashtag 的功能,Redis 仅 对 key 中使用大括号 {}
包裹的部分进行哈希值计算,并基于这个哈希值分配存储节点。仅当大括号内有有内容时才会使用大括号内的内容来计算哈希值,否则会使用整个 key 的来计算哈希值。
事务和 Lua 脚本不能跨节点执行,一个事务所涉及的所有 key 必须位于同一个节点上,否则会拒绝执行并提示 command keys must in same slot
。而 Lua 脚本执行过程中因为 key 不在同一节点上导致失败,则会导致脚本停止执行,可能会影响数据的一致性。在开发时需要考虑到可能跨节点的问题,将可能参与事务的 key 使用 hashtag 指定存储节点来避免执行失败。
在 Redis 7.0.11 及后续版本提供了一个选项配置 allow-cross-slot-keys
,允许在单个命令中操作位于不同节点的 key,例如 mset
、mget
等批量操作命令,但执行事务依旧不能跨节点。