Lua
Lua 是一个简洁、轻量、可扩展的脚本语言,它的特性有
- 轻量:源码包只有核心库,编译后体积很小
- 高效:由 ANSI C 写的,启动快、运行快
- 内嵌:可内嵌到各种编程语言或系统中运行,提升静态语言的灵活性。如 OpenResty 就是将 Lua 嵌入到 nginx 中执行
Redis 在 2.6 版本后,开始支持Lua脚本
优点
-
减少网络开销:多个请求通过脚本一次发送,减少网络延迟
-
原子操作:将脚本作为一个整体执行,中间不会插入其他命令,无需使用事务
-
复用:客户端发送的脚本永久存在redis中,其他客户端可以复用脚本
-
可嵌入性:可嵌入JAVA,C#等多种编程语言,支持不同操作系统跨平台交互
执行步骤
- 检查脚本是否执行过,没执行过使用脚本的 sha1 校验和生成一个 Lua 函数
- 为函数绑定超时、错误处理勾子
- 创建一个伪客户端,通过这个伪客户端执行 Lua 中的 Redis 命令
- 处理伪客户端的返回值,最终返回给客户端
Spring Boot 实现
redis 数据结构zset 是一个有序集合,我们可以通过zadd 根据score 有序存储,通过zrange 查询,通过zrem 删除。但有个问题,我们在有序集合取值的时候,无法做到像队列、栈一样,做到弹栈,即需要先通过zrange 拿到最大(小)值,然后通过zrem删除刚刚取得值,考虑到并发问题,zrange和zrem操作加并发锁。中间至少四次操作redis网络请求(获取锁、zrange、
zrem、释放锁),那有没有什么办法通过一次网络请求就可以完成,那就是Lua函数,
Lua函数
//获取最大(小)值
local object = redis.call('ZRANGE',KEYS[1],0,0);
//删除最大(小)值
redis.call('ZREMRANGEBYRANK',KEYS[1],0,0);
//返回目标值
return object;
存储有序元素 LuaBean
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LuaBean {
private Integer score;
private String name;
private Integer order;
}
存储有序集合
@GetMapping("/add")
public Object set(){
LuaBean luaBean = new LuaBean(SCORE,"su"+SCORE,SCORE);
Boolean add = redisTemplate.opsForZSet().add(REDIS_KEY, luaBean, luaBean.getScore());
++SCORE;
return add;
}
有序结果集
[
{
"score": 1,
"name": "su1",
"order": 1
},
{
"score": 2,
"name": "su2",
"order": 2
},
{
"score": 3,
"name": "su3",
"order": 3
},
{
"score": 4,
"name": "su4",
"order": 4
},
{
"score": 5,
"name": "su5",
"order": 5
}
]
执行语句集
@GetMapping("/pop")
public Object pop(){
String scriptText = "local object = redis.call('ZRANGE',KEYS[1],0,0);\n" +
"redis.call('ZREMRANGEBYRANK',KEYS[1],0,0);\n" +
"return object;";
DefaultRedisScript<LuaBean> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(LuaBean.class);
redisScript.setScriptText(scriptText);
List<String> keys = new ArrayList<>();
keys.add(REDIS_KEY);
Object execute = redisTemplate.execute(redisScript, keys);
return execute;
}
可以发现每次操作我们模拟的pop
我们可以拿到
{
"score": 1,
"name": "su1",
"order": 1
}
第二次
{
"score": 2,
"name": "su2",
"order": 2
}
此时redis 剩余集合
[
{
"score": 3,
"name": "su3",
"order": 3
},
{
"score": 4,
"name": "su4",
"order": 4
},
{
"score": 5,
"name": "su5",
"order": 5
}
]
ok,目前为止,redis 系列的Lua操作就说到这里,欢迎继续关注