Redis和Lua


Lua简介

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis。

Redis中使用Lua的好处:

  • 减少网络开销,在 Lua 脚本中可以把多个命令放在同一个脚本中运行。
  • 原子操作,Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心会出现竞态条件。
  • 复用性,客户端发送的脚本会永远存储在 Redis 中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑。

Lua基本语法

Lua 程序代码一般是保存到一个以 lua 结尾的文件。

注释

(1) 单行注释:两个减号是单行注释

--

(2) 多行注释

--[[
 多行注释
 多行注释
 --]]

标示符

Lua 标示符用于定义一个变量,函数获取其他用户定义的项。标示符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上 0 个或多个字母,下划线,数字(0 到 9)。

最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。

Lua 不允许使用特殊字符如 @$, 和 % 来定义标示符。 Lua 是一个区分大小写的编程语言。因此在 Lua 中 Runoob 与 runoob 是两个不同的标示符。以下列出了一些正确的标示符:

mohd         zara      abc     move_name    a_123
myname50     _temp     j       a23b9        retVal

关键词

以下列出了 Lua 的保留关键词。保留关键字不能作为常量或变量或其他用户自定义标示符:

and、break、do、else、elseif、end、false、for、function、if、in、local、nil、not、or、repeat、return、then、true、until、while、goto。

一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于 Lua 内部全局变量。

全局变量

在默认情况下,变量总是认为是全局的。

全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。

如果你想删除一个全局变量,只需要将变量赋值为nil。

数据类型

Lua 是动态类型语言,变量不需要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。

Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。

数据类型描述
nil 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
boolean 包含两个值:false和true。
number 表示双精度类型的实浮点数
string 字符串由一对双引号或单引号来表示
function 由 C 或 Lua 编写的函数
userdata 表示任意存储在变量中的C数据结构
thread 表示执行的独立线路,用于执行协同程序
table Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。

lua循环

很多情况下我们需要做一些有规律性的重复操作,因此在程序中就需要重复执行某些语句。

(1) Lua 语言提供了以下几种循环处理方式

循环类型描述
while循环 在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。
for循环 重复执行指定语句,重复次数可在 for 语句中控制。
repeat...until 重复执行循环,直到 指定的条件为真时为止
循环嵌套 可以在循环内嵌套一个或多个循环语句(while do ... end;for ... do ... end;repeat ... until;)

(2) 循环控制语句

控制语句描述
break语句 退出当前循环或语句,并开始脚本执行紧接着的语句。
goto语句 将程序的控制点转移到一个标签处。

(3) 无限循环

在循环体中如果条件永远为 true 循环语句就会永远执行下去,以下以 while 循环为例:

while( true )
do
   print("循环将永远执行下去")
end

lua流程控制

Lua 编程语言流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。

控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true和非nil为真。

要注意的是Lua中 0 为 true:

--[ 0 为 true ]
if(0)
then
    print("0 为 true")
end

Lua 提供了以下控制结构语句:

语句描述
if 语句 if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。
if...else语句 if 语句 可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码。
if嵌套语句 你可以在if 或 else if中使用一个或多个 if 或 else if 语句 。

lua函数

Lua 提供了许多的内建函数,你可以很方便的在程序中调用它们,如print()函数可以将传入的参数打印在控制台上。

Lua 编程语言函数定义格式如下:

optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
    function_body
    return result_params_comma_separated
end

说明:

  • optional_function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local

  • function_name: 指定函数名称。

  • argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。

  • function_body: 函数体,函数中需要执行的代码语句块。

  • result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。

以下实例定义了函数 max(),参数为 num1, num2,用于比较两值的大小,并返回最大值:

--[[ 函数返回两个值的最大值 --]]
function max(num1, num2)

   if (num1 > num2) then
      result = num1;
   else
      result = num2;
   end

   return result; 
end
-- 调用函数
print("两值比较最大值为 ",max(10,4))
print("两值比较最大值为 ",max(5,6))

1、参数传递函数

Lua 中我们也可以将函数作为参数传递给函数,如下实例:

myprint = function(param)
   print("这是打印函数 -   ##",param,"##")
end

function add(num1,num2,functionPrint)
   result = num1 + num2
   -- 调用传递的函数参数
   functionPrint(result)
end
myprint(10)
-- myprint 函数作为参数传递
add(2,5,myprint)

2、多返回值

Lua函数中,在return后列出要返回的值的列表即可返回多值,如:

function maximum (a)
    local mi = 1             -- 最大值索引
    local m = a[mi]          -- 最大值
    for i,val in ipairs(a) do
       if val > m then
           mi = i
           m = val
       end
    end
    return m, mi
end

print(maximum({8,10,23,12,5}))

3、可变参数

Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 ... 表示函数有可变的参数。

如我们计算几个数的平均值:

function average(...)
   result = 0
   local arg={...}    --> arg 为一个表,局部变量
   for i,v in ipairs(arg) do
      result = result + v
   end
   print("总共传入 " .. #arg .. " 个数")
   return result/#arg
end

print("平均值为",average(10,5,3,4,5,6))

有时候我们可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:

function fwrite(fmt, ...)  ---> 固定的参数fmt
    return io.write(string.format(fmt, ...))     
end

fwrite("runoob\n")       --->fmt = "runoob", 没有变长参数。  
fwrite("%d%d\n", 1, 2)   --->fmt = "%d%d", 变长参数为 1 和 2

通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select('#', …)或者 select(n, …)

  • select('#', …) 返回可变参数的长度。
  • select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。

调用 select 时,必须传入一个固定实参 selector(选择开关) 和一系列变长参数。如果 selector 为数字 n,那么 select 返回参数列表中从索引 n 开始到结束位置的所有参数列表,否则只能为字符串 #,这样 select 返回变长参数的总数。

function f(...)
    a = select(3,...)  -->从第三个位置开始,变量 a 对应右边变量列表的第一个参数
    print (a)
    print (select(3,...)) -->打印所有列表参数
end

f(0,1,2,3,4,5) 

lua常用函数

1、一些字符串的函数

name="name is chinar,hello world.CSDN"

-- string.lower —— 字符串转:小写
print(string.lower(name))

-- string.upper —— 字符串转:大写
print(string.upper(name))

-- string.sub —— 字符串提取字符:从name中的第1个到第6个(算上空格)
print(string.sub(name,1,6))

-- string.find —— 字符串查找字符:从name中找字符串 hel,返回下标表示从几到几
print(string.find(name,'hel'))

-- .. —— 字符串拼接
print('fps://'..name)

(1) string.gsub(mainString,findString,replaceString,num)

在字符串中替换。

mainString 为要操作的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换),如:

string.gsub("aaaa","a","z",3);
zzza    3

(2) string.len(arg)

计算字符串长度。

(3) 字符与整数相互转换

-- 字符转换
-- 转换第一个字符
print(string.byte("Lua"))
-- 转换第三个字符
print(string.byte("Lua",3))
-- 转换末尾第一个字符
print(string.byte("Lua",-1))
-- 第二个字符
print(string.byte("Lua",2))
-- 转换末尾第二个字符
print(string.byte("Lua",-2))

-- 整数 ASCII 码转换为字符
print(string.char(97))

2、Table 表

-- 第一种表创建的方式
-- 表的声明,声明一个空表
MyTable={}
-- 表的赋值
MyTable[1]=33
MyTable[3]='chinar'
-- 表的赋值 下边两种是相同的,都表示键为:name.所以后边的hello world 会覆盖掉 chianr work
MyTable['name']='chianr work'
MyTable.name='hello world'
-- 输出值
print(MyTable[1],MyTable['name'],MyTable.name)

-- 第二种表创建的方式
-- 表声明,并直接赋值完成初始化。如果名字重复,会直接覆盖之前的表
MyTable={name='chinar',high=175,isFemale=false}
-- 输出MyTable中的isFemale为:false
print(MyTable.isFemale)


-- 第三种创建方式
-- 对应的键为下标,这种表由于全部都是数字,所以可以直接用for循环直接遍历
MyTable={66,777,8888,99999,000000,1}
print(MyTable[1])
-- 遍历表,#MyTable可以获取表的长度
for i=1,#(MyTable) do
    print(MyTable[i])
end
-- 遍历表,同时访问键值。可以用作遍历所有表,不是数字同样可以输出键值
MyTable={name='chinar',high=175,isFemale=false}
for i,v in pairs(MyTable) do
    print(i,v)
end
MyTable={'dd',66,777,8888,99999,000000,1}
-- table.concat()合并表中的字符串,值。输出一长串的字符串
print(table.concat(MyTable))

-- table.insert 在表中插入一个元素
table.insert(MyTable,4,"chianr")--相当于MyTable[4]="chianr"
print(MyTable[4])

-- 移除指定位置数据
print(MyTable[1])--当前第二个元素为dd
-- 移除第二个元素
table.remove(MyTable,1)
print(MyTable[1])--当前第二个元素为66

MyTable={66,777,8888,99999,000000,1}
-- table.sort 从小到大排列
table.sort(MyTable)
print(table.concat(MyTable))

-- table.unpack返回一个指定范围的数组
print(table.unpack(MyTable,2,3))

3、类的创建 和 函数的创建

-- 类的声明
People={}

-- 创建一个私有变量this来代替People,方便书写
local this=People

-- 类变量
People.hp=100
People.sp=1000

-- 第一种声明方法的操作
People.Fangfa=function( ... )
    print('chinar')
end

-- 第二种声明方法的操作 —— 这种较为常用
function People.Run( ... )
    this.Fangfa()--方法中调用已经声明的方法
    print(this.hp,"Run")
end

-- 调用方法
People.Run()

4、数学函数

-- 定义一个函数:Chinar 
-- 返回值为:参数a1+a2
function Chinar(a1,a2)
    return a1+a2
end

-- 定义变量fun接收函数Chianr的返回值
fun=Chinar(66,88)

-- 输出
print(fun)

-- 求绝对值
print(math.abs(-66))

-- 求最大数
print(math.max(1,4,555,3,66,999))

-- 求随机数,需要多些几个才会有不同的值
print(math.random())
print(math.random())

-- 求平方根
print(math.sqrt(9))

-- 其余的几乎都是求三角函数的夹角
print(math.sin(60))
print(math.cos(60))
print(math.tan(60))

lua运算符

运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。Lua提供了以下几种运算符类型:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 其他运算符

1、算数运算符

a = 21
b = 10
c = a + b
print("Line 1 - c 的值为 ", c )
c = a - b
print("Line 2 - c 的值为 ", c )
c = a * b
print("Line 3 - c 的值为 ", c )
c = a / b
print("Line 4 - c 的值为 ", c )
c = a % b
print("Line 5 - c 的值为 ", c )
c = a^2
print("Line 6 - c 的值为 ", c )
c = -a
print("Line 7 - c 的值为 ", c )

2、关系运算符

a = 21
b = 10

if( a == b )
then
   print("Line 1 - a 等于 b" )
else
   print("Line 1 - a 不等于 b" )
end

if( a ~= b )
then
   print("Line 2 - a 不等于 b" )
else
   print("Line 2 - a 等于 b" )
end

if ( a < b )
then
   print("Line 3 - a 小于 b" )
else
   print("Line 3 - a 大于等于 b" )
end

if ( a > b ) 
then
   print("Line 4 - a 大于 b" )
else
   print("Line 5 - a 小于等于 b" )
end

-- 修改 a 和 b 的值
a = 5
b = 20
if ( a <= b ) 
then
   print("Line 5 - a 小于等于  b" )
end

if ( b >= a ) 
then
   print("Line 6 - b 大于等于 a" )
end

3、逻辑运算符

a = true
b = true

if ( a and b )
then
   print("a and b - 条件为 true" )
end

if ( a or b )
then
   print("a or b - 条件为 true" )
end

print("---------分割线---------" )

-- 修改 a 和 b 的值
a = false
b = true

if ( a and b )
then
   print("a and b - 条件为 true" )
else
   print("a and b - 条件为 false" )
end

if ( not( a and b) )
then
   print("not( a and b) - 条件为 true" )
else
   print("not( a and b) - 条件为 false" )
end

4、其他运算符

a = "Hello "
b = "World"

print("连接字符串 a 和 b ", a..b )

print("b 字符串长度 ",#b )

print("字符串 Test 长度 ",#"Test" )

print("菜鸟教程网址长度 ",#"www.runoob.com" )

5、运算符优先级

从高到低:

^
not    - (unary)
*      /       %
+      -
..
<      >      <=     >=     ~=     ==
and
or

除了 ^ 和 .. 外所有的二元运算符都是左连接的。

a+i < b/2+1          <-->       (a+i) < ((b/2)+1)
5+x^2*8              <-->       5+((x^2)*8)
a < y and y <= z     <-->       (a < y) and (y <= z)
-x^2                 <-->       -(x^2)
x^y^z                <-->       x^(y^z)

示例:

a = 20
b = 10
c = 15
d = 5

e = (a + b) * c / d;-- ( 30 * 15 ) / 5
print("(a + b) * c / d 运算值为  :",e )

e = ((a + b) * c) / d; -- (30 * 15 ) / 5
print("((a + b) * c) / d 运算值为 :",e )

e = (a + b) * (c / d);-- (30) * (15/5)
print("(a + b) * (c / d) 运算值为 :",e )

e = a + (b * c) / d;  -- 20 + (150/5)
print("a + (b * c) / d 运算值为   :",e )

lua字符串

字符串或串(String)是由数字、字母、下划线组成的一串字符。

Lua 语言中字符串可以使用以下三种方式来表示:

  • 单引号间的一串字符。
  • 双引号间的一串字符。
  • [[ 与 ]] 间的一串字符。
string1 = "Lua"
print("\"字符串 1 是\"",string1)
string2 = 'runoob.com'
print("字符串 2 是",string2)

string3 = [["Lua 教程"]]
print("字符串 3 是",string3)

lua数组

Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。

(1) 一维数组

array = {"Lua", "Tutorial"}

for i= 0, 2 do
   print(array[i])
end

以上代码执行输出结果为:

nil
Lua
Tutorial

正如你所看到的,我们可以使用整数索引来访问数组元素,如果知道的索引没有值则返回nil。

在 Lua 索引值是以 1 为起始,但你也可以指定 0 开始。

lua迭代器

在 Lua 中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。

1、泛型 for 迭代器

泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。

泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:

for k, v in pairs(t) do
    print(k, v)
end

上面代码中,k, v为变量列表;pairs(t)为表达式列表。

array = {"Google", "Runoob"}

for key,value in ipairs(array) 
do
   print(key, value)
end

以上代码执行输出结果为:

Google
Runoob

以上实例中我们使用了 Lua 默认提供的迭代函数 ipairs。

示例:

array = {"Google", "Runoob"}

function elementIterator (collection)
   local index = 0
   local count = #collection
   -- 闭包函数
   return function ()
      index = index + 1
      if index <= count
      then
         --  返回迭代器的当前元素
         return collection[index]
      end
   end
end

for element in elementIterator(array)
do
   print(element)
end

lua Table(表)

table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。

Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。

Lua table 是不固定大小的,你可以根据自己需要进行扩容。

Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。

1、table(表)的构造

构造器是创建和初始化表的表达式。表是Lua特有的功能强大的东西。最简单的构造函数是{},用来创建一个空表。可以直接初始化数组:

-- 初始化表
mytable = {}

-- 指定值
mytable[1]= "Lua"

-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存

当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。

-- 简单的 table
mytable = {}
print("mytable 的类型是 ",type(mytable))

mytable[1]= "Lua"
mytable["wow"] = "修改前"
print("mytable 索引为 1 的元素是 ", mytable[1])
print("mytable 索引为 wow 的元素是 ", mytable["wow"])

-- alternatetable和mytable的是指同一个 table
alternatetable = mytable

print("alternatetable 索引为 1 的元素是 ", alternatetable[1])
print("mytable 索引为 wow 的元素是 ", alternatetable["wow"])

alternatetable["wow"] = "修改后"

print("mytable 索引为 wow 的元素是 ", mytable["wow"])

-- 释放变量
alternatetable = nil
print("alternatetable 是 ", alternatetable)

-- mytable 仍然可以访问
print("mytable 索引为 wow 的元素是 ", mytable["wow"])

mytable = nil
print("mytable 是 ", mytable)

2、table操作

(1) table连接

fruits = {"banana","orange","apple"}
-- 返回 table 连接后的字符串
print("连接后的字符串 ",table.concat(fruits))

-- 指定连接字符
print("连接后的字符串 ",table.concat(fruits,", "))

-- 指定索引来连接 table
print("连接后的字符串 ",table.concat(fruits,", ", 2,3))

(2) 插入和删除

fruits = {"banana","orange","apple"}

-- 在末尾插入
table.insert(fruits,"mango")
print("索引为 4 的元素为 ",fruits[4])

-- 在索引为 2 的键处插入
table.insert(fruits,2,"grapes")
print("索引为 2 的元素为 ",fruits[2])

print("最后一个元素为 ",fruits[5])
table.remove(fruits)
print("移除后最后一个元素为 ",fruits[5])

(3) table排序

fruits = {"banana","orange","apple","grapes"}
print("排序前")
for k,v in ipairs(fruits) do
        print(k,v)
end

table.sort(fruits)
print("排序后")
for k,v in ipairs(fruits) do
        print(k,v)
end

(4) table最大值

table.maxn 在 Lua5.2 之后该方法已经不存在了,我们定义了 table_maxn 方法来实现。

function table_maxn(t)
  local mn=nil;
  for k, v in pairs(t) do
    if(mn==nil) then
      mn=v
    end
    if mn < v then
      mn = v
    end
  end
  return mn
end
tbl = {[1] = 2, [2] = 6, [3] = 34, [26] =5}
print("tbl 最大值:", table_maxn(tbl))
print("tbl 长度 ", #tbl)

注意:当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。

可以使用以下方法来代替:

function table_leng(t)
  local leng=0
  for k, v in pairs(t) do
    leng=leng+1
  end
  return leng;
end

Redis + LUA 整合使用

从 Redis2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 LUA 脚本进行求值。(这种形式是不需要单独安装 LUA)。

EVAL命令

语法:

EVAL script numkeys key [key …] arg [arg …]

参数说明:

  • script: 参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。
  • numkeys: 用于指定键名参数的个数。
  • key [key ...]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
  • arg [arg ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。

示例:

redis 127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

其中 "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 是被求值的 Lua 脚本,数字 2 指定了键名参数的数量, key1 和 key2 是键名参数,分别使用 KEYS[1] 和 KEYS[2] 访问,而最后的 first 和 second 则是附加参数,可以通过 ARGV[1] 和 ARGV[2] 访问它们。

lua脚本中调用redis命令

在 Lua 脚本中,可以使用两个不同函数来执行 Redis 命令,它们分别是:

  • redis.call()
  • redis.pcall()

redis.call() 和 redis.pcall() 的唯一区别在于它们对错误处理的不同。

当 redis.call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因:

redis> lpush foo a
(integer) 1

redis> eval "return redis.call('get', 'foo')" 0
(error) ERR Error running script (call to f_282297a0228f48cd3fc6a55de6316f31422f5d17): ERR Operation against a key holding the wrong kind of value

和 redis.call() 不同, redis.pcall() 出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误:

redis 127.0.0.1:6379> EVAL "return redis.pcall('get', 'foo')" 0
(error) ERR Operation against a key holding the wrong kind of value

redis.call() 和 redis.pcall() 两个函数的参数可以是任何格式良好(well formed)的 Redis 命令:

> eval "return redis.call('set','foo','bar')" 0
OK

需要注意的是,上面这段脚本的确实现了将键 foo 的值设为 bar 的目的,但是,它违反了 EVAL 命令的语义,因为脚本里使用的所有键都应该由 KEYS 数组来传递,就像这样:

> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK

要求使用正确的形式来传递键(key)是有原因的,因为不仅仅是 EVAL 这个命令,所有的 Redis 命令,在执行之前都会被分析,借此来确定命令会对哪些键进行操作。

因此,对于 EVAL 命令来说,必须使用正确的形式来传递键,才能确保分析工作正确地执行。除此之外,使用正确的形式来传递键还有很多其他好处,它的一个特别重要的用途就是确保 Redis 集群可以将你的请求发送到正确的集群节点。不过,这条规矩并不是强制性的,从而使得用户有机会滥用(abuse) Redis 单实例配置(single instance configuration),代价是这样写出的脚本不能被 Redis 集群所兼容。

在 Lua 数据类型和 Redis 数据类型之间转换

当 Lua 通过 call() 或 pcall() 函数执行 Redis 命令的时候,命令的返回值会被转换成 Lua 数据结构。同样地,当 Lua 脚本在 Redis 内置的解释器里运行时,Lua 脚本的返回值也会被转换成 Redis 协议(protocol),然后由 EVAL 将值返回给客户端。

数据类型之间的转换遵循这样一个设计原则:如果将一个 Redis 值转换成 Lua 值,之后再将转换所得的 Lua 值转换回 Redis 值,那么这个转换所得的 Redis 值应该和最初时的 Redis 值一样。

换句话说, Lua 类型和 Redis 类型之间存在着一一对应的转换关系。

1、Redis 到 Lua 的转换表

  • Redis integer reply -> Lua number / Redis 整数转换成 Lua 数字
  • Redis bulk reply -> Lua string / Redis bulk 回复转换成 Lua 字符串
  • Redis multi bulk reply -> Lua table (may have other Redis data types nested) / Redis 多条 bulk 回复转换成 Lua 表,表内可能有其他别的 Redis 数据类型
  • Redis status reply -> Lua table with a single ok field containing the status / Redis 状态回复转换成 Lua 表,表内的 ok 域包含了状态信息
  • Redis error reply -> Lua table with a single err field containing the error / Redis 错误回复转换成 Lua 表,表内的 err 域包含了错误信息
  • Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type / Redis 的 Nil 回复和 Nil 多条回复转换成 Lua 的布尔值 false

2、Lua到Redis的转换表

  • Lua number -> Redis integer reply (the number is converted into an integer) / Lua 数字转换成 Redis 整数
  • Lua string -> Redis bulk reply / Lua 字符串转换成 Redis bulk 回复
  • Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any) / Lua 表(数组)转换成 Redis 多条 bulk 回复
  • Lua table with a single ok field -> Redis status reply / 一个带单个 ok 域的 Lua 表,转换成 Redis 状态回复
  • Lua table with a single err field -> Redis error reply / 一个带单个 err 域的 Lua 表,转换成 Redis 错误回复
  • Lua boolean false -> Redis Nil bulk reply. / Lua 的布尔值 false 转换成 Redis 的 Nil bulk 回复

从 Lua 转换到 Redis 有一条额外的规则,这条规则没有和它对应的从 Redis 转换到 Lua 的规则:

  • Lua boolean true -> Redis integer reply with value of 1. / Lua 布尔值 true 转换成 Redis 整数回复中的 1

还有下面两点需要重点注意:

  • lua中整数和浮点数之间没有什么区别。因此,我们始终Lua的数字转换成整数的回复,这样将舍去小数部分。如果你想从Lua返回一个浮点数,你应该将它作为一个字符串。

使用脚本散发 Redis 日志

在 Lua 脚本中,可以通过调用 redis.log 函数来写 Redis 日志(log):

redis.log(loglevel, message)

其中, message 参数是一个字符串,而 loglevel 参数可以是以下任意一个值:

  • redis.LOG_DEBUG
  • redis.LOG_VERBOSE
  • redis.LOG_NOTICE
  • redis.LOG_WARNING

上面的这些等级(level)和标准 Redis 日志的等级相对应。

对于脚本散发(emit)的日志,只有那些和当前 Redis 实例所设置的日志等级相同或更高级的日志才会被散发。

以下是一个日志示例:

redis.log(redis.LOG_WARNING, "Something is wrong with this script.")

执行上面的函数会产生这样的信息:

[32343] 22 Mar 15:21:39 # Something is wrong with this script.

案例学习

Redis的常用命令

一、redis keys命令

1、DEL key
DUMP key 
序列化给定的key并返回序列化的值

2、EXISTS key 
检查给定的key是否存在

3、EXPIRE key seconds 
为key设置过期时间

4、EXPIRE key timestamp 
用时间戳的方式给key设置过期时间

5、PEXPIRE key milliseconds 
设置key的过期时间以毫秒计

6、KEYS pattern 
查找所有符合给定模式的key

7、MOVE key db 
将当前数据库的key移动到数据库db当中

8、PERSIST key
移除key的过期时间,key将持久保存

9、PTTL key
以毫秒为单位返回key的剩余过期时间

10、TTL key
以秒为单位,返回给定key的剩余生存时间

11、RANDOMKEY
从当前数据库中随机返回一个key

12、RENAME key newkey
修改key的名称

13、RENAMENX key newkey
仅当newkey不存在时,将key改名为newkey

14、TYPE key
返回key所存储的值的类型


二、reids字符串命令

1、SET key value

2、GET key

3、GETRANGE key start end
返回key中字符串值的子字符

4、GETSET key value
将给定key的值设为value,并返回key的旧值

5、GETBIT KEY OFFSET
对key所储存的字符串值,获取指定偏移量上的位

6、MGET KEY1 KEY2
获取一个或者多个给定key的值

7、SETBIT KEY OFFSET VALUE
对key所是存储的字符串值,设置或清除指定偏移量上的位

8、SETEX key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。

9、SETNX key value
只有在 key 不存在时设置 key 的值。

10、SETRANGE key offset value
用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。

11、STRLEN key
返回 key 所储存的字符串值的长度。

12、MSET key value [key value ...]
同时设置一个或多个 key-value 对。

13、MSETNX key value [key value ...] 
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

14、PSETEX key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。

15、INCR key
将 key 中储存的数字值增一。

16、INCRBY key increment
将 key 所储存的值加上给定的增量值(increment) 。

17、INCRBYFLOAT key increment
将 key 所储存的值加上给定的浮点增量值(increment) 。

18、DECR key
将 key 中储存的数字值减一。

19、DECRBY key decrement
key 所储存的值减去给定的减量值(decrement) 。

20、APPEND key value
如果 key 已经存在并且是一个字符串, APPEND 命令将 指定value 追加到改 key 原来的值(value)的末尾。


三、Redis hash 命令

1    HDEL key field1 [field2] 
删除一个或多个哈希表字段

2    HEXISTS key field 
查看哈希表 key 中,指定的字段是否存在。

3    HGET key field 
获取存储在哈希表中指定字段的值。

4    HGETALL key 
获取在哈希表中指定 key 的所有字段和值

5    HINCRBY key field increment 
为哈希表 key 中的指定字段的整数值加上增量 increment 。

6    HINCRBYFLOAT key field increment 
为哈希表 key 中的指定字段的浮点数值加上增量 increment 。

7    HKEYS key 
获取所有哈希表中的字段

8    HLEN key 
获取哈希表中字段的数量

9    HMGET key field1 [field2] 
获取所有给定字段的值

10    HMSET key field1 value1 [field2 value2 ] 
同时将多个 field-value (域-值)对设置到哈希表 key 中。

11    HSET key field value 
将哈希表 key 中的字段 field 的值设为 value 。

12    HSETNX key field value 
只有在字段 field 不存在时,设置哈希表字段的值。

13    HVALS key 
获取哈希表中所有值

14    HSCAN key cursor [MATCH pattern] [COUNT count] 
迭代哈希表中的键值对。


四、Redis 列表命令

1    BLPOP key1 [key2 ] timeout 
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

2    BRPOP key1 [key2 ] timeout 
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

3    BRPOPLPUSH source destination timeout 
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

4    LINDEX key index 
通过索引获取列表中的元素

5    LINSERT key BEFORE|AFTER pivot value 
在列表的元素前或者后插入元素

6    LLEN key 
获取列表长度

7    LPOP key 
移出并获取列表的第一个元素

8    LPUSH key value1 [value2] 
将一个或多个值插入到列表头部

9    LPUSHX key value 
将一个值插入到已存在的列表头部

10    LRANGE key start stop 
获取列表指定范围内的元素

11    LREM key count value 
移除列表元素

12    LSET key index value 
通过索引设置列表元素的值

13    LTRIM key start stop 
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

14    RPOP key 
移除并获取列表最后一个元素

15    RPOPLPUSH source destination 
移除列表的最后一个元素,并将该元素添加到另一个列表并返回

16    RPUSH key value1 [value2] 
在列表中添加一个或多个值

17    RPUSHX key value 
为已存在的列表添加值

五、Redis 集合命令

1    SADD key member1 [member2] 
向集合添加一个或多个成员

2    SCARD key 
获取集合的成员数

3    SDIFF key1 [key2] 
返回给定所有集合的差集

4    SDIFFSTORE destination key1 [key2] 
返回给定所有集合的差集并存储在 destination 中

5    SINTER key1 [key2] 
返回给定所有集合的交集

6    SINTERSTORE destination key1 [key2] 
返回给定所有集合的交集并存储在 destination 中

7    SISMEMBER key member 
判断 member 元素是否是集合 key 的成员

8    SMEMBERS key 
返回集合中的所有成员

9    SMOVE source destination member 
将 member 元素从 source 集合移动到 destination 集合

10    SPOP key 
移除并返回集合中的一个随机元素

11    SRANDMEMBER key [count] 
返回集合中一个或多个随机数

12    SREM key member1 [member2] 
移除集合中一个或多个成员

13    SUNION key1 [key2] 
返回所有给定集合的并集

14    SUNIONSTORE destination key1 [key2] 
所有给定集合的并集存储在 destination 集合中

15    SSCAN key cursor [MATCH pattern] [COUNT count] 
迭代集合中的元素


六、Redis 有序集合命令

1    ZADD key score1 member1 [score2 member2] 
向有序集合添加一个或多个成员,或者更新已存在成员的分数

2    ZCARD key 
获取有序集合的成员数

3    ZCOUNT key min max 
计算在有序集合中指定区间分数的成员数

4    ZINCRBY key increment member 
有序集合中对指定成员的分数加上增量 increment

5    ZINTERSTORE destination numkeys key [key ...] 
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中

6    ZLEXCOUNT key min max 
在有序集合中计算指定字典区间内成员数量

7    ZRANGE key start stop [WITHSCORES] 
通过索引区间返回有序集合成指定区间内的成员

8    ZRANGEBYLEX key min max [LIMIT offset count] 
通过字典区间返回有序集合的成员

9    ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 
通过分数返回有序集合指定区间内的成员

10    ZRANK key member 
返回有序集合中指定成员的索引

11    ZREM key member [member ...] 
移除有序集合中的一个或多个成员

12    ZREMRANGEBYLEX key min max 
移除有序集合中给定的字典区间的所有成员

13    ZREMRANGEBYRANK key start stop 
移除有序集合中给定的排名区间的所有成员

14    ZREMRANGEBYSCORE key min max 
移除有序集合中给定的分数区间的所有成员

15    ZREVRANGE key start stop [WITHSCORES] 
返回有序集中指定区间内的成员,通过索引,分数从高到底

16    ZREVRANGEBYSCORE key max min [WITHSCORES] 
返回有序集中指定分数区间内的成员,分数从高到低排序

17    ZREVRANK key member 
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序

18    ZSCORE key member 
返回有序集中,成员的分数值

19    ZUNIONSTORE destination numkeys key [key ...] 
计算给定的一个或多个有序集的并集,并存储在新的 key 中

20    ZSCAN key cursor [MATCH pattern] [COUNT count] 
迭代有序集合中的元素(包括元素成员和元素分值)


七、Redis 发布订阅命令

1    PSUBSCRIBE pattern [pattern ...] 
订阅一个或多个符合给定模式的频道。

2    PUBSUB subcommand [argument [argument ...]] 
查看订阅与发布系统状态。

3    PUBLISH channel message 
将信息发送到指定的频道。

4    PUNSUBSCRIBE [pattern [pattern ...]] 
退订所有给定模式的频道。

5    SUBSCRIBE channel [channel ...] 
订阅给定的一个或多个频道的信息。

6    UNSUBSCRIBE [channel [channel ...]] 
指退订给定的频道。

示例:
redis 127.0.0.1:6379> SUBSCRIBE redisChat

Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1

现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收到消息。
redis 127.0.0.1:6379> PUBLISH redisChat "Redis is a great caching technique"

(integer) 1

# 订阅者的客户端会显示如下消息
1) "message"
2) "redisChat"
3) "Redis is a great caching technique"


八、Redis 事务命令

1    DISCARD 
取消事务,放弃执行事务块内的所有命令。

2    EXEC 
执行所有事务块内的命令。

3    MULTI 
标记一个事务块的开始。

4    UNWATCH 
取消 WATCH 命令对所有 key 的监视。

5    WATCH key [key ...] 
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。