Redis之Lua脚本介绍

Lua是一种轻量级脚本语言,它是用 C 语言编写的,跟数据的存储过程有点类似。

使用 Lua 脚本来执行 Redis 命令的好处

1、一次发送多个命令,减少网络开销。
2、Redis 会将整个脚本作为一个整体执行,不会被其他请求打断,保持原子性。
3、对于复杂的组合命令,我们可以放在文件中,可以实现程序之间的命令集复用。

在Redis 中调用Lua 脚本

使用 eval 方法,语法格式:

redis> eval lua-script key-num [key1 key2 key3 ....] [value1 value2 value3 ....]
  • eval 代表执行 Lua 语言的命令。
  • lua-script 代表 Lua 语言脚本内容。
  • key-num 表示参数中有多少个 key,需要注意的是 Redis 中 key 是从 1 开始的,如果没有 key 的参数,那么写 0。
  • [key1 key2 key3…]是 key 作为参数传递给 Lua 语言,也可以不填,但是需要和 key-num 的个数对应起来。
  • [value1 value2 value3 ….]这些参数传递给 Lua 语言,它们是可填可不填的。

示例,返回一个字符串,0 个参数:
redis> eval "return 'Hello World'" 0

在Lua 脚本中调用 Redis 

使用 redis.call(command, key [param1, param2…])进行操作。语法格式:

redis> eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value
  • command 是命令,包括 set、get、del 等
  • key 是被操作的键
  • param1,param2…代表给 key 的参数

注意:跟 Java 不一样,定义只有形参,调用只有实参。Lua 是在调用时用 key 表示形参,argv 表示参数值(实参)。

 

设置键值对

在 Redis 中调用 Lua 脚本执行 Redis 命令

redis> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 vincent 9527
redis> get vincent

这个命令等价于 set vincent 9527

在 redis-cli 中直接写 Lua 脚本不够方便,也不能实现编辑和复用,通常我们会把脚本放在文件里面,然后执行这个文件

 

在 Redis 中调用Lua 脚本文件中的命令 , 操作Redis

创建 Lua 脚本文件:

cd /usr/local/soft/redis5.0.5/src
vim vincent.lua

Lua 脚本内容,先设置,再取值:

redis.call('set','vincent','lua666')
return redis.call('get','vincent')

在 Redis 客户端中调用 Lua 脚本

cd /usr/local/soft/redis5.0.5/src
redis-cli --eval vincent.lua 0

得到返回值:

[root@localhost src]# redis-cli --eval vincent.lua 0
"lua666"

案例 :对IP进行限流

需求:在 X 秒内只能访问 Y 次。
设计思路:用 key 记录 IP,用 value 记录访问次数。

分析:拿到 IP 以后,对 IP+1。如果是第一次访问,对 key 设置过期时间(参数 1)。否则判断次数,超过限定的次数(参数 2),返回 0。如果没有超过次数则返回 1。超过时间,key 过期之后,可以再次访问。

KEY[1]是 IP, ARGV[1]是过期时间 X,ARGV[2]是限制访问的次数 Y。

-- ip_limit.lua
-- IP 限流,对某个 IP 频率进行限制 ,6 秒钟访问 10 次
local num=redis.call('incr',KEYS[1])
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

10 秒钟内限制访问 20 次,调用测试(连续调用 20 次):

./redis-cli --eval "ip_limit.lua" app:ip:limit:192.168.2.101 , 10 20

app:ip:limit:192.168.2.101 是 key 值 ,后面是参数值,中间要加上一个空格和一个逗号,再加上一个 空格 
即:./redis-cli –eval [lua 脚本] [key…]空格,空格[args…]
多个参数之间用一个 空格 分割 

Lua脚本为什么要缓存

在脚本比较长的情况下,如果每次调用脚本都需要把整个脚本传给 Redis 服务端,会产生比较大的网络开销。为了解决这个问题,Redis 提供了 EVALSHA 命令,允许开发者通过脚本内容的 SHA1 摘要来执行脚本

 

如何缓存Lua脚本

Redis 在执行 script load 命令时会计算脚本的 SHA1 摘要并记录在脚本缓存中,执行 EVALSHA 命令时 Redis 会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了则执行脚本,否则会返回错误:"NOSCRIPT No matching script. Please use
EVAL."

127.0.0.1:6379> script load "return 'Hello World'"
"470877a599ac74fbfda41caa908de682c5fc7d4b"
127.0.0.1:6379> evalsha "470877a599ac74fbfda41caa908de682c5fc7d4b" 0
"Hello World"

案例:自动相乘

Redis 有 incrby 这样的自增命令,但是没有自乘,比如乘以 3,乘以 5。
我们可以写一个自乘的运算,让它乘以后面的参数:

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

把这个脚本变成单行,语句之间使用分号隔开

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

script load '命令':

127.0.0.1:6379> 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'

"be4f93d8a5379e5e5b768a74e77c8a4eb0434441"

调用:

127.0.0.1:6379> set num 2
OK
127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 num 6
(integer) 12

Lua脚本超时

Redis 的指令执行本身是单线程的,这个线程还要执行客户端的 Lua 脚本,如果 Lua脚本执行超时或者陷入了死循环,是不是没有办法为客户端提供服务了呢?

例如:
eval 'while(true) do end' 0

为了防止某个脚本执行时间过长导致 Redis 无法提供服务,Redis 提供了lua-time-limit 参数限制脚本的最长运行时间,默认为 5 秒钟。

lua-time-limit 5000(redis.conf 配置文件中)

当脚本运行时间超过这一限制后,Redis 将开始接受其他命令但不会执行(以确保脚本的原子性,因为此时脚本并没有被终止),而是会返回“BUSY”错误。

Redis 提供了一个 script kill 的命令来中止脚本的执行。新开一个客户端:

script kill

如果当前执行的 Lua 脚本对 Redis 的数据进行了修改(SET、DEL 等),那么通过script kill 命令是不能终止脚本运行的

127.0.0.1:6379> eval "redis.call('set','vincent','666') while true do end" 0

因为要保证脚本运行的原子性,如果脚本执行了一部分终止,那就违背了脚本原子性的要求。最终要保证脚本要么都执行,要么都不执行

127.0.0.1:6379> script kill
(error) 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 nosave 和 shutdown 的区别在于 shutdown nosave 不会进行持久化操作,意味着发生在上一次快照后的数据库修改都会丢失

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RedisLua脚本是一种在Redis中执行操作的脚本语言。它允许用户通过Lua脚本执行一系列的Redis操作,包括读取、写入和删除数据等。RedisLua脚本具有以下特点: 1. 原子性:RedisLua脚本在执行过程中是原子的,它们要么全部执行成功,要么全部不执行。 2. 可重用性:Lua脚本可以被缓存和重复使用,提高执行效率。 3. 灵活性:Lua脚本支持逻辑控制语句、循环和函数等高级特性,可以实现复杂的业务逻辑。 4. 安全性:Lua脚本可以通过Redis的安全机制进行权限控制,确保脚本的执行安全。 5. 扩展性:RedisLua脚本可以通过调用Redis提供的API扩展其功能。 RedisLua脚本执行流程包括加载脚本、编译脚本和执行脚本三个步骤。在加载脚本阶段,Redis会将脚本加载到内存中,并对其进行缓存,以提高执行效率。在编译脚本阶段,Redis会对脚本进行语法检查和编译。在执行脚本阶段,Redis会按照预定的执行流程执行脚本中的命令,并返回执行结果。 RedisLua脚本在实际应用中有许多场景,包括但不限于: 1. 原子性操作:通过Lua脚本可以实现多个Redis操作的原子性,例如加锁、解锁、事务操作等。 2. 复杂数据处理:通过Lua脚本可以对Redis中的数据进行复杂的处理和计算,例如排序、过滤、聚合等。 3. 分布式锁:通过Lua脚本可以实现分布式锁,保证多个客户端之间的数据的一致性和原子性。 4. 实时计算:通过Lua脚本可以进行实时计算,例如统计、推荐算法等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值