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数据库提供了一些管理功能比如 流水线:打包发送多条命令,并在一个回复里面接收所有被执行命令的结果.事务:一次执行多条命令,被执行的命令要么就全部都被执行,要么就一个也不执行.并且事 ...
随机推荐
- spring-bean依赖注入-02(通过p命名空间注入)
上一篇博客讲述了为什么使用spring依赖注入,怎么注入,详见 spring-bean依赖注入-01(等你来点击) 废话不多说,开始使用p命名空间进行set注入 使用另外一种注入方式是这样的(具体实现 ...
- linux升级Nginx1.6到Nginx1.12.2
我的升级环境: 旧版Nginx:1.6 新版Nginx:1.12.2 系统:Redhat 5.5 64位 前期准备 1.查看Nginx的安装位置 ps -ef |grep nginx --如 ...
- Spring MVC 工作原理和流程、注解
Spring MVC 是实现MVC设计模式的企业级开发框架,是Spring框架的一个子模块,无需整合,开发起来更加便捷. MVC设计模式 MVC是一种设计模式,它将应用程序分为 Controller. ...
- 能直接调试的开放API?这个API Hub绝了
01 此前时不时会有一些研发小伙伴和我诉苦,说很多企业由于人力财力限制或者需求不强,会直接购买使用第三方的开放API,这样一来, 一则由于开放项目不是量身定制的,寻找自己合适的接口也要搜索调研蛮多 ...
- JavaWeb入门day9-随笔(session/cookie)
会话 一个网站怎么证明用户访问过 服务端给客户端一个信件,客户端下次访问服务端带上信件就可以:cookie 服务器登记,下次访问匹配:session 保存会话的技术 cookie 客户端技术(响应.请 ...
- AngularJS性能优化心得,自己踩过的抗,及一些别人的经验(转哦)
脏数据检查 != 轮询检查更新 谈起angular的脏检查机制(dirty-checking), 常见的误解就是认为: ng是定时轮询去检查model是否变更.其实,ng只有在指定事件触发后,才进入$ ...
- OV5640图像采集(一)VGA显示
vga控制器模块 1 引言 项目的背景是采集无人车间现场的工件图像并送往控制间pc端处理,最终实现缺陷检测.项目包括图像采集模块,数据传输模块,上位机,缺陷检测算法等四个部分.其中,图像采集模块又分 ...
- 最新MATLAB R2021b超详细安装教程(附完整安装文件)
摘要:本文详细介绍Matlab R2021b的安装步骤,为方便安装这里提供了完整安装文件的百度网盘下载链接供大家使用.从文件下载到证书安装本文都给出了每个步骤的截图,按照图示进行即可轻松完成安装使用. ...
- Spring 源码(6)BeanFactoryPostProcessor怎么执行的?
上一篇文章 https://www.cnblogs.com/redwinter/p/16167214.html 解读了如何进行自定义属性值的编辑器的解析器,并且还介绍了BeanFactory的准备过程 ...
- JavaWeb之如何把请求数据转成实体类
JavaWeb之如何把请求数据转成实体类 自己写个工具类加入下面两个静态方法 自定一个注解类DateTimeFormatting 调用方式User user = util.ObjectFromMap( ...