首页 > 数据库 >踩坑之RedisTemplate执行Lua脚本

踩坑之RedisTemplate执行Lua脚本

时间:2023-03-02 19:00:56浏览次数:51  
标签:脚本 return lua redis Lua key class RedisTemplate

(目录)


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、代码测试:

@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 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、代码测试:

@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 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作为返回类型

注意:

为什么返回值不用 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}
	 */
	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)

标签:脚本,return,lua,redis,Lua,key,class,RedisTemplate
From: https://blog.51cto.com/panyujie/6096568

相关文章

  • 接口限流常见算法方案原理 及其 实现(Guava RateLimiter,Redis+AOP+Lua)
    (目录)什么是限流?为什么要限流?限流,这个词其实并不陌生,在我们生活中也随处可见。做核酸时,工作人员会在核酸检测点的空地上摆放着弯弯曲曲的围栏,人们排着队左拐右拐的往前......
  • FZU图书馆 油猴脚本 辅助抢座
    注意:后续图书馆由于系统升级Oauth部分发生变化,由于系统更新的bug导致我根本上不去网页版,所以无法进行后续的修改。有需要的同学请在研究的基础上进行一定的修改,大致的流程......
  • Shell 脚本加密
    shc今天学到一个牛逼的命令,shc。作用是对shell脚本进行加密。并且附带一些有趣的功能。命令格式shc[-edate][-maddr][-iiopt][-xcmnd][-llopt][......
  • cowtransfer(奶牛快传)自动上传文件脚本—流程分析
    cowtransfer(奶牛快传)自动上传文件脚本—流程分析序言:距离上传发文也有几天了,这几天也是将这个脚本优化了一下。如果还不清楚这个脚本的效果是怎么样的小伙伴可以......
  • 2023-03-02 TypeError: null is not an object (evaluating 'ImageCropPicker.openPic
    问题描述:rn项目使用到了一个插件react-native-image-crop-picker,运行后报错。原因:安装该插件的时候没有link到android包里。解决方案:react-nativelinkreact-native-......
  • 有趣又实用的python脚本
    1.使用Python进行速度测试这个高级脚本帮助你使用Python测试你的Internet速度。只需安装速度测试模块并运行以下代码。#pipinstallpyspeedtest#pipinstalls......
  • loadrunner---脚本录制常见问题
    一:loadrunner录制脚本时ie浏览器不弹出?1.IE浏览器取消勾选【启用第三方浏览器扩展】启动IE,从【工具】进入【Internet选项】,切到高级,去掉【启用第三方浏览器扩展(需要重启动......
  • 工具利用,原创脚本分享
    前言手动信息收集很累也很慢,这时候一个好用的工具能节省不少时间。脚本文件与源代码下载在文末工具介绍theHarvester是一款通过搜索引擎、PGP服务器以及SHODAN数据库收......
  • openwrt 网络检测脚本
    背景openwrt有些固件不太稳定,会时不时的断网,导致家里无法上网,遇到这种情况只能手动重启openwrt设备,该操作不方便,作为一个极客爱好者,那肯定是要实现自动化处理的了,写一个......
  • CentOS7.6 添加系统自启脚本
    一、编辑脚本1.在自定义的脚本中添加#chkconfig:2352080#chkconfig:23452080 其中2345是默认启动级别,全部0-6共有7个级别。0表示:表示......