1、背景

有时候,我们需要一次性操作多个 Redis 命令,但是 这样的多个操作不具备原子性,而且 Redis 的事务也不够强大,不支持事务的回滚,还无法实现命令之间的逻辑关系计算。所以,一般在开发中,我们会利用 lua 脚本来实现 Redis 的事务。

2、lua 脚本

Redis 中使用 lua 脚本,我们需要注意的是,从 Redis 2.6.0后才支持 lua 脚本的执行。

使用 lua 脚本的好处:

  • 原子操作:lua脚本是作为一个整体执行的,所以中间不会被其他命令插入。
  • 减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延。
  • 复用性:lua脚本可以常驻在redis内存中,所以在使用的时候,可以直接拿来复用,也减少了代码量。

3、Redis 中执行 lua 脚本

1、命令格式:

EVAL script numkeys key [key ...] arg [arg ...]

说明:

  • script是第一个参数,为Lua 5.1脚本(字符串)。
  • 第二个参数numkeys指定后续参数有几个key。
  • key [key ...],被操作的key,可以多个,在lua脚本中通过KEYS[1], KEYS[2]获取
  • arg [arg ...],参数,可以多个,在lua脚本中通过ARGV[1], ARGV[2]获取。

2、如果直接使用 redis-cli命令:

redis-cli --eval lua_file key1 key2 , arg1 arg2 arg3

说明:

  • eval 命令后不再是 lua 脚本的字符串形式,而是一个 lua 脚本文件。后缀为.lua
  • 不再需要numkeys参数,而是用 , 隔开多个key和多个arg

4、使用 RedisTemplate 执行 lua 脚本

例子:删除 Redis 分布式锁

引入依赖:此依赖为我们整合了 Redis ,并且提供了非常好用的 RedisTemplate。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

方式一:lua 脚本文件

1、新建 lua 脚本文件:

if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

说明:先获取指定key的值,然后和传入的arg比较是否相等,相等值删除key,否则直接返回0。

2、代码测试:

/**
* @author Howinfun
* @desc lua 测试
* @date 2019/11/5
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test")
@SpringBootTest(classes = ThirdPartyServerApplication.class)
public class RedisTest { @Autowired
private StringRedisTemplate redisTemplate;
@Test
public void contextLoads() {
String lockKey = "123";
String UUID = cn.hutool.core.lang.UUID.fastUUID().toString();
boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey,UUID,3, TimeUnit.MINUTES);
if (!success){
System.out.println("锁已存在");
}
// 执行 lua 脚本
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 指定 lua 脚本
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/DelKey.lua")));
// 指定返回类型
redisScript.setResultType(Long.class);
// 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey),UUID);
System.out.println(result);
}
}

方式二:直接编写 lua 脚本(字符串)

1、代码测试:

/**
* @author Howinfun
* @desc lua 脚本测试
* @date 2019/11/5
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test")
@SpringBootTest(classes = ThirdPartyServerApplication.class)
public class RedisTest { /** 释放锁lua脚本 */
private static final String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; @Autowired
private StringRedisTemplate redisTemplate;
@Test
public void contextLoads() {
String lockKey = "123";
String UUID = cn.hutool.core.lang.UUID.fastUUID().toString();
boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey,UUID,3, TimeUnit.MINUTES);
if (!success){
System.out.println("锁已存在");
}
// 指定 lua 脚本,并且指定返回值类型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT,Long.class);
// 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey),UUID);
System.out.println(result);
}
}

注意:可能会有同学发现,为什么返回值不用 Integer 接收而是用 Long。这里是因为 spring-boot-starter-data-redis 提供的返回类型里面不支持 Integer。

大家可以看看源码:

/**
* Represents a data type returned from Redis, currently used to denote the expected return type of Redis scripting
* commands
*
* @author Jennifer Hickey
* @author Christoph Strobl
*/
public enum ReturnType { /**
* Returned as Boolean
*/
BOOLEAN, /**
* Returned as {@link Long}
*/
INTEGER, /**
* Returned as {@link List<Object>}
*/
MULTI, /**
* Returned as {@literal byte[]}
*/
STATUS, /**
* Returned as {@literal byte[]}
*/
VALUE; /**
* @param javaType can be {@literal null} which translates to {@link ReturnType#STATUS}.
* @return never {@literal null}.
*/
public static ReturnType fromJavaType(@Nullable Class<?> javaType) { if (javaType == null) {
return ReturnType.STATUS;
}
if (javaType.isAssignableFrom(List.class)) {
return ReturnType.MULTI;
}
if (javaType.isAssignableFrom(Boolean.class)) {
return ReturnType.BOOLEAN;
}
if (javaType.isAssignableFrom(Long.class)) {
return ReturnType.INTEGER;
}
return ReturnType.VALUE;
}
}

所以当我们使用 Integer 作为返回值的时候,是报以下错误:

org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: java.lang.IllegalStateException

	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:74)
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)

SpringBoot + Redis 执行lua脚本的更多相关文章

  1. PHP中使用redis执行lua脚本示例

    摸索了一下在PHP中如何使用redis执行lua脚本,写了一个脚本如下,供以后参考 <?php $redis = new Redis(); #实例化redis类 $redis->conne ...

  2. Redis执行Lua脚本的情况

    第一个测试: 往Redis里面存入1000个Hash,每个Hash里面有100个元素(Key 0-99,值是Key^2). PHP代码,执行33s左右 <?php $redis = new Re ...

  3. Redis执行Lua脚本示例

    Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行.使用脚本的好处如下: 1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在red ...

  4. 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁

    spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...

  5. redis中lua脚本的简单使用

    一.背景 在使用redis的过程中,发现有些时候需要原子性去操作redis命令,而redis的lua脚本正好可以实现这一功能.比如: 扣减库存操作.限流操作等等. redis的pipelining虽然 ...

  6. redis之lua脚本

    背景介绍 redis数据库提供了一些管理功能比如 流水线:打包发送多条命令,并在一个回复里面接收所有被执行命令的结果.事务:一次执行多条命令,被执行的命令要么就全部都被执行,要么就一个也不执行.并且事 ...

  7. Redis结合Lua脚本实现高并发原子性操作

    从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis … 案例-实现访问频率限制: 实现访问者 $ip 在一定的时间 $time 内只能访问 $limit 次. 非脚 ...

  8. redis(6)lua脚本

    一.lua脚本 lua是一种轻量小巧的脚本语言,用标准的C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能. lua的详细内容你可以参考lua官方网站 ...

  9. 使用jedis执行lua脚本

    转: redis学习(十五) 使用jedis执行lua脚本(实现一个对IP的限流) 2018年09月15日 20:07:26 码农-文若书生 阅读数:1609   使用jedis执行lua脚本(实现一 ...

随机推荐

  1. Scanner类的next()方法和nextLine()方法的异同点

    通过一段代码就可以明白其中的奥妙!! import java.util.Scanner; public class next_nextLine { public static void main(St ...

  2. 让你如绅士般基于描述编写 Python 命令行工具的开源项目:docopt

    作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...

  3. shell命令大全笔记

    ## -print 将匹配的文件输出到标准输出## -exec 将匹配的文件执行该参数所给出的shell命令## -ok 将匹配的文件执行该参数所给出的shell命令,每次执行命令有提示 #----- ...

  4. 视频转换器 Wondershare Video Converter Ultimate v11.5.1 中文便携版

    Wondershare Video Converter Ultimate 是万兴公司出品的一款多功能音视频转换.DVD 刻录软件.视频下载软件.有了它,您可以随时随地观看.下载.编辑.转换.刻录视频, ...

  5. mpvue 星星打分组件

    上图: <template> <div class="container"> <div v-for="(star,index) in sta ...

  6. 【Java基础】Java开发过程中的常用工具类库

    目录 Java开发过程中的常用工具类库 1. Apache Commons类库 2. Guava类库 3. Spring中的常用工具类 4. 其他工具 参考 Java开发过程中的常用工具类库 1. A ...

  7. python编程基础之二十五

    匿名函数:不用def 定义的函数,没有函数名 lambda只是一个表达式,函数体比def简单的多 lambda的函数体不再是代码块 lambda只有一行,增加运行效率 lambda [参数1][参数2 ...

  8. InfluxDB从原理到实战 - InfluxDB常用的基础操作

    0x00 基础操作介绍 在本文中将介绍InfluxDB常用的基础操作,帮助读者建立对InfluxDB的感性认识,快速的动手玩起来,持续查询(Continuous Queies).Group by.Se ...

  9. SEER流量众筹模块开发测试网络及使用文档发布

    SEER利用区块链奖励机制,可解决传统体育赛事痛点,提高行业运转效率.比如提高赛事方收入,让观众自由选择想看的比赛,给予赛事众筹的参与者贡献影响力,使其获得由智能合约量化的激励等.此功能可广泛应用于包 ...

  10. Creator3D 守护你的球球—UV动画与天空盒

    1 游戏预览 在线体验地址:http://example.creator-star.cn/follo-ball/ 2 场景物体 场景物体 新建场景后,引擎会为我们创建默认的摄像机和灯光,这个我们就不介 ...