Solve those pits of spring data redis

  • 2021-11-14 05:37:39
  • OfStack

Those pits in the directory spring data redis 1. Using lua script, return type resolution error 2. spring redis Configuring Client based on lettuce must show advantages and disadvantages of calling spring data redis

Those pits in spring data redis

spring's IOC rarely has bug, AOPbug begins to increase, and bug is everywhere when it comes to its "toy" components. And unlike a common open source framework, if you report issue on github, it will be forced to shut down by "This is not an bug". Open a blog post to record, and give people who are distressed by the same problem a rest.

1. Using the lua script, return type resolution errors

Background: 1 Generally speaking, redis will return execution results even if there is no return statement in the script, which looks like: {"Ok" = "ok"}, or {"ok": "ok"}. However, if redis does not return, or return returns a value after the statement, spring will have a problem with the 1-layer shell wrapped in spring. The package affected: spring encapsulates all versions of jedis, including all versions below spring-data-redis 2.0, and versions above 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>

In this case, you will encounter

XXX cannot be cast to XXX

Reason: In the DefaultScriptExecutor. java class:


    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);
        });
    }

As a consumer, 1 will generally set the return value to Object, because there are several logics in the same script, and the return value may be Boolean, string, Number and so on under different circumstances.


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

The execute method of DefaultScriptExecutor resolves Object type to List type, and then sets returnType to 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;
    }

Because result (originally only one Object) is resolved to List, there is a problem in conversion. In addition, the conversion of null is not set here, so null is not List. . . Fortunately, the implementation of spring redis based on lettuce does not have this problem.

2. spring redis Configuring Client based on lettuce must display calls

According to the official reference, the configuration of lettuce of spring only needs to simply use an RedisStandaloneConfiguration object containing host, port, database, password and other link necessary information as a parameter to pass to the constructor of LettuceConnectionFactory, which is the same as the connection pool. However, in actual use, it is found that ConnectionFactory is used to establish a connection from its client attribute.

Now that the client information is available, you can connect, but the connection pool is not turned on, although it has been specified in the constructor parameters. Limited by time, this point has not been adjusted yet.


    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);

Advantages and disadvantages of spring data redis

spring-data-redis comes from the integration of cache api of spring and redis. Its naming rules define key and manage key by the rules of spring cache, and further weaken API of redis.

In fact, the functionality provided by redis is powerful enough and can be used directly, while supporting flexible sub-libraries.

The cache function of spring is mainly implemented by @ Cacheable @ CacheEvict @ CachePut

@Cacheable Mainly for method configuration, the results can be cached according to the request parameters of the method @CachePut Mainly for method configuration, it can cache the results according to the request parameters of the method. Unlike @ Cacheable, it triggers the call of the real method every time @CachEvict Mainly for method configuration, the cache can be emptied according to the conditions specified in 1

By default, Spring is implemented by CacheManagerBean. In fact, there are three kinds: EHCache, Redis and ConcurrentHashMap. The default ConcurrentHashMap has not expired.

The use of Redis is to manually adjust expire, so temporarily use native jedis, directly call api of redis


Related articles: