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

标题@TORedisTemplate执行lua脚本在Redis集群模式下报错EvalSha is not supported in cluster environme

异常信息:

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) ~[?:?]

代码示例:

	// 
private static final String SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
public void test(String phone) {
String uuid = UUID.randomUUID().toString();
String prizeId = "10000";
	// 对prizeId 为“10000”的奖品加锁
try {
    Boolean addLock = redisTemplate.opsForValue().setIfAbsent(prizeId, uuid, 5000, TimeUnit.MILLISECONDS);
	if (addLock) {
	// 对该奖品进行操作。。。。
	System.out.println(phone+"1111111拿到锁==========");
	// 操作完毕,进行释放锁
RedisScript<Long> redisScript = new DefaultRedisScript<>(SCRIPT, Long.class);
 Long resp= redisTemplate.execute(redisScript, Collections.singletonList(prizeId), uuid);
     System.out.println(phone+"释放锁结果:========"+resp);
    }else {
            System.out.println(phone+"2222未拿到锁============");
        }

    }catch (Exception e){
        System.out.println(phone+"异常");
    }
}

执行(释放锁)如下第二步骤,集群下报错:
RedisScript redisScript = new DefaultRedisScript<>(SCRIPT, Long.class);
redisTemplate.execute(redisScript, Collections.singletonList(prizeId), uuid);

解决方案

原因:spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本。
下面展示一些 内联代码片

private static final String SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
public void test(String phone) {
    String uuid = UUID.randomUUID().toString();
    String prizeId = "10000";
    try {
Boolean addLock = redisTemplate.opsForValue().setIfAbsent(prizeId,uuid,80000, TimeUnit.MILLISECONDS);
        if (addLock) {
            System.out.println(phone+"1111111拿到锁==========");
         
            Boolean resp = unlock(prizeId,uuid);
            System.out.println(phone+"释放锁结果========"+resp);
        }else {
            System.out.println(phone+"2222未拿到锁=========");
        }
    }catch (Exception e){
        System.out.println(phone+"异常");
    }
}

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

    List<String> keys = new ArrayList<>();
    keys.add(prizeId);
    List<String> args = new ArrayList<>();
    args.add(uuid);

    Boolean resp = true;
    Long result = redisTemplate.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(SCRIPT, keys, args);
            }

            // 单机模式
            else if (nativeConnection instanceof Jedis) {
                return (Long) ((Jedis) nativeConnection).eval(SCRIPT, keys, args);
            }
            return 0L;
        }
    });
   	 if (result == 1L){
    	   resp = true;
   	 }else {
       		 resp = false;
  	  }
    return resp;
}

参考文档:
https://my.oschina.net/dengfuwei/blog/1600681
https://cnsyear.com/posts/36ce5187.html

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值