在Redis2.6中,包含了一个Lua的解析器,让开发这可以在Redis内部写更好的查询语句。

没错这很像大型的关系数据库中的存储过程

这个最难的就是学习Lua,但是Lua是一个比其他流行语言小很多的脚本语言,他有很好的文档,有积极的社区。

这章不会详细介绍Lua,但是会有一些有用的例子会被提到。

Redis 官网:https://redis.io (英文),https://www.runoob.com/redis/redis-tutorial.html (中文)

Lua 官网:http://www.lua.org (英文),  https://www.runoob.com/lua/lua-tutorial.html (中文)

 

Redis + Lua 存储过程

在开始用Lua脚本之前,你会想知道为什么用他。

很多开发者不喜欢传统的存储过程,这有什么不同吗?

一个简短的答案:NO。

不正当的运用,Redis的Lua脚本结果很难测试,因为有复杂的业务逻辑在里面。

适当的运用,可以有效的提高效率以及简化代码。

在一个方法内,组合多命令,打包很多简单逻辑,代码会变得简单,会提高性能,因为去掉了一些中间过程的结果,最后的结果会在脚本中计算。

在接下来的例子中会很好解释上述内容。

 

Eval

eval命令会执行Lua脚本。

我们会运用很多的参数,让我们来看一个例子(在Ruby中执行,因为在命令行中执行多行不太好):

script = <<-eos
  local friend_names = redis.call(’smembers’, KEYS[1])
  local friends = {}
  for i = 1, #friend_names do
    local friend_key = ’user:’ .. friend_names[i]
    local gender = redis.call(’hget’, friend_key, ’gender’)
    if gender == ARGV[1] then
        table.insert(friends, redis.call(’hget’, friend_key, ’details’))
        end
  end
return friends eos

Redis.new.eval(script, [’friends:leto’], [’m’])

在上面的例子中我们获得Leto的所有男性朋友,我们用redis.call(“command”,ARG1,ARG2,…)来调用Redis命令。

如果你对Lua陌生,你要仔细看每一行代码。{}是创建一个空table(能代表array或dictionary),#TABLE 是获得Table中的元素的数量。 ..连接字符串用。

eval实际上接收4个参数。第二个参数应该是key 的数量,然而Ruby自动的帮我们填上了,考虑下:

eval ”.....” ”friends:leto” ”m”
vs
eval ”.....” 1 ”friends:leto” ”m”

在第一个(错误)的例子中,Redis怎么会知道有多少个是key,有多少是参数呢?在第二个中,就明确了。

这会带来第二个问题:为什么必须明确列出keys?

在Redis中要知道每个命令运行的时间,那些keys是需要的。这会让像Redis Cluster分布请求在多个Redis服务器中。你可能发现我们之前的例子是动态的读取key。hget获得Leto的所有男性朋友。这是因为提前列出key只是一个建议,并不是强制规则。上面的代码我们会很好的在一个单例中运行,但不能在尚未发布的Redis Cluster中。

 

脚本管理

即使通过eval可以把脚本缓存到Redis中,但是每次执行都要传递一个body并不理想。

作为替代,你可以注册一个脚本在Redis,然后用key执行。

script load 命令,他能返回一个SHA1值:

redis = Redis.new
script_key = redis.script(:load, "THE_SCRIPT")

如果想加载这个脚本,我们可以用evalsha :

redis.evalsha(script_key, ['friends:leto'], ['m'])

script kill,script flushscript exists是一些管理Lua 脚本的命令。

他们分别是:杀死运行的脚本,删除缓存中的所有脚本,看一个脚本是否在缓存中。

 

Libraries

Redis的Lua提供了一个很有用库,当table.libstring.libmath.lib都是很有用的。

cjson.lib值得拿出来说一下:

redis.evalsha ".....", [KEY1], [JSON.fast_generate({gender: 'm', ghola: true})

你也可以反序列化在Lua脚本中:

local arguments = cjson.decode(ARGV[1])

当然,JSON库也可以在Redis直接解析存储,之前的例子也可以写成这样:

local friend_names = redis.call('smembers', KEYS[1])
local friends = {}
for i = 1, #friend_names do
    local friend_raw = redis.call('get', 'user:' .. friend_names[i])
    local friend_parsed = cjson.decode(friend_raw)
        if friend_parsed.gender == ARGV[1] then
            table.insert(friends, friend_raw)
    end
end
return friends

从指定的hash字段里获得性别可以替换成直接从好友数据里获得。(这会比较慢。)

因为Redis是单线程的,所有你不用担心你的Lua脚本会被别的Redis命令打断。

一个最值得一提的优势就是key不会在执行的时候过期。如果一个key出现在开始脚本时,他会在任何点出现,除非你删除它。

下一章我们会详细的介绍Redis的管理和配置,但是现在,简单的了解lua-time-limit定义了一个Lua脚本如许执行的时间。默认的是5秒。

 

总结

在这章我们介绍了Lua脚本。

Lua 可以在任何地方用到,但是谨慎的实现自己的功能,不要滥用。

Lua 就会使你的代码简洁,并且能提高效率。

Lua 脚本也可以像其他Reids命令一样做一些限制,如果可能尽可能的多练习。

 

 

参考推荐

Redis + Lua 脚本实现复合操作

Redis 核心知识图谱

Redis 数据结构底层实现

单机开启多个 Redis 实例

Redis 主从集群配置高可用技术方案

BloomFilter + Redis 大数据去重策略的实现

Python 操作 redis 接口函数

Python 操作 Redis 数据库的函数

Redis 双主备份实现

php-redis 各种函数中文手册

统计Redis中各种数据的大小

Redis 常用命令

Redis系列(2)—— 概述

Redis系列(3)—— 数据结构

Redis系列(4)—— 高级功能

Redis系列(5)—— 排序与订阅

Redis系列(6)—— Lua

Redis实例(8)—— 事务

Redis实例(10)—— 持久化

Redis实例(11)—— 虚拟内存

Redis实例(12)—— 管线

Redis实例(13)—— 服务器管理

Redis实例(14)—— 内存优化

 

Redis,MemCached,MongoDB概述