前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis:18---常用功能之(Lua脚本)

Redis:18---常用功能之(Lua脚本)

作者头像
用户3479834
发布2021-02-03 12:48:17
7410
发布2021-02-03 12:48:17
举报
文章被收录于专栏:游戏开发司机
  • 为了保证多条命令组合的原子性,Redis提供了简单的事务功能以及集成Lua脚本来解决这个问题,本文介绍Lua,事务已经在前一篇文章介绍过了

一、Lua概述

  • Lua语言是在1993年由巴西一个大学研究小组发明,其设计目标是作为嵌入式程序移植到其他应用程序,它是由C语言实现的,虽然简单小巧但是功能强大
  • 所以许多应用都选用它作为脚本语言,尤其是在游戏领域,例如大名鼎鼎的暴雪公司将Lua语言引入到“魔兽世界”这款游戏中,Rovio公司将 Lua语言作为“愤怒的小鸟”这款火爆游戏的关卡升级引擎,Web服务器Nginx 将Lua语言作为扩展,增强自身功能
  • Redis将Lua作为脚本语言可帮助开发者定制自己的Redis命令,在这之前,必须修改源

二、Lua的基本语法

  • 现在先简单地介绍一下Lua的基本语法(只介绍部分语法),与Redis无关,纯属于Lua的语法

数据类型

  • Lua语言提供了如下几种数据类型:booleans(布尔)、numbers(数值)、strings(字符串)、tables(表格),和许多高级语言相比,相对简单

全局变量/局部变量

  • local代表val是一个局部变量,如果没有local代表是全局变量
代码语言:javascript
复制
local strings val = "world"

字符串

  • 下面定义一个字符串类型的数据:
代码语言:javascript
复制
local strings val = "world"

print

  • print函数可以打印出变量的值,例如下面代码将打印world
代码语言:javascript
复制
local strings val = "world"
printf(val)

注释

  • "--"是Lua语言的注释
代码语言:javascript
复制
-- 注释

数组

  • 在Lua中,如果要使用类似数组的功能,可以用tables类型
  • 下面代码使用定义了一个tables类型的变量myArray,但和大多数编程语言不同的是, Lua的数组下标从1开始计算:
代码语言:javascript
复制
local tables myArray = {"redis", "jedis", true, 88.0}
-- trueprint(myArray[3])

for

  • 下面代码会计算1到100的和,关键字for以end作为结束符:
代码语言:javascript
复制
local int sum = 0
for i = 1, 100dosum = sum + iend
-- 输出结果为5050print(sum)
  • 要遍历myArray,首先需要知道tables的长度,只需要在变量前加一个# 号即可:
代码语言:javascript
复制
for i = 1, #myArraydoprint(myArray[i])end
  • 除此之外,Lua还提供了内置函数ipairs,使用for index,value ipairs(tables)可以遍历出所有的索引下标和值:
代码语言:javascript
复制
for index,value in ipairs(myArray)doprint(index)print(value)end

while

  • 下面代码同样会计算1到100的和,只不过使用的是while循环,while循环同样以end作为结束符
代码语言:javascript
复制
local int sum = 0local int i = 0
while i <= 100dosum = sum +ii = i + 1end
--输出结果为5050print(sum)

if、else

  • 要确定数组中是否包含了jedis,有则打印true,注意if以end结尾,if后 紧跟then:
代码语言:javascript
复制
local tables myArray = {"redis", "jedis", true, 88.0}
for i = 1, #myArraydoif myArray[i] == "jedis"thenprint("true")breakelse--do nothingendend

哈希

  • 如果要使用类似哈希的功能,同样可以使用tables类型
  • 例如下面代码 定义了一个tables,每个元素包含了key和value,其中strings1..string2是将两个字符串进行连接:
代码语言:javascript
复制
local tables user_1 = {age = 28, name = "tome"}--user_1 age is 28print("user_1 age is " .. user_1["age"])
  • 如果要遍历user_1,可以使用Lua的内置函数pairs:
代码语言:javascript
复制
for key,value in pairs(user_1)do print(key .. value)end

函数定义

  • 在Lua中,函数以function开头,以end结尾,funcName是函数名,中间部分是函数体
代码语言:javascript
复制
function funcName()...end
代码语言:javascript
复制
-- contact函数将两个字符串拼接:function contact(str1, str2)return str1 .. str2end
--"hello world"print(contact("hello ", "world"))

三、在Redis中使用Lua(eval、evalsha)

  • 在Redis中执行Lua脚本有两种方法:eval和evalsha

eval

  • EVAL命令可以直接执行Lua脚本
  • 格式如下:
代码语言:javascript
复制
eval 脚本内容 key个数 key列表 参数列表
  • 例如:下面执行一个Lua脚本,内容为“hello world”,参数为0个
  • 例如:下面使用了key列表和参数列表来为Lua脚本提供更多的灵活性:
    • 此时KEYS[1]="redis",ARGV[1]="world",所以最终的返回结果是"hello redisworld"。
代码语言:javascript
复制
eval 'return "hello " .. KEYS[1] .. ARGV[1]' 1 redis world
  • 如果Lua脚本较长,还可以使用redis-cli --eval直接执行文件。eval命令和--eval参数本质是一样的,客户端如果想执行Lua脚本,首先在客户端编写好Lua脚本代码,然后把脚本作为字符串发送给服务端,服务端会将执行结果返回给客户端,整个过程如下图所示:

evalsha

  • 除了使用eval,Redis还提供了evalsha命令来执行Lua脚本
  • 格式如下:
代码语言:javascript
复制
evalsha 脚本SHA1值 key个数 key列表 参数列表
  • 执行原理如下:
    • 首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和
    • evalsha命令使用SHA1作为参数可以直接执行对应Lua脚本,避免每次发送Lua脚本的开销。这样客户端就不需要每次执行脚本内容,而脚本也会常驻在服务端,脚本功能得到了复用

演示案例:

  • 建立一个名为lua_get.lua的脚本文件,内容如下:
代码语言:javascript
复制
return "hello " .. KEYS[1] .. ARGV[1]
  • 加载脚本:在系统命令行执行script load命令(下面会介绍)可以将脚本内容加载到Redis内存中,例如下面将lua_get.lua加载到Redis中,得到SHA1 为:"7413dc2440db1fea7c0a0bde841fa68eefaf149c"
代码语言:javascript
复制
redis-cli script load "$(cat lua_get.lua)"
  • 执行脚本:所以在redis-cli内部使用上面的SHA1值就可以执行该脚本了
代码语言:javascript
复制
evalsha 7413dc2440db1fea7c0a0bde841fa68eefaf149c 1 redis world

四、Lua中的Redis API

  • 下面介绍几个在Lua中可以使用的Redis API,更多的API可以百度自行查阅

call()

  • Lua可以使用redis.call函数实现对Redis的访问
  • 例如,下面代码是Lua使用redis.call调用了Redis的set和get操作:
代码语言:javascript
复制
redis.call("set", "hello", "world")redis.call("get", "hello")
  • 在Redis中执行的效果如下:

pcall()

  • 除此之外Lua还可以使用redis.pcall函数实现对Redis的调用
  • redis.call和 redis.pcall的不同在于:如果redis.call执行失败,那么脚本执行结束会直接返 回错误,而redis.pcall会忽略错误继续执行脚本,所以在实际开发中要根据 具体的应用场景进行函数的选择。

log()

  • Lua可以使用redis.log函数将Lua脚本的日志输出到Redis的日志文件中, 但是一定要控制日志级别
  • 备注:Redis3.2提供了Lua Script Debugger功能用来调试复杂的Lua脚本,具体 可以参考:http://redis.io/topics/ldb

五、使用案例

  • Lua脚本功能为Redis开发和运维人员带来如下三个好处:
    • Lua脚本在Redis中是原子执行的,执行过程中间不会插入其他命令
    • Lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这些命令常驻在Redis内存中,实现复用的效果
    • Lua脚本可以将多条命令一次性打包,有效地减少网络开销

演示案例

  • ①当前列表记录着热门用户的id, 假设这个列表有5个元素,如下所示
代码语言:javascript
复制
RPUSH host:user:list user:1:ratio user:8:ratio user:3:ratio user:99:ratio user:72:ratio
lrange host:user:list 0 -1
  • user:{id}:ratio代表用户的热度,它本身又是一个字符串类型的键:
代码语言:javascript
复制
set user:1:ratio 986set user:8:ratio 762set user:3:ratio 556set user:99:ratio 400set user:72:ratio 101
  • ②现要求将列表内所有的键对应热度做加1操作,并且保证是原子执行, 此功能可以利用Lua脚本来实现,例如下面是一个名为lrange_and_mincr.lua脚本的内容
代码语言:javascript
复制
-- 将列表中所有元素取出,赋值给mylistlocal mylist = redis.call("lrange", KEYS[1], 0, -1)
-- 定义局部变量count=0,这个count就是最后incr的总次数local count = 0
-- 遍历mylist中所有元素,每次做完count自增,最后返回countfor index,key in ipairs(mylist)doredis.call("incr",key)count = count + 1endreturn count
  • ③并执行如下操作,返回结果为5:
代码语言:javascript
复制
redis-cli --eval lrange_and_mincr.lua host:user:list
  • ④执行后所有用户的热度自增1:
代码语言:javascript
复制
mget user:1:ratio user:8:ratio user:3:ratio user:99:ratio user:72:ratio

六、Redis管理Lua脚本

  • Redis提供了4个命令实现对Lua脚本的管理,下面分别介绍

①script load

代码语言:javascript
复制
script load script
  • 此命令用于将Lua脚本加载到Redis内存中,上面evalsha命令的演示案例中有介绍

②script exists

代码语言:javascript
复制
scripts exists sha1 [sha2 …]
  • 此命令用于判断sha1、sha2...是否已经加载到Redis内存中
  • 例如,在上面evalsha命令介绍的演示案例中,我们加载了一个SHA1值为“7413dc2440db1fea7c0a0bde841fa68eefaf149c”的Lua脚本,现在我们进行检测,结果返回1

③script flush

代码语言:javascript
复制
script flush
  • 此命令用于清除Redis内存已经加载的所有Lua脚本
  • 例如:例如我们清楚上面那个SHA1值为“7413dc2440db1fea7c0a0bde841fa68eefaf149c”的脚本,执行script flush后,脚本不再存在了:

④script kill

代码语言:javascript
复制
script kill
  • 此命令用于杀掉正在执行的Lua脚本
  • 如果Lua脚本比较耗时,甚至Lua脚本存在问题,那么此时Lua脚本的执行会阻塞Redis,直到脚本执行完毕或者外部进行干预将其结束。例如,下面的Lua脚本会无线循环,因此客户端会阻塞
  • lua-time-limit参数:
    • Redis提供了一个lua-time-limit参数,单位为毫秒,默认为5000毫秒(5秒),它是Lua脚本的“超时时间”
    • 但这个超时时间仅仅是当Lua脚本时间超过lua-time-limit后,向其他命令调用发送BUSY的信号,但是并不会停止掉服务端和客户端的脚本执行,所以当达到lua-time-limit值之后,其他客户端在执行正常的命令时,将会收到“Busy Redis is busy running a script”错误,并且提示使用script kill或者shutdown nosave命令来杀掉这个busy的脚本(见下图演示案例)
  • script kill比shutdown命令更好:在Lua阻塞时,使用script kill更好,因为shutdown会让Redis服务停止,而script不会,其只是关闭Lua脚本的执行

演示案例

  • 接着上图,我们左侧的客户端执行的Lua脚本处于阻塞状态,此时我们在右边输入script kill杀死正在执行的Lua脚本,此时左侧客户端返回,所有客户端可以继续执行访问了

scritp kill命令失效的情况

  • 如果Lua脚本正在执行写操作,那么script kill命令就会失效
    • 例如,下面左侧客户端执行的Lua脚本一直不停的在执行set操作,右侧客户端使用script kill时会显示出错(上提示Lua脚本正在向Redis执行写命令,要么等待脚本执行结束要么使用shutdown nosave停掉Redis服务)
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 游戏开发司机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Lua概述
  • 二、Lua的基本语法
    • 全局变量/局部变量
      • 字符串
        • print
          • 注释
            • 数组
              • for
                • while
                  • if、else
                    • 哈希
                      • 函数定义
                      • 三、在Redis中使用Lua(eval、evalsha)
                        • eval
                          • evalsha
                            • 演示案例:
                            • 四、Lua中的Redis API
                              • call()
                              • 五、使用案例
                                • 演示案例
                                • 六、Redis管理Lua脚本
                                  • ①script load
                                    • ②script exists
                                      • ③script flush
                                        • ④script kill
                                          • 演示案例
                                            • scritp kill命令失效的情况
                                            相关产品与服务
                                            云数据库 Redis
                                            腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                                            领券
                                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档