简介

Redis 从 2.6 版本开始引入使用 Lua 编程语言进行的服务器端脚本编程功能,这个功能可以让用户直接在 Redis 内部执行各种操作,从而达到简化代码并提高性能的作用。 P248

在不编写 C 代码的情况下添加新功能 P248

通过使用 Lua 对 Redis 进行脚本编程,我们可以避免一些减慢开发速度或者导致性能下降对常见陷阱。 P248

将 Lua 脚本载入 Redis P249
  • SCRIPT LOAD 命令可以将脚本载入 Redis ,这个命令接受一个字符串格式的 Lua 脚本为参数,它会把脚本存储起来等待之后使用,然后返回被存储脚本的 SHA1 校验和
  • EVALSHA 命令可以调用之前存储的脚本,这个命令接收脚本的 SHA1 校验和以及脚本所需的全部参数
  • EVAL 命令可以直接执行指定的脚本,这个命令接收脚本字符串以及脚本所需的全部参数。这个命令除了会执行脚本之外,还会将被执行的脚本缓存到 Redis 服务器里面

由于 Lua 的数据传入和传出限制, Lua 与 Redis 需要进行相互转换。因为脚本在返回各种不同类型的数据时可能会产生含糊不清的结果,所以我们应该尽量显式的返回字符串。 P250

我们可以在 官方文档 中找到 Redis 和 Lua 不同类型之间的转换表:

Redis 类型转换至 Lua 类型

Lua 类型转换至 Redis 类型

创建新的状态消息 P251
  • Lua 脚本跟单个 Redis 命令以及 MULTI/EXEC 事务一样,都是原子操作
  • 已经对结构进行了修改的 Lua 脚本将无法被中断
    • 不执行任何写命令对只读脚本:可以在脚本对运行时间超过 lua-time-limit 选项指定的时间之后,执行 SCRIPT KILL 命令杀死正在运行对脚本
    • 有写命令的脚本:杀死脚本将导致 Redis 存储的数据进入一种不一致的状态。在这种情况下

使用 Lua 重写锁和信号量 P254

如果我们事先不知道哪些键会被读取和写入,那么就应该使用 WATCH/MULTI/EXEC 事务或者锁,而不是 Lua 脚本。因此,在脚本里面对未被记录到 KEYS 参数中的键进行读取或者写入,可能会在程序迁移至 Redis 集群的时候出现不兼容或者故障。 P254

获取锁在目前已不需要使用 Lua 脚本实现,可以直接使用 SET ,并用 PXNX 选项即可在键不存在的时候设置带过期时间的值。释放锁时为了保证释放的时自己获取的锁,需要使用 Lua 脚本实现。相关代码已在 实现自动补全、分布式锁和计数信号量 中实现。

移除 WATCH/MULTI/EXEC 事务 P258

一般来说,如果只有少数几个客户端尝试对被 WATCH 命令监视对数据进行修改,那么事务通常可以在不发生明显冲突或重试的情况下完成。但是,如果操作需要进行好几次通信往返,或者操作发生冲突的概率较高,又或者网络延迟较大,那么客户端可能需要重试很多次才能完成操作。 P258

使用 Lua 脚本替代事务不仅可以保证客户端尝试的执行都可以成功,还能降低通信开销,大幅提高 TPS 。同时由于 Lua 脚本没有多次通信往返,所以执行速度也会明显快于细粒度锁的版本。

Lua 脚本可以提供巨大的性能优势,并且能在一些情况下大幅地简化代码,但运行在 Redis 内部但 Lua 脚本只能访问位于 Lua 脚本之内或者 Redis 数据库之内的数据,而锁或者 WATCH/MULTI/EXEC 事务并没有这一限制。 P263

使用 Lua 对列表进行分片 P263

分片列表的构成 P263

为了能够对分片列表的两端执行推入操作和弹出操作,在构建分片列表时除了需要存储组成列表的各个分片之外,还需要记录列表第一个分片的 ID 以及最后一个分片的 ID 。当分片列表为空时,这两个字符串存储的分片 ID 将是相同的。 P263

组成分片列表的每个分片都会被命名为 <listname>:<shardid> ,并按照顺序进行分配。具体来说,如果程序总是从左端弹出元素,并从右端推入元素,那么最后一个分配的索引就会逐渐增大,并且新分片的 ID 也会变得越来越大。如果程序总是从右端弹出元素,并从左端推入元素,那么第一个分片的索引就会逐渐减少,并且新分片的 ID 也会变得越来越小。 P264

当分片列表包含多个列表时,位于分片两端的列表可能是被填满的,但位于两端之间的其他列表总是被填满的。 P264

将元素推入分片列表 P265

Lua 脚本根据命令 LPUSH/RPUSH 找到列表的第一个分片或者最后一个分片,然后将元素推入分片对应的列表中,若分片已达个数上限(可以取配置中的 list-max-ziplist-entries 的值 - 1 作为上限),则会自动产生一个新的分片,继续推入,并更新第一个分片或者最后一个分片的分片 ID 。当推入操作执行完毕后,它会返回被推入元素的数量。 P265

从分片里面弹出元素 P266

Lua 脚本根据命令 LPOP/RPOP 找到列表的第一个分片或者最后一个分片,然后在分片非空的情况下,从分片里面弹出一个元素,如果列表在执行弹出操作之后不再包含任何元素,那么程序就对记录着列表两端分片信息的字符串键进行修改(注意只有列表端分片为空时才修改对应的字符串键,而整个列表为空时,不做调整) P267

对分片列表执行阻塞弹出操作 P267

这一段书上讲得看不懂,也不知道为什么需要书中的花式操作才能完成。

个人觉得分片列表的阻塞弹出其实并不需要列表自身的阻塞弹出,我们可以不断执行上述 Lua 脚本实现的弹出元素的操作,若弹出成功,则直接返回,若弹出失败,则睡 1 ms 后继续执行弹出操作,直至弹出成功或者达到超时时间。这样我们对 Redis 对操作只在 Lua 脚本中,原子性保证了一定会弹出分片列表两端的元素。

02-04 04:23