RedisTemplate EvalSha不支持集群模式

RedisTemplate访问单机和集群

1. 背景

项目开发中,需要执行Lua脚本。因为测试环境的Redis是单节点部署,所以在开发中使用如下代码是成功执行的:

   @Resource
   StringRedisTemplate stringRedisTemplate;
   
   public Object exec(RedisScript script, List<String>keys,Object... args){
        return stringRedisTemplate.execute(script,keys,args);
    }

2. 异常信息

但是准生产及生产环境的Redis是集群部署,所以在使用上述代码时会报错,EvalSha不支持集群环境:

org.springframework.dao.InvalidDataAccessApiUsageException: EvalSha is not supported in cluster environment.
	at org.springframework.data.redis.connection.jedis.JedisClusterScriptingCommands.evalSha(JedisClusterScriptingCommands.java:83)
	at org.springframework.data.redis.connection.DefaultedRedisConnection.evalSha(DefaultedRedisConnection.java:1240)
	at org.springframework.data.redis.connection.DefaultStringRedisConnection.evalSha(DefaultStringRedisConnection.java:1653)
	at sun.reflect.GeneratedMethodAccessor173.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.data.redis.core.CloseSuppressingInvocationHandler.invoke(CloseSuppressingInvocationHandler.java:61)
	at com.sun.proxy.$Proxy175.evalSha(Unknown Source)
	at org.springframework.data.redis.core.script.DefaultScriptExecutor.eval(DefaultScriptExecutor.java:77)
	at org.springframework.data.redis.core.script.DefaultScriptExecutor.lambda$execute$0(DefaultScriptExecutor.java:68)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:171)
	at org.springframework.data.redis.core.script.DefaultScriptExecutor.execute(DefaultScriptExecutor.java:58)
	at org.springframework.data.redis.core.script.DefaultScriptExecutor.execute(DefaultScriptExecutor.java:52)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:346)

3. 原因分析

RedisTemplate默认情况下拿到的connection是单机的:

调用链:
org.springframework.data.redis.core.RedisTemplate#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.lang.Object...)
org.springframework.data.redis.core.script.DefaultScriptExecutor#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.lang.Object...)
org.springframework.data.redis.core.script.DefaultScriptExecutor#execute(org.springframework.data.redis.core.script.RedisScript, org.springframework.data.redis.serializer.RedisSerializer, org.springframework.data.redis.serializer.RedisSerializer, java.util.List, java.lang.Object...)
org.springframework.data.redis.core.RedisTemplate#execute(org.springframework.data.redis.core.RedisCallback)
org.springframework.data.redis.core.RedisTemplate#execute(org.springframework.data.redis.core.RedisCallback, boolean)
org.springframework.data.redis.core.RedisTemplate#execute(org.springframework.data.redis.core.RedisCallback, boolean, boolean)
org.springframework.data.redis.core.RedisConnectionUtils#getConnection(org.springframework.data.redis.connection.RedisConnectionFactory, boolean)
org.springframework.data.redis.core.RedisConnectionUtils#doGetConnection
org.springframework.data.redis.core.RedisConnectionUtils#fetchConnection

可以看到在 org.springframework.data.redis.core.RedisConnectionUtils#fetchConnection 方法中,如果配置的是单节点的redis,获取到的连接是单机的

4. 解决方案

    @Resource
    StringRedisTemplate stringRedisTemplate;
	
    public Object exec(RedisScript script, List<String>keys,Object... args){
		// 将可变参转为 List
        List<String> list = new ArrayList<>();
        for (Object arg : args) {
            list.add(arg.toString());
        }
        // 将 RedisScript 转换为 string 类型的脚本
        String scriptScriptAsString = script.getScriptAsString();
        return stringRedisTemplate.execute((RedisCallback<Object>) connection -> {
			// 获取连接
            Object nativeConnection = connection.getNativeConnection();
            // 集群模式的实例
            if (nativeConnection instanceof JedisCluster) {
                return (Long)((JedisCluster) nativeConnection).eval(scriptScriptAsString, keys, list);
            }
            // 单机模式的实例
            if (nativeConnection instanceof Jedis) {
                return (Long)((Jedis) nativeConnection).eval(scriptScriptAsString, keys, list);
            }
            return null;
        });
    }

5. JedisCluster 和 Jedis 两种连接类型的区别

RedisTemplate EvalSha不支持集群模式_第1张图片

RedisTemplate EvalSha不支持集群模式_第2张图片

你可能感兴趣的:(Java,redis,spring,boot,springboot,lua,java)