解决spring data redis的那些坑

目录
  • spring data redis的那些坑
    • 1. 使用lua脚本,返回类型解析错误
    • 2. spring redis基于lettuce配置Client必须显示调用
  • spring data redis 的优缺点

spring data redis的那些坑

spring 的IOC很少有bug,AOPbug开始多起来,到了它的一些“玩具”一样的组件,bug无处不在。而且跟一般的开源框架不同,在github上你报告issue,会被“这不是一个bug”强行关闭。开一博文记录,给遇到同样问题而苦恼的人歇歇脚。

1. 使用lua脚本,返回类型解析错误

背景:一般来讲,就算脚本里没有return语句,redis也是会返回执行结果,看起来就像:{“Ok” = “ok”},或者{“ok”:”ok”}。然而对于一些操作redis没有返回,或者return语句后面返回一个值,spring包了的那一层壳就会出问题。影响的包:spring封装了jedis的所有版本,包括:spring-data-redis 2.0以下的所有版本,以及使用了jedis的2.0以上版本:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.0.0.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

这种情况下就会遇到

XXX cannot be cast to XXX

原因:DefaultScriptExecutor.java类中:

    public <T> T execute(final RedisScript<T> script, final RedisSerializer<?> argsSerializer,
            final RedisSerializer<T> resultSerializer, final List<K> keys, final Object... args) {
        return template.execute((RedisCallback<T>) connection -> {
            final ReturnType returnType = ReturnType.fromJavaType(script.getResultType()); // return type is wrong.
            final byte[][] keysAndArgs = keysAndArgs(argsSerializer, keys, args);
            final int keySize = keys != null ? keys.size() : 0;
            if (connection.isPipelined() || connection.isQueueing()) {
                // We could script load first and then do evalsha to ensure sha is present,
                // but this adds a sha1 to exec/closePipeline results. Instead, just eval
                connection.eval(scriptBytes(script), returnType, keySize, keysAndArgs);
                return null;
            }
            return eval(connection, script, returnType, keySize, keysAndArgs, resultSerializer);
        });
    }

而作为消费者,一般会将返回值设置为Object,因为同一个脚本里有若干的逻辑,不同情况下返回值可能是布尔型,字符串型,Number型等。

    ScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("META-INF/scripts/redis.lua"));
    DefaultRedisScript<Object> redisScript = new DefaultRedisScript<Object>();
    redisScript.setScriptSource(scriptSource);
    redisScript.setResultType(Object.class);

而DefaultScriptExecutor的execute方法,会把Object类型解析为List类型,进而设置returnType为Multi。

public Object convert(Object result) {
        if (result instanceof String) {
            // evalsha converts byte[] to String. Convert back for consistency
            return SafeEncoder.encode((String) result);
        }
        if (returnType == ReturnType.STATUS) {
            return JedisConverters.toString((byte[]) result);
        }
        if (returnType == ReturnType.BOOLEAN) {
            // Lua false comes back as a null bulk reply
            if (result == null) {
                return Boolean.FALSE;
            }
            return ((Long) result == 1);
        }
        if (returnType == ReturnType.MULTI) {
            List<Object> resultList = (List<Object>) result;
            List<Object> convertedResults = new ArrayList<>();
            for (Object res : resultList) {
                if (res instanceof String) {
                    // evalsha converts byte[] to String. Convert back for
                    // consistency
                    convertedResults.add(SafeEncoder.encode((String) res));
                } else {
                    convertedResults.add(res);
                }
            }
            return convertedResults;
        }
        return result;
    }

会因为result(原本只是一个Object),被解析为List,转换出了问题。此外,这里居然没有设置null的转换,难道null就不是List了。。。好在spring redis基于lettuce的实现不存在这个问题。

2. spring redis基于lettuce配置Client必须显示调用

从官方的reference看,spring的lettuce的配置只需要简单使用一个包含host、port、database、password等链接必须信息构造的RedisStandaloneConfiguration对象作为参数传递给LettuceConnectionFactory 的构造函数,同理连接池,然而实际使用中发现,ConnectionFactory用于建立连接的是从它的client属性获取的服务器地址等,因此必须调用afterPropertiesSet方法。

现在client信息有了,可以连接,但是连接池又未开启,尽管已经在构造器参数中指定过。受限于时间,还没有调这个点。

    LettucePoolingClientConfiguration poolingClientConfiguration = LettucePoolingClientConfiguration.builder()
        .poolConfig(new GenericObjectPoolConfig())
        .build();

    RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(
        redisProperty.getHost(),redisProperty.getPort()
    );
    redisStandaloneConfiguration.setDatabase(redisProperty.getDatabase());

    LettuceConnectionFactory cf = new LettuceConnectionFactory(redisStandaloneConfiguration, poolingClientConfiguration);
    cf.afterPropertiesSet(); // must
    StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
    stringRedisTemplate.setConnectionFactory(cf);
    setSerializer(stringRedisTemplate);

spring data redis 的优缺点

spring-data-redis是由spring的 cache api 整合 redis 而来,它的命名规则由spring cache 的规则来定义key和对key的管理,进一步弱化redis的API。

事实上redis提供的功能已经足够强大,并且可以直接使用,同时支持灵活的分库。

spring 的 cache 功能主要由 @Cacheable @CacheEvict @CachePut 实现

  • @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
  • @CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
  • @CachEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空

默认情况下Spring使用CacheManagerBean 来实现,其实现有3种:EHCache,Redis,ConcurrentHashMap,默认的ConcurrentHashMap 是没有过期的。

Redis 的使用也是要自己手动调 expire ,所以暂时使用原生的 jedis ,直接调用 redis 的api

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 解决spring集成redisson踩过的坑

    目录 spring集成redisson踩过的坑 第一坑就是版本兼容问题 第二个坑是设置密码问题 spring整合redisson配置 配置方式 单节点配置standalone 哨兵配置sentinel 集群配置cluster 主从部署方式(master/slave) spring集成redisson踩过的坑 我用spring的xml集成一直报错,所以只能选择注解方式: @Configuration public class RedissionConfig { Logger log = Logge

  • Spring-data-redis操作redis知识总结

    什么是spring-data-redis spring-data-redis是spring-data模块的一部分,专门用来支持在spring管理项目对redis的操作,使用java操作redis最常用的是使用jedis,但并不是只有jedis可以使用,像jdbc-redis,jredis也都属于redis的java客户端,他们之间是无法兼容的,如果你在一个项目中使用了jedis,然后后来决定弃用掉改用jdbc-redis就比较麻烦了,spring-data-redis提供了redis的java客

  • Spring使用redis遇到的问题及解决方案

    本人在spring中使用redis作为缓存时,遇到两个坑,现在记录如下,算是作为自己的备忘吧,文笔不好,望大家见谅: 一.配置文件 <!-- 加载Properties文件 --> <bean id="configurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locatio

  • 详解java之redis篇(spring-data-redis整合)

    1,利用spring-data-redis整合 项目使用的pom.xml: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma

  • 使用Spring Data Redis实现数据缓存的方法

    引言 目前很多系统为了解决数据读写的性能瓶颈,在系统架构设计中使用Redis实现缓存,Spring框架为了让开发人员更加方便快捷的使用Redis实现缓存,对Redis的操作进行了包装. 0.缓存 个人理解的缓存是指用于存储频繁使用的数据的空间,关注点是存储数据的空间和使用频繁的数据.缓存技术,简单的说就是先从缓存中查询数据是否存在,存在则直接返回,不存在再执行相应的操作获取数据,并将获取的数据存储到缓存中,它是一种提升系统性能的重要方法. 1.Redis Redis是一个开源的.内存存储key-

  • 解决spring data redis的那些坑

    目录 spring data redis的那些坑 1. 使用lua脚本,返回类型解析错误 2. spring redis基于lettuce配置Client必须显示调用 spring data redis 的优缺点 spring data redis的那些坑 spring 的IOC很少有bug,AOPbug开始多起来,到了它的一些"玩具"一样的组件,bug无处不在.而且跟一般的开源框架不同,在github上你报告issue,会被"这不是一个bug"强行关闭.开一博文记

  • 解决Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程问题

    待解决的问题 Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程 解决办法 为spring session添加springSessionRedisTaskExecutor线程池. /** * 用于spring session,防止每次创建一个线程 * @return */ @Bean public ThreadPoolTaskExecutor springSessionRedisTaskExecutor(){ T

  • 浅谈Spring Data Redis读不到设进去的值

    目录 目标 1 注意读.取一致性 解析 AbstractOperations#rawKey 修正 目标 精通 Spring Data Redis 操作流程. Spring Data提供了对市场上主流数据库支持: Spring Data Commons Spring Data JPA Spring Data KeyValue Spring Data LDAP Spring Data MongoDB Spring Data Redis Spring Data REST Spring Data for

  • 解决spring data jpa 批量保存更新的问题

    spring data jpa 批量保存更新问题 使用jpa批量保存时,看日志发现是一条一条打印的,然后去看了下源码,果然是循环调用的单个保存(巨坑啊) 经查询jpa是可以实现批量保存更新的,具体设置如下: spring.jpa.properties.hibernate.jdbc.batch_size=500 spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true spring.jpa.properties.hibernate

  • 解决spring data jpa saveAll() 保存过慢问题

    目录 spring data jpa saveAll() 保存过慢 问题发现 解决方案1 此方案在第二天失效了 以上方案有问题,下面附上彻底解决的截图和记录 JPA的saveAll方法执行效率很差 spring data jpa saveAll() 保存过慢 问题发现 今天在生产环境执行保存数据时 影响队列中其他程序的运行 随后加日志排查 发现 执行 4500条 insert操作时 耗时 9分钟 我类个去- 解决方案1 此方案在第二天失效了 废话不多说 直接上配置文件参数 application

  • 解决Spring Data Jpa 实体类自动创建数据库表失败问题

    目录 Spring Data Jpa 实体类自动创建数据库表失败 找了半天发现是一个配置的问题 可能导致JPA 无法自动建表的问题汇总 1.没加@Entity或引错Entity所在包 2.jpa配置中ddl-auto未设置update 3.实体类的包不是启动程序所在包的子包 4.mysql配置问题 5.依赖不全 6.实体类间关系错误 7.启动类注解问题 8.其他问题 Spring Data Jpa 实体类自动创建数据库表失败 先说一下我遇到的这个问题,首先我是通过maven创建了一个spring

  • 解决spring jpa中update的坑

    spring jpa中update遇到的坑 使用jpa 自己编写update语句, 遇到问题: 1.在同一个service事物中,先执行保存,在执行更新,紧接着执行查询--查询结果为更新前的结果. 2.执行自定义update方法结束后执行查询查出结果依然为update前的结果集 解决问题所在: 自定义update并未清空实体缓存.注解@Modifying加参数eg:@Modifying(clearAutomatically = true) jpa更新问题记录 使用jpa 去更新: @Modify

  • Spring Data JPA例子代码[基于Spring Boot、Mysql]

    关于Spring Data Spring社区的一个顶级工程,主要用于简化数据(关系型&非关系型)访问,如果我们使用Spring Data来开发程序的话,那么可以省去很多低级别的数据访问操作,如编写数据查询语句.DAO类等,我们仅需要编写一些抽象接口并定义相关操作即可,Spring会在运行期间的时候创建代理实例来实现我们接口中定义的操作. 关于Spring Data子项目 Spring Data拥有很多子项目,除了Spring Data Jpa外,还有如下子项目. Spring Data Comm

  • spring使用redis操作key-value的示例代码

    连接到 Redis Redis 连接工厂会生成到 Redis 数据库服务器的连接.Spring Data Redis 为四种 Redis 客户端实现提供了连接工厂: JedisConnectionFactory JredisConnectionFactory LettuceConnectionFactory SrpConnectionFactory 具体选择哪一个取决于你.我建议你自行测试并建立基准,进而确定哪一种 Redis 客户端和连接工厂最适合你的需求.从 Spring Data Redi

随机推荐