深入理解Spring Redis的使用 (四)、RedisTemplate执行Redis脚本
对于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的使用,会让很多问题迎刃而解。
到这里我所能带给大家的也就说完了。下一篇我将把自己遇到的问题罗列下,前车之鉴,后事之师。希望大家以后遇到后,不会在卡太长时间。
深入理解Spring Redis的使用 (四)、RedisTemplate执行Redis脚本的更多相关文章
- RedisTemplate执行Redis脚本
对于Redis脚本使用过的同学都知道,这个主要是为了防止竞态条件而用的.因为脚本是顺序执行的.(不用担心效率问题)比如我在工作用,用来设置考试最高分. 如果还没有用过的话,先去看Redis脚本的介绍, ...
- Redis(十):使用RedisTemplate执行Redis脚本
对于Redis脚本使用过的同学都知道,这个主要是为了防止竞态条件而用的.因为脚本是顺序执行的.(不用担心效率问题)比如我在工作用,用来设置考试最高分. 如果还没有用过的话,先去看Redis脚本的介绍, ...
- Redis笔记(四):Redis事务支持
Redis 事务 Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证: 批量操作在发送 EXEC 命令前被放入队列缓存. 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其 ...
- 【Redis笔记(四)】 Redis数据结构 - list链表
原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/50573605 经过前面的介绍,我们学习了Redis中string字符串.hash ...
- Redis学习第四课:Redis List类型及操作
list是一个链表结构,主要功能是push.pop.获取一个范围的所有值等,操作中key理解为链表的名字. Redis的list类型其实就是一个每个子元素都是string类型的双向链表.我们可以通过p ...
- Redis 学习笔记四 Mysql 与Redis的同步实践
一.测试环境在Ubuntu kylin 14.04 64bit 已经安装Mysql.Redis.php.lib_mysqludf_json.so.Gearman. 点击这里查看测试数据库及表参考 本文 ...
- Redis系列(四):Redis持久化和主从复制原理
一.持久化 所谓的持久化就是把内存中的数据写到磁盘中去,防止服务宕机后内存数据丢失.Redis4.0之前提供了两种持久化方式:RDB(默认) 和AOF,Redis4.x之后新增了一种混合持久化(本文所 ...
- SpringBoot通过RedisTemplate执行Lua脚本
如果你对Redis和Lua的关系不太清楚,请先阅读:Redis进阶之使用Lua脚本开发 1.RedisScript 首先你得引入spring-boot-starter-data-redis依赖,其次把 ...
- SpringBoot + Redis 执行lua脚本
1.背景 有时候,我们需要一次性操作多个 Redis 命令,但是 这样的多个操作不具备原子性,而且 Redis 的事务也不够强大,不支持事务的回滚,还无法实现命令之间的逻辑关系计算.所以,一般在开发中 ...
随机推荐
- Linux基础 -Ubuntu
Ubuntu 下: sudo 以管理员权限执行 apt 是Advanced Packaging Tool ,Ubuntu下的安装包管理工具,早期使用apt-get,从Ubuntu16开始建议使用apt ...
- ubuntu 下php + nginx
location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; root /home/roger/uicode/pcp_web; # # N ...
- 2018-2019-2 网络对抗技术 20165323 Exp6 信息搜集与漏洞扫描
一.实验内容 二.实验步骤 1.各种搜索技巧的应用 2.DNS IP注册信息的查询 3.基本的扫描技术 主机发现 端口扫描 OS及服务版本探测 具体服务的查点 4.漏洞扫描 三.实验中遇到的问题 四. ...
- 【工具】Idea GenerateAllSetter
使用工具自动生成setter方法调用,不是idea原生态生成getter/setter https://github.com/gejun123456/intellij-generateAllSetMe ...
- RabbitMQ 使用QOS(服务质量)+Ack机制解决内存崩溃的情况
当消息有几万条或者几十万条的时候,如果消费的方式不对,会造成内存崩溃的情况 一:consumer 1. 短链接:basicget 独自去获取message... request 的方式去获取,断开式. ...
- 运营-赵本山最近有点烦:二人转产业链滑铁卢 关联公司IPO预披露
http://news.cecb2b.com/info/20141117/2868962.shtml
- 对JS 的classList 简单记录
一 对classList整体感觉 浏览器支持参考(http://www.runoob.com/jsref/prop-element-classlist.html) classList 是一个对象 ,返 ...
- 使用pl/sql developer登陆不了oracle
1,Oracle ORA12514 监听程序当前无法识别连接描述符中请求的服务 这里最主要的原因在于:(参考:https://www.cnblogs.com/shangshan/p/6359880.h ...
- hadoop2-HBase的Java API操作
Hbase提供了丰富的Java API,以及线程池操作,下面我用线程池来展示一下使用Java API操作Hbase. 项目结构如下: 我使用的Hbase的版本是 hbase-0.98.9-hadoop ...
- google 历史版本浏览器下载
传送门: https://www.portablesoft.org/google-chrome-legacy-versions/