13-应用实例
6.3.4 应用实例
本节会结合几个编程语言的Redis客户端,通过实例介绍在应用中如何使用脚本功能。
1.同时获取多个散列类型键的键值
假设有若干个用户的ID,现在需要获得这些用户的资料。用户的资料使用散列类型键存储,所以我们可以编写一个可以一次性对多个键执行 HGETALL 命令的脚本。
Predis将脚本功能抽象成了Redis的命令,我们可以通过脚本定义自己的命令并像调用其他命令一样调用我们自己写的脚本。首先我们定义 HMGETALL ( M 表示多个的意思)类:
< ?php
class HMGetAll extends Predis\Command\ScriptedCommand
{
// 定义前多少个参数会被作为KEYS变量
// false表示所有的参数
public function getKeysCount()
{
return false;
}
// 返回脚本内容
public function getScript()
{
return
< < < LUA
local result = {}
for i, v in ipairs(KEYS) do
result[i] = redis.call('HGETALL', v)
end
return result
LUA;
}
}
$ client = new Predis\Client();
// 定义hmgetall命令
$ client->getProfile()->defineCommand('hmgetall', 'HMGetAll');
// 执行hmgetall命令
$ value = $ client->hmgetall('user:1', 'user:2', 'user:3');
2.获得并删除有序集合中分数最小的元素
列表类型提供了 LPOP 和 RPOP 两个命令实现弹出操作,然而有序集合类型却没有相应命令。不使用脚本功能的话必须借助事务来实现,比较烦琐,在Redis的官方文档中有这样的例子:
WATCH zset
$element = ZRANGE zset 0 0
MULTI ZREM zset $element
EXEC
虽然代码不算长,但还要考虑事务执行失败(即执行 WATCH 命令后其他客户端修改了 zset 键)时必须重新执行。
redis-py 客户端同样对 EVAL 和 EVALSHA 两个命令进行了抽象。首先使用 register_script 函数建立一个脚本对象,然后就可以使用该对象发送脚本命令了。代码如下:
r = redis.StrictRedis()
lua = """
local element = redis.call('ZRANGE', KEYS[1], 0, 0)[1]
if element then
redis.call('ZREM', KEYS[1], element)
end
return element
"""
ztop = r.register_script(lua)
# 执行我们自己定义的ZTOP命令并打印出结果
print ztop(keys=['zset'])
3.处理JSON
3.2节介绍字符串类型时曾提到可以将对象JSON化后存入字符串类型键中。如果需要对这些对象进行计算,可以使用脚本在服务端完成计算后再返回,既节省了网络带宽,又保证了操作的原子性。
下面介绍使用脚本功能实现统计多个学生的课程分数总和。首先我们定义一个学生类,包括姓名和该学生的所有课程分数:
// 学生类的构造函数,参数是学生姓名
function Student(name) {
this.name = name;
this.courses = {};
}
// 添加一个课程,参数为课程名和分数
Student.prototype.addCourse = function(name, score) {
this.courses[name] = score;
}
而后我们创建两个学生实例并为其添加课程:
// 创建学生Bob,为其添加两门课程的成绩
var bob = new Student('Bob');
bob.addCourse('Mathematics', 80);
bob.addCourse('Literature', 95);
// 创建学生Jeff,为其添加两门课程的成绩
var jeff = new Student('Jeff');
jeff.addCourse('Mathematics', 85);
jeff.addCourse('Chemistry', 70);
连接Redis,将两个实例JSON序列化后存入Redis中:
var redis = require("redis");
var client = redis.createClient();
// 将两个对象JSON序列化后存入数据库中
client.mset(
'user:1', JSON.stringify(bob),
'user:2', JSON.stringify(jeff)
);
现在开始进行最有趣的环节,即编写Lua脚本计算所有学生的所有课程的分数总和:
var lua = " \
local sum = 0 \
local users = redis.call('mget', unpack(KEYS)) \
for _, user in ipairs(users) do \
local courses = cjson.decode(user).courses \
for _, score in pairs(courses) do \
sum = sum + score \
end \
end \
return sum \
";
接着调用node_redis的 eval 函数执行脚本,此函数会先计算脚本的SHA1摘要并尝试使用 EVALSHA 命令调用,如果失败就使用 EVAL 命令,这一过程对我们是透明的:
client.eval(lua, 2, 'user:1', 'user:2', function (err, sum) {
// 结果是330
console.log(sum);
});
提示
因为在脚本中我们使用了
unpack函数将KEYS表展开,所以执行脚本时我们可以传入任意数量的键参数,这是一个很有用的小技巧。