RedisTemplate执行lua脚本在Redis集群模式下报错EvalSha is not supported in cluster environment.

文章描述了在使用SpringDataRedis的RedisTemplate执行lua脚本时,在集群模式下遇到EvalShaisnotsupportedinclusterenvironment的错误。原因是RedisTemplate默认使用的是单机模式的执行方式。解决方案是通过检查连接类型,分别对JedisCluster和Jedis实例进行不同的操作,以支持在集群模式下执行lua脚本。
摘要由CSDN通过智能技术生成

RedisTemplate执行lua脚本在Redis集群模式下报错:EvalSha is not supported in cluster environment.

执行方法,Redis是单节点执行成功但是集群下报错

public boolean unlock(String key, String value) {
    Long result = template.execute(redisScript, Arrays.asList(key),Arrays.asList(value));
    return RELEASE_SUCCESS.equals(result);
}
org.springframework.dao.InvalidDataAccessApiUsageException: EvalSha is not supported in cluster environment.
	at org.springframework.data.redis.connection.jedis.JedisClusterScriptingCommands.evalSha(JedisClusterScriptingCommands.java:83) ~[spring-data-redis-2.1.8.RELEASE.jar:2.1.8.RELEASE]
	at org.springframework.data.redis.connection.DefaultedRedisConnection.evalSha(DefaultedRedisConnection.java:1318) ~[spring-data-redis-2.1.8.RELEASE.jar:2.1.8.RELEASE]
	at org.springframework.data.redis.connection.DefaultStringRedisConnection.evalSha(DefaultStringRedisConnection.java:1729) ~[spring-data-redis-2.1.8.RELEASE.jar:2.1.8.RELEASE]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_181]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_181]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_181]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_181]
	at org.springframework.data.redis.core.CloseSuppressingInvocationHandler.invoke(CloseSuppressingInvocationHandler.java:61) ~[spring-data-redis-2.1.8.RELEASE.jar:2.1.8.RELEASE]
	at com.sun.proxy.$Proxy196.evalSha(Unknown Source) ~[?:?]

原因

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

解决

集群模式直接抛出不支持执行脚本的异常,因为只能拿到原redis的connection来执行脚本

public boolean unlock(String key, String value) {
     //使用Lua脚本:先判断是否是自己设置的锁,再执行删除
     // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
     // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本

     List<String> keys = new ArrayList<>();
     keys.add(key);
     List<String> args = new ArrayList<>();
     args.add(value);

     Long result = template.execute(new RedisCallback<Long>() {
         @Override
         public Long doInRedis(RedisConnection connection) throws DataAccessException {
             Object nativeConnection = connection.getNativeConnection();
             // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
             // 集群模式
             if (nativeConnection instanceof JedisCluster) {
                 return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
             }

             // 单机模式
             else if (nativeConnection instanceof Jedis) {
                 return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
             }
             return 0L;
         }
     });

     //返回最终结果
     return RELEASE_SUCCESS.equals(result);
 }

@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;
    });
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Redis集群中,Java可以通过以下两种方式来让Lua脚本在固定节点上执行: 1. 直接指定节点:可以通过JedisCluster的getClusterNodes()方法获取到Redis集群中所有节点的信息,然后根据节点信息选择一个固定的节点来执行Lua脚本。例如,可以使用JedisClustereval()方法来执行Lua脚本,并指定需要执行脚本的节点,如下所示: ``` JedisCluster jedisCluster = new JedisCluster(nodes, config); String script = "return redis.call('get', KEYS[1])"; String result = jedisCluster.eval(script, 1, "key1").toString(); ``` 在这个例子中,我们使用eval()方法执行了一个简单的Lua脚本,返回了键为“key1”的值。eval()方法的第二个参数指定了脚本的参数个数,第三个参数指定了脚本执行的键。 2. 自定义分片算法:Java还可以通过自定义分片算法,将相同的键映射到同一个节点上执行脚本。例如,可以使用JedisCluster的setSlot()方法将相同的键映射到同一个节点上,然后再执行Lua脚本。示例如下: ``` JedisCluster jedisCluster = new JedisCluster(nodes, config); String script = "return redis.call('get', KEYS[1])"; String key = "key1"; int slot = JedisClusterCRC16.getSlot(key); jedisCluster.setSlot(key, slot); String result = jedisCluster.eval(script, 1, key).toString(); ``` 在这个例子中,我们使用JedisClusterCRC16的getSlot()方法获取键“key1”对应的槽号,然后使用setSlot()方法将键“key1”映射到对应的节点上。最后,我们使用eval()方法执行Lua脚本,并指定需要执行脚本的键。 需要注意的是,在使用自定义分片算法时,需要保证相同的键映射到同一个节点上执行,否则会导致脚本执行结果不一致的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讓丄帝愛伱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值