Redis之Lua的应用(四)
一、什么是Lua脚本
Lua是一个高效的轻量级脚本语言(和JavaScript类似),用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua在葡萄牙语中是“月亮”的意思,它的logo形式卫星,寓意是Lua是一个“卫星语言”,能够方便地嵌入到其他语言中使用;其实在很多常见的框架中,都有嵌入Lua脚本的功能,比如OpenResty、Redis等。
使用Lua脚本的好处:
减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行
原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心会出现竞态条件
复用性,客户端发送的脚本会永远存储在redis中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑
二、Lua的下载和安装
Lua是一个独立的脚本语言,所以它有专门的编译执行工具,下面简单带大家安装一下。
安装步骤
tar -zxvf lua-5.4.3.tar.gz
cd lua-5.4.3
make linux
make install
最后,直接输入lua命令即可进入lua的控制台。Lua脚本有自己的语法、变量、逻辑运算符、函数等,这块我就不在这里做过多的说明,可以自己进入下面链接进去看
https://www.runoob.com/lua/lua-tutorial.html
三、Redis与Lua
Redis中集成了Lua的编译和执行器,所以我们可以在Redis中定义Lua脚本去执行。同时,在Lua脚本中,可以直接调用Redis的命令,来操作Redis中的数据。
redis.call(‘set’,'hello','world') local value=redis.call(‘get’,’hello’)
redis.call 函数的返回值就是redis命令的执行结果,前面我们介绍过redis的5中类型的数据返回的值的类型也都不一样,redis.call函数会将这5种类型的返回值转化对应的Lua的数据类型
在很多情况下我们都需要脚本可以有返回值,毕竟这个脚本也是一个我们所编写的命令集,我们可以像调用其他redis内置命令一样调用我们自己写的脚本,所以同样redis会自动将脚本返回值的Lua数据类型转化为Redis的返回值类型。 在脚本中可以使用return 语句将值返回给redis客户端,通过return语句来执行,如果没有执行return,默认返回为nil。
四、Redis中执行Lua脚本相关的命令
编写完脚本后最重要的就是在程序中执行脚本。Redis提供了EVAL命令可以使开发者像调用其他Redis内置命令一样调用脚本。
EVAL命令-执行脚本
[EVAL] [脚本内容] [key参数的数量] [key …] [arg …]
可以通过key和arg这两个参数向脚本中传递数据,他们的值可以在脚本中分别使用KEYS和ARGV 这两个类型的全局变量访问。
比如我们通过脚本实现一个set命令,通过在redis客户端中调用,那么执行的语句是:
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 lua hello


上述脚本相当于使用Lua脚本调用了Redis的set命令,存储了一个key=lua,value=hello到Redis中。
EVALSHA命令
考虑到我们通过eval执行lua脚本,脚本比较长的情况下,每次调用脚本都需要把整个脚本传给redis,比较占用带宽。为了解决这个问题,redis提供了EVALSHA命令允许开发者通过脚本内容的SHA1摘要来执行脚本。该命令的用法和EVAL一样,只不过是将脚本内容替换成脚本内容的SHA1摘要
Redis在执行EVAL命令时会计算脚本的SHA1摘要并记录在脚本缓存中
执行EVALSHA命令时Redis会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了就执行脚本,否则返回“NOSCRIPT No matching script,Please use EVAL”
# 将脚本加入缓存并生成sha1命令
script load "return redis.call('get','lua')"
# ["13bd040587b891aedc00a72458cbf8588a27df90"]
# 传递sha1的值来执行该命令
evalsha "13bd040587b891aedc00a72458cbf8588a27df90" 0
五、自己通过Redisson执行Lua脚本
通过lua脚本来实现一个访问频率限制功能。
思路,定义一个key,key中包含ip地址。 value为指定时间内的访问次数,比如说是10秒内只能访问3次。
定义Lua脚本
local times=redis.call('incr',KEYS[1])
-- 如果是第一次进来,设置一个过期时间
if times == 1 then
redis.call('expire',KEYS[1],ARGV[1])
end
-- 如果在指定时间内访问次数大于指定次数,则返回0,表示访问被限制
if times > tonumber(ARGV[2]) then
return 0
end
-- 返回1,允许被访问
return 1
定义controller,提供访问测试方法
@RestController
public class RedissonLuaController {
@Autowired
RedissonClient redissonClient; private final String LIMIT_LUA="local times=redis.call('incr',KEYS[1])\n" +
"if times==1 then\n" +
" redis.call('expire',KEYS[1],ARGV[1])\n" +
"end\n" +
"if times > tonumber(ARGV[2]) then\n" +
" return 0\n" +
"end \n" +
"return 1"; @GetMapping("/lua/{id}")
public String lua(@PathVariable("id") Integer id) throws ExecutionException, InterruptedException {
RScript rScript=redissonClient.getScript();
List<Object> keys= Arrays.asList("LIMIT:"+id);
RFuture<Object> future=rScript.evalAsync(RScript.Mode.READ_WRITE,LIMIT_LUA, RScript.ReturnType.INTEGER,keys,10,3);
return future.get().toString();
} }
要注意,上述脚本执行的时候会有问题,因为redis默认的序列化方式导致value的值在传递到脚本中时,转成了对象类型,需要修改redisson.yml文件,增加codec的序列化方式。
application.yml
spring:
redis:
redisson:
file: classpath:redisson.yml
redisson.yml
singleServerConfig:
address: redis://192.168.221.128:6379 codec: !<org.redisson.codec.JsonJacksonCodec> {}
六、Lua脚本的原子性
redis的脚本执行是原子的,即脚本执行期间Redis不会执行其他命令。所有的命令必须等待脚本执行完以后才能执行。为了防止某个脚本执行时间过程导致Redis无法提供服务。Redis提供了lua-time-limit参数限制脚本的最长运行时间。默认是5秒钟。
非事务性操作
当脚本运行时间超过这个限制后,Redis将开始接受其他命令但不会执行(以确保脚本的原子性),而是返回BUSY的错误,下面演示一下这种情况。
打开两个客户端窗口,在第一个窗口中执行lua脚本的死循环
eval "while true do end" 0
在第二个窗口中运行get lua,会得到如下的异常。
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
我们会发现执行结果是Busy, 接着我们通过script kill 的命令终止当前执行的脚本,第二个窗口的显示又恢复正常了。
存在事务性操作
如果当前执行的Lua脚本对Redis的数据进行了修改(SET、DEL等),那么通过SCRIPT KILL 命令是不能终止脚本运行的,因为要保证脚本运行的原子性,如果脚本执行了一部分终止,那就违背了脚本原子性的要求。最终要保证脚本要么都执行,要么都不执行
同样打开两个窗口,第一个窗口运行如下命令
eval "redis.call('set','name','ljx') while true do end" 0
在第二个窗口运行
get lua
结果一样,仍然是busy,但是这个时候通过script kill命令,会发现报错,没办法kill。遇到这种情况,只能通过shutdown nosave命令来强行终止redis。shutdown nosave和shutdown的区别在于 shutdown nosave不会进行持久化操作,意味着发生在上一次快照后的数据库修改都会丢失。
Redis之Lua的应用(四)的更多相关文章
- Redis进阶实践之七Redis和Lua初步整合使用
一.引言 Redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运行在任何平台上,也可以嵌入到大多数语言当 ...
- Redis进阶实践之七Redis和Lua初步整合使用(转载 7)
Redis进阶实践之七Redis和Lua初步整合使用 一.引言 Redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运 ...
- redis(6)lua脚本
一.lua脚本 lua是一种轻量小巧的脚本语言,用标准的C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能. lua的详细内容你可以参考lua官方网站 ...
- .Net Core使用分布式缓存Redis:Lua脚本
一.前言 运行环境window,redis版本3.2.1.此处暂不对Lua进行详细讲解,只从Redis的方面讲解. 二.Redis的Lua脚本 在Redis的2.6版本推出了脚本功能,允许开发者使用L ...
- 快速入门Redis调用Lua脚本及使用场景介绍
Redis 是一种非常流行的内存数据库,常用于数据缓存与高频数据存储.大多数开发人员可能听说过redis可以运行 Lua 脚本,但是可能不知道redis在什么情况下需要使用到Lua脚本. 一.阅读本文 ...
- redis中lua脚本的简单使用
一.背景 在使用redis的过程中,发现有些时候需要原子性去操作redis命令,而redis的lua脚本正好可以实现这一功能.比如: 扣减库存操作.限流操作等等. redis的pipelining虽然 ...
- redis成长之路——(四)
加强redis连接的连接池 虽然StackExchange.Redis针对连接已经做了很多工作,不过在实际应用场景中还是不够的,比如多实例的vip就会导致只能连到其中一个实例上等:本实例中的连接池只能 ...
- PHP中使用redis执行lua脚本示例
摸索了一下在PHP中如何使用redis执行lua脚本,写了一个脚本如下,供以后参考 <?php $redis = new Redis(); #实例化redis类 $redis->conne ...
- redis之lua脚本
背景介绍 redis数据库提供了一些管理功能比如 流水线:打包发送多条命令,并在一个回复里面接收所有被执行命令的结果.事务:一次执行多条命令,被执行的命令要么就全部都被执行,要么就一个也不执行.并且事 ...
随机推荐
- Java基础语法01——变量与运算符
本文是对Java基础语法的第一部分的学习,包括注释:标识符的命名规则与规范:变量的数据类型分类以及转换:以及六种运算符(算术.赋值.比较.逻辑.三元和位运算符).
- [翻译] Cassandra 分布式结构化存储系统
Cassandra 分布式结构化存储系统 摘要 Cassandra 是一个分布式存储系统,用于管理分布在许多商品服务器上的大量结构化数据,同时提供无单点故障(no single point of fa ...
- partOne测试收获总结
测试收获总结 执行类中构造多个方法,将各个功能分解出来,将大的,复杂的问题转化成小的,简单的问题,来进行处理,正所谓复杂问题简单化,简单问题流程化.大道至简编程精益.现总结编程中的一些问题,①在J ...
- Nginx作为高性能服务器的缘由以及请求过程
Nginx作为高性能服务器的缘由以及请求过程 简介: Nginxx采用的是多进程(单线程)&多路IO复用模型,使用I/O多路复用技术的Nginx,就成了"并发事件驱动"的服 ...
- Typora使用手册(基础)
Typora使用手册 第一步,你首先得拥有一个Typora,可通过(https://typoraio.cn/)该网址下载. 第二步,安装并打开. 第三步,让我们开始认识并设置自己的Typora吧~ 什 ...
- 类变量_main方法_代码块
需要解决的问题: 统计一共创建了多少个对象 类变量(静态变量) 被所有对象共享 可以通过 类名(推荐)|对象名.变量名 方式来访问 main main 方法 是虚拟机在调用 args 传值灵活 结果 ...
- 【hexo博客搭建】将搭建好的hexo博客部署到阿里云服务器上面(下)
一.部署到阿里云服务器 既然博客也已经成功在本地部署,然后主题也成功安装,接下来就可以部署到服务器上面了,如果你也想要魔改matery主题,可以去各种博客上面找一找大佬的教程,或者联系我,也可以让你少 ...
- JQ的is()
is():根据选择器.元素或jQuery对象来检测匹配元素集合, 如果这些元素中至少有一个元素匹配给定的参数,则返回true. <!DOCTYPE html> <html lang ...
- 中间件漏洞之Apache
中间件之Apache漏洞 我们常见的中间件有IIS.Apache.Nginx,其中Apache中间件有什么漏洞呢? Apache 解析漏洞: 漏洞原因:该解析漏洞属于用户配置问题(mime.types ...
- 使用RoslynSyntaxTool工具互相转换C#代码与语法树代码
项目地址 MatoApps/RoslynSyntaxTool: 此工具能将C#代码,转换成使用语法工厂构造器(SyntaxFactory)生成等效语法树代码 (github.com) 基础概念 S ...