SpringBoot中 Lua函数操作redis

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](https://my.oschina.net/difrik)
@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操作就说到这里,欢迎继续关注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章