ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

使用RedisTemplate执行Redis脚本

2021-07-15 10:33:29  阅读:151  来源: 互联网

标签:脚本 return script Redis RedisScript connection RedisTemplate


对于Redis脚本使用过的同学都知道,这个主要是为了防止竞态条件而用的。因为脚本是顺序执行的。(不用担心效率问题)比如我在工作用,用来设置考试最高分。

如果还没有用过的话,先去看Redis脚本的介绍,发送脚本,缓存脚本,发送sha1执行脚本,以及基本的lua脚本的语法。

1. Redis脚本的使用场景

在一些缓存的设置中,经常会出现竞态条件,由于并发导致数据有误。比如大家熟知的++操作。我们自己通过Redis实现++的话,很容易在并发下出现误差。所以Redis提供了incr函数。我在设置最高分的时候,获取分数70,A得分80,然后设置80。然后在同时,B获取分数70,但是B得分78,78>70,然后设置78。这样的话,B的修改就把A的修改替换了。这也是数据库隔离级别中的第一类更新丢失。

为了防止这个问题,Redis提供了顺序执行命令的方法,就是使用脚本。

2. 脚本类RedisScript

RedisTemplate对脚本提供了很高的支持,执行方法同之前的类似,都是通过connection回调。但是这里要注意的是:脚本不支持事务,所以脚本之前不能进行connection.multi()开启事务,也不能用@Transactional注解让spring来开启事务,这些都会抛出异常,大家可以自行测试下。

 

看到这里,我们新建一个最高分的脚本类,MaxscoreScript,继承自接口RedisScript,需要实现三个方法:

复制代码
public interface RedisScript<T> {

    /**
     * @return The SHA1 of the script, used for executing Redis evalsha command
     */
    String getSha1();

    /**
     * @return The script result type. Should be one of Long, Boolean, List, or deserialized value type. Can be null if
     *         the script returns a throw-away status (i.e "OK")
     */
    Class<T> getResultType();

    /**
     * @return The script contents
     */
    String getScriptAsString();

}
复制代码

 

getSha1(),是获取脚本摘要。看过脚本的应该知道,Redis支持缓存脚本,第一次发送脚本以后,后面都直接发送该脚本的sha1摘要信息来直接执行,大概是为了节省传输成本吧。毕竟摘要只需要32个字符。

getResultType(),是获取返回类型的class,需要和定义的泛型T一致。其实之前在Hibernate的Basedao里面,都用过直接通过实例来获取泛型的class。虽然不明白这里为什么加,但是实现这个方法,绝对是没有错。

getScriptAsString(),毫无疑问,这个是返回脚本内容。至于怎么写脚本,这里不做说明。

 

3. 使用RedisTemplate执行脚本

复制代码
    public <T> T execute(RedisScript<T> script, List<K> keys, Object... args) {
        return scriptExecutor.execute(script, keys, args);
    }

    public <T> T execute(RedisScript<T> script, RedisSerializer<?> argsSerializer, RedisSerializer<T> resultSerializer,
            List<K> keys, Object... args) {
        return scriptExecutor.execute(script, argsSerializer, resultSerializer, keys, args);
    }
复制代码

相比之下,第二个是自己传入序列化器,第一个是用默认的key和value序列化器。

强烈建议使用第二个。然后统一传入(StringSerializer),里面的参数全部转为String。我在使用的时候,lua进行大小比较,发现比较的有误。后来调试才发现,是JdkSerializer中,Redis把他当一串\xu..这样的字符串,字符串比较显然不准确。但是如果是"11","12"这样的字符串,就可以通过lua转为num类型,再进行比较。

当然,也许有别的业务场景,不需要这么做,也有可能。自行分析斟酌。

 

我们看到执行脚本的源码

复制代码
    protected <T> T eval(RedisConnection connection, RedisScript<T> script, ReturnType returnType, int numKeys,
            byte[][] keysAndArgs, RedisSerializer<T> resultSerializer) {

        Object result;
        try {
            result = connection.evalSha(script.getSha1(), returnType, numKeys, keysAndArgs);
        } catch (Exception e) {

            if (!exceptionContainsNoScriptError(e)) {
                throw e instanceof RuntimeException ? (RuntimeException) e : new RedisSystemException(e.getMessage(), e);
            }

            result = connection.eval(scriptBytes(script), returnType, numKeys, keysAndArgs);
        }

        if (script.getResultType() == null) {
            return null;
        }

        return deserializeResult(resultSerializer, result);
    }
复制代码

 

这一段代码是最终调用的,之前还有一些序列化参数的操作没有贴出来。

默认直接提交sha1摘要,得到异常,如果异常属于exceptionContainsNoScriptError ,再发送执行脚本,获取返回结果后通过用户定义的结果序列化器进行反序列化。

 

4. 总结

 

这一篇讲了RedisTemplate如何使用脚本。可能对于不懂脚本在connection 或者在redis控制台下使用的同学来说,还是不能理解。但是如果懂得控制台发送脚本,那么通过RedisTemplate的使用,会让很多问题迎刃而解。

转载于:https://www.cnblogs.com/shamo89/p/8648506.html

标签:脚本,return,script,Redis,RedisScript,connection,RedisTemplate
来源: https://www.cnblogs.com/shoshana-kong/p/15014232.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有