Redis cli两种运行方式

redis cli两种运行方式:交互式、命令式

Redis Lua脚本好处

Lua脚本是Redis 2.6之后引入的

  1. 批量执行命令
  2. 原子性
  3. 操作集合的复用

Lua脚本实现原子性的原理,就是执行一个Lua脚本的时候,里面有多个命令,这个时候会阻塞其它任何命令的执行和其它任何客户端的请求,也就是说,执行Lua的时候,只会执行Lua里的命令,以此来达到Lua里的多条命令成为一个整体来执行。

Redis执行Lua的两种方式

一种是连接客户端,交互式执行Lua,一种是编写Lua脚本文件,用命令行的方式执行。
有很多坑,先了解一下
1、用交互式方式(redis-cli进入客户端再执行命令)执行eval命令的时候,后面不能跟lua脚本(只能跟Lua语言),会报错如下:

# test2.lua 0: test2.lua脚本路径 0参数个数
127.0.0.1:6379> eval test2.lua 0
(error) ERR Error compiling script (new function): user_script:1: '=' expected near '<eof>'

2、用命令式方式(redis-cli后面跟执行的命令)执行命令的时候,如果你的Redis设置了密码,在连接的时候会输出认证提示

# -r表示重复执行;这里连接认证之后执行3次ping命令
[pdx_haokai@VM-0-3-centos bin]$ redis-cli -a Pass9612 -r 3 ping
# 可以看到会有Warning: Using a password with '-a' or '-u'输出
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
PONG
PONG
PONG

这种输出会影响Lua脚本取值,但不影响正常运行
需要redis输入密码时去除提示Warning: Using a password提示,拼接如下命令可以去除提示

2>/dev/null

在这里插入图片描述

1.交互式执行Lua

Redis客户端执行Lua脚本命令

EVAL script numkeys [key [key ...]] [arg [arg ...]]
  • EVAL代表执行Lua语言的命令
  • script代表Lua脚本内容,内容引号引起来,建议用双引号
  • numkeys表示参数中有多少个key,需要注意Redis中key是从1开始的,如果没有key的参数,写0
  • [key [key …]]是key作为参数传递给Lua语言,也可以不填,但是需要[key [key …]]和numkeys的个数对应起来
  • [arg [arg …]]这些参数传递给Lua语言,它们可填可不填
# "return '2234'"是Lua脚本内容,numkeys为0
127.0.0.1:6379> eval "return '2234'" 0
"2234"
127.0.0.1:6379>

Lua脚本中怎么执行Redis命令

Lua的使用,就是为了能够执行Redis命令
通过redis.call来执行Redis命令

redis.call(command,key[param1,param2,...])
  • command是Redis命令,包括set、get、del等
  • key是被操作的键
  • key[param1,param2,…]代表给key的参数
# --eval:用于执行lua脚本
127.0.0.1:6379> eval "return redis.call('set','key001','567')" 0
OK
127.0.0.1:6379> get key001
"567"
# KEYS[1],ARGV[1]中KEYS和ARGV是形参名,大写不能改
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 key002 789
OK
127.0.0.1:6379> get key002
"789"
# KEYS[1],ARGV[1]中KEYS和ARGV是形参名,KEYS改为小写,直接不识别参数
127.0.0.1:6379> eval "return redis.call('set',keys[1],ARGV[1])" 1 key002 710
(error) ERR Error running script (call to f_a9e55ca6c2982f8ce741628e9c3df21db06f4edb): @enable_strict_lua:15: user_script:1: Script attempted to access unexisting global variable 'keys'

这里需要注意下,如果用交互式方式(redis-cli进入客户端再执行命令)执行eval命令的时候,后面不能跟lua脚本,是不识别的,会报错如下:

127.0.0.1:6379> eval test2.lua 0
(error) ERR Error compiling script (new function): user_script:1: '=' expected near '<eof>'

2.命令式执行Lua

Lua脚本文件注释编写

在Lua程序中,有两种注释,单行注释和多行注释

1.单行注释
单行注释可以注释整行或者一行中的一部分。他一般不用于连续多行的注释文本,当然,和其他语言一样,也可以用来注释掉多行连续的代码,例如:

-- 注释方式1
if x > 1 then
    return true;  -- 注释
else
    return false; -- 注释
end

-- 注释方式2
-- if x > 1 then
    -- return true;
--else
    -- return false;
-- end

在以上示例中,注释方式2是不规范的,如果要注释多行,尽量的采用多行注释。

2.多行注释
多行注释一般用于连续多行注释,当然也可以用于单行注释,例如:

--[[
if x > 1 then
    -- 注释1
else
    -- 注释2
end
--]]

Lua的多行注释是可以嵌套的,这是其他语言没有的优点。

Lua脚本文件编写

Lua语言中执行Redis命令,用起来不是很方便,所以都会使用去执行Lua脚本文件

# 编写操作Redis命令
redis.call(command,key[param1,param2,...])

# 调用Lua脚本
redis-cli --eval 脚本名称 参数个数 参数1 参数2 ......

新建一个测试脚本:test.lua

vim test.lua

内容如下

--[[
多行注释:
这是一个测试脚本
--]]
redis.call('set','key1','123')--放入一个数字字符串
redis.call('incryby','key2','1024')--数字字符串增加1024
return redis.call('get','key2')

命令式执行简单Lua脚本文件

以命令式执行Lua脚本文件,交互式只不能执行Lua脚本文件(不识别文件的)。

redis-cli -a Pass9612 2>/dev/null --eval ../../lua/test.lua
  • -a:如配置了密码,可用a选项,无密码可忽略
  • 2>/dev/null:输入密码时去除提示Warning: Using a password提示,方便取值查看,无密码可忽略
  • –eval 你的lua脚本文件路径

在这里插入图片描述

Lua脚本案例

案例会用到tonumber函数

tonumber函数会尝试将它的参数转换为数字

如果参数已经是一个数字或者是一个可以转换成数字的字符串,那么这个函数就会返回转换后的数值,否则,返回nil(表示转换失败)。

这个函数有一个额外的参数base可用来指定参数的进制:

(1)默认参数值是10

(2)参数的取值范围是[2, 36]

(3)当参数值超过10时,使用A代表10(大小写都可以),B代表11,以此类推最后Z代表35

带参数的:Lua脚本实现IP限流

每个用户在X秒内只能访问Y次
需要用到三个参数

  • KEYS:key,IP限流,则key需要包含IP去区分
  • ARGV[1]:第一个参数,X,用来设置过期时间
  • ARGV[2]:第二个参数,Y,控制规定时间内访问次数

新建ip.lua文件,内容如下

--[[
1.Lua 中的变量全是全局变量,无论语句块或是函数里,除非用 local 显式声明为局部变量,变量默认值均为nil
2.使用local创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。
(代码块:指的是一个控制结构内,一个函数体,或者一个chunk(变量被声明的那个文件或者文本串))

尽可能使用局部变量,有两个好处
a.避免命名冲突
b.访问速度更快(原因是local变量是存放在lua的堆栈里面的是array操作,而全局变量是存放在_G中的table中,效率不及堆栈)
--]]
local num=redis.call('incr',KEYS[1])-- 计数+1,key不存在会新建
if tonumber(num)==1 then
        --第一次访问,用第一个参数设置过期时间
        redis.call('expire',KEYS[1],ARGV[1])
        return 1
elseif tonumber(num)>tonumber(ARGV[2]) then
        --不是第一次访问,跟第二个参数进行比较,是否超过限制
        return 0 --超过限制
else
        return 1 --没有超过限制
end

命令方式执行Lua脚本ip.lua,10s中访问最多6次,appname_test1:ip:192.168.0.1是模拟的key

# 空格的写法需要注意(这是固定语法): key后面+空格+逗号+空格+参数1+空格+参数2
# 我redis有密码,用-a和2>/dev/null屏蔽密码提示
redis-cli -a Pass9612 2>/dev/null --eval ../../lua/ip.lua appname_test1:ip:192.168.0.1 , 10 6

在这里插入图片描述

不带参数的:Lua脚本实现自乘

Redis中有自增,但没有自乘。
传入key、arg1。对key乘以arg1,得到乘以的结果。

local curVal = redis.call('get',KEYS[1])
if curVal == false then
        --根据key获取当前value,没有key则为0
        curVal = 0
else
        curVal = tonumber(curVal)
end
curVal = curVal*tonumber(ARGV[1])
redis.call('set',KEYS[1],curVal)
return curVal

需要key有值,否则结果都是0
在这里插入图片描述

Redis缓存Lua脚本

eval "local curVal = redis.call('get',KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal*tonumber(ARGV[1]) ;redis.call('set',KEYS[1],curVal); return curVal" 1 num 2

在这里插入图片描述

以客户端执行乘法脚本为例,在redis客户端执行Lua语言,拼接了很多命令,如果这样的一长执行串命令发给服务端,那么网络通信就会产生比较大的开销,所以Redis支持在服务端直接缓存一部分脚本的内容(它是用脚本生成了一段摘要,服务端可以根据这个摘要去执行脚本内容)。

你可以把Lua脚本里的内容提取出来,转成一行,使用script load进行脚本内容的缓存,注意执行命令时单双引号,脚本中的’get’和命令load "local…"的单双引号不要重复,否则执行会报Invalid argument(s)

script load "local curVal = redis.call('get',KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal*tonumber(ARGV[1]) ;redis.call('set',KEYS[1],curVal); return curVal"
"5d134cb368cf22d88c9e0130cbae1775a22cf348"

在这里插入图片描述
需要key有值,否则结果都是0
在这里插入图片描述

Redis Lua原子性验证

Lua是保证原子性的,它的执行是排他的,Lua中的命令是一个整体,没执行完毕其它命令都将阻塞。

如果Lua脚本执行很慢,那么其它客户端执行命令都会陷入阻塞中,等待Lua脚本内容执行完毕。

客户端执行一个死循环Lua,不做任何值的修改

eval "while (true) do end" 0

在这里插入图片描述
其他客户端再执行命令,提示Redis正在忙于执行脚本,你只能通过SCRIPT KILL或者SHUTDOWN NOSAVE让这个脚本不再执行。
在这里插入图片描述

# 提示Redis正在忙于执行脚本,你只能通过SCRIPT KILL或者SHUTDOWN NOSAVE让这个脚本不再执行
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE

由于没有任何值的修改,因此可以直接SCRIPT KILL
在这里插入图片描述

客户端执行一个死循环Lua,涉及操作key

eval "redis.call('set','key3','333') while(true) do end" 0

在这里插入图片描述
可以看到SCRIPT KILL已经不可以了,因为你有对key进行操作
在这里插入图片描述

# 对不起,脚本已经对数据集执行了写命令。您可以等待脚本终止,或者使用SHUTDOWN NOSAVE命令强行终止服务器
UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.

SHUTDOWN NOSAVE是不保存当前的操作并强制Redis停机(正常停机是SHUTDOWN ),这种操作是由破坏性的,会导致一部分内容丢失。

SHUTDOWN NOSAVE之后,重新启动Redis,查看Lua中操作的key,并没有被保存
在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐