前面已经讲过了redis的基础和部分进阶,链接如下:
redis五种数据类型及相关命令
redis进阶:事务|过期|缓存|排序|空间节省
今天我们讲讲redis的脚本,将会让我们更加高效的使用redis
redis在2.6版本推出了脚本功能,允许开发者使用Lua语言编写脚本传到redis中执行,在使用脚本之前,我们首先需要了解为什么要使用脚本,使用脚本的好处主要包括以下三点:
Lua语言简介:Lua是一个高效的轻量级脚本语言,能够方便的嵌入到其他语言中,使得程序的升级和扩展变得更容易,redis使用Lua5.1版本,所以以下语法基于此版本,且着重介绍编写redis脚本会用到的部分
Lua是一个动态类型语言,一个变量可以存储任何类型的值,编写redis脚本时会用到的类型如下:
Lua的变量分为全局变量和局部变量,全局变量无需声明就可以使用,默认值是 nil
变量名必须是非数字开头,只能包含字母,数字和下划线,区分大小写,且不能与Lua的保留关键字相同
全局变量的声明:
a = 1 --为全局变量a赋值
print(b) --无需声明即可使用,默认值是 nil
a = nil --删除全局变量a的方法是赋值 nil ,全局变量没有声明和未声明之分,只有 非nil 和 nil 的区别
在redis脚本中不能使用全局变量,只允许使用局部变量以防止脚本之间相互影响
局部变量的声明:
local c --声明一个局部变量 c ,默认值是 nil
local d = 1 --声明一个局部变量 d 并赋值为 1
local e, f --可以同时声明多个局部变量
存储函数的局部变量声明:
local say_hi = function ()
print 'hi'
end
作用域:
局部变量的作用域从声明开始到所在层的语句块末尾,比如:
local x = 10
if true then
local x = x + 1
print(x)
do
local x = x + 1
pring(x)
end
print(x)
end
print(x)
//打印结果为:
11
12
11
10
-- 这是一个单行注释,以"--"开始,行尾结束,一般习惯在"--"后面跟上一个空格
--[[
这是一个多行注释
以"--[["开始, 到"]]"结束
]]
Lua支持多重赋值,比如:
local a, b = 1, 2 -- a的值是1, b的值是2
local c, d = 1, 2, 3 -- c的值是1, d的值是2, 3被舍弃了
local e, f = 1 -- e的值是1, f的值是nil
执行多重赋值前,Lua会先计算所有表达式的值,比如:
local a = {1, 2, 3}
local i = 1
i, a[i] = i+1, 5 -- 等价于: i, a[1] = 2, 5
-- 结果: i的值为2, a则为{5, 2, 3}
-- lua表类型索引是从1开始的
Lua有下面5类操作符:
print(1 and 5) -- 5
print(1 or 5) -- 1
print(not 0) -- false
print('' or 1) -- ''
print('this price is' .. 25) -- 'this price is 25'
print(#'hello') -- 5
运算符优先级如下,依次降低:
^
not #
* / %
+ -
..
< > <= >= ~= ==
and
or
if 条件表达式 then
语句块
elseif 条件表达式 then
语句块
else
语句块
end
Lua每个语句后面都可以;结尾,但一般都会省略
也不强制要求缩进,所有代码都可以写在同一行,但为了可读性,编写时一定要注意缩进
Lua支持while,repeat和for循环
while 条件表达式 do
语句块
end
repeat
语句块
until 条件表达式
for 变量 = 初值, 终值, 步长 do
语句块
end
for循环步长可以省略,默认为1,如计算1-100的和:
--其中i虽然没用local声明,但i是局部变量
local sum = 0
for i = 1, 100 do
sum = sum + i
end
表是Lua唯一的数据结构,可以理解为关联数组,任何类型的值(除了空类型)都可以作为表的索引
Lua约定数组的索引是从1开始的,而不是0
表的定义:
-- 第一种,类似于java Map
a = {} -- 将变量a赋值一个空表
a['field'] = 'value' -- 将field字段赋值value
print(a.field) --打印结果为 'value'
-- 或者这样定义:
people = {
name = 'Bob'
age = 20
}
print(people.name) -- 打印结果为: 'Bob'
-- 遍历非数组的表值:
for index, value in pairs(people) do
print(index)
print(value)
end
-- 打印结果:
name
Bob
age
20
-- 第二种,类似于java List
a = {}
a[1] = 'Bob'
a[1] = 'Jeff'
-- 或者:
a = {'Bob', 'Jeff'}
print(a[1]) --打印结果为: 'Bob'
-- 遍历方式:
for index, value in ipairs(a) do
print(index) --迭代数组 a 的索引
print(value) --迭代数组 a 的值
end
-- 或者:
for i = 1,#a do
print(i)
print(a[i])
end
-- 打印结果都为:
1
Bob
2
Jeff
pairs于ipairs都是Lua的迭代器,区别在于前者会遍历所有值不为nil的索引,而后者只会从索引1开始递增遍历到最后一个值不为nil的整数索引
函数的定义:
local 局部变量名 = function (参数列表)
函数体
end
-- 简化版定义:
local function 局部变量名 (参数列表)
函数体
end
-- 例:
local function square (num)
return num * num
end
-- 可变参数个数,将最后一个形参写为: ... ,比如希望传入若干参数计算这些数的平方:
local function square (...)
loca argv = {...}
for i = 1, #argv do
argv[i] = argv[i] * argv[i]
end
return unpack(argv) --unpack函数用来返回表中元素,相当于return argv[1], argv[2], argv[3]
end
a, b, c = square(1, 2, 3)
print(a)
print(b)
print(c)
-- 打印结果:
1
4
9
Lua中return和break语句必须是语句块的最后一条语句,即后面只能是end,else或util,如果需要在语句块中间使用,必须人为的使用do和end将其包围
将脚本存为 ratelimiting.lua
在命令行输入 redis-cli --eval /path/to/ratelimiting.lua rate.limiting:127.0.0.1 , 10 3
参数说明:
tips: , 两边的空格不能省略,否则报错
redis.call('set', 'foo', 'bar')
local value = redis.call('get', 'foo') --value的值为bar
redis.call函数会将5种类型的返回值转换为Lua对应数据类型(空结果特殊,对应Lua的false),具体对应关系:
还提供了redis.pcall函数,功能与redis.call相同,唯一区别是当命令出错时,redis.pcall记录错误继续执行,redis.call直接返回错误,不会继续执行
脚本中使用return语句将值返回给客户端,如果没有执行return语句则默认返回nil,同样redis会将脚本返回值的Lua数据类型转换成redis的返回值类型(Lua的false特殊,会被转换成空结果)
EVAL命令
格式: EVAL 脚本内容 key参数个数 [key…] [arg…]
key参数个数不能省略,无参则设为0
例如实现SET命令:
脚本内容:
return reids.call(‘SET’, KEYS[1], ARGV[1])
现在打开redis-cli执行脚本:
redis> EVAL "return reids.call('SET', KEYS[1], ARGV[1])" 1 foo bar
OK
redis> GET foo
"bar"
其中要读写的键名应该作为key参数,其他的作为arg参数,否则无法兼容集群
EVALSHA命令
如果脚本较长,每次传整个脚本占用较多带宽,则使用该命令
执行EVAL命令时redis会计算脚本的SHA1摘要并记录在脚本缓存中,执行EVALSHA命令时redis会根据提供的摘要从缓存中查找对应脚本内容,如果找到就执行,否则返回"NOSCRIPT No matching script.Please use EVAL"
用法与EVAL一样,只不过将脚本内容替换成脚本内容的SHA1摘要
一般使用该命令流程为:
虽然麻烦,但是很多编程语言的redis客户端封装了该操作
redis执行脚本期间不会执行其他命令,如果脚本执行时间过长将导致redis无法提供服务,所以可以通过配置 lua-time-limit 参数限制脚本运行的最长时间,默认5秒,
超过这个时间后redis将开始接收其他命令但不执行,而是返回busy错误,
此时redis实际会执行的只有两个命令: SCRIPT KILL 和 SHUTDOWN NOSAVE,
如果脚本已经对reids数据进行了修改,SCRIPT KILL会返回错误,无法终止脚本,此时只能使用SHUTDOWN NOSAVE命令,但该命令不会做持久化操作,这意味着发生在上一次快照后的数据库修改都会丢失