Redis 实战 —— 14. Redis 的 Lua 脚本编程

简介

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

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

经过使用 Lua 对 Redis 进行脚本编程,咱们能够避免一些减慢开发速度或者致使性能降低对常见陷阱。 P248github

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

因为 Lua 的数据传入和传出限制, Lua 与 Redis 须要进行相互转换。由于脚本在返回各类不一样类型的数据时可能会产生含糊不清的结果,因此咱们应该尽可能显式的返回字符串。 P250redis

咱们能够在 官方文档 中找到 Redis 和 Lua 不一样类型之间的转换表:数据库

Redis 类型转换至 Lua 类型编程

Redis Lua
integer reply number
bulk reply string
multi bulk reply table (may have other Redis data types nested)
status reply table with a single ok field containing the status
error reply table with a single err field containing the error
Nil bulk reply false boolean type
Nil multi bulk reply false boolean type

Lua 类型转换至 Redis 类型缓存

Lua Redis
number integer reply (the number is converted into an integer)
string bulk reply
table (array) multi bulk reply (truncated to the first nil inside the Lua array if any)
table with a single ok field status reply
table with a single err field error reply
boolean false Nil bulk reply
boolean true integer reply with value of 1
建立新的状态消息 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 脚本中,原子性保证了必定会弹出分片列表两端的元素。

本文首发于公众号:满赋诸机(点击查看原文) 开源在 GitHub :reading-notes/redis-in-action

相关文章
相关标签/搜索