ute执行lua脚本踩坑记录

阅读: 评论:0

ute执行lua脚本踩坑记录

一波三折

目录

EvalSha is not supported in cluster environment

No way to dispatch this command to Redis Cluster because keys have different slots

Jedis does not support password protected Redis Cluster configurations!

背景:最近项目新引入redis,代码中为了保证redis原子操作,大部分操作都使用了lua脚本,redisTemplate的API如下

本地Redis单机模式下测试没问题

因生产环境下Cluster集群部署,稳妥起见本地搭一个三主三从集群测试,报错如下

EvalSha is not supported in cluster environment

org.springframework.dao.InvalidDataAccessApiUsageException: EvalSha is not supported in cluster environment.at org.tion.jedis.JedisClusterConnection.evalSha(JedisClusterConnection.java:3568)

从ute跟踪源码到org.tion.jedis.JedisClusterConnection.evalSha,发现spring-redis的jedis集群连接不支持evalsha操作

jar包版本:spring-data-redis-1.7.1.RELEASE.jar

	public <T> T eval(byte[] script, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {throw new InvalidDataAccessApiUsageException("Eval is not supported in cluster environment.");}public <T> T evalSha(String scriptSha, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {throw new InvalidDataAccessApiUsageException("EvalSha is not supported in cluster environment.");}public <T> T evalSha(byte[] scriptSha, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {throw new InvalidDataAccessApiUsageException("EvalSha is not supported in cluster environment.");}

解决:换种API&#ute(RedisCallback<T> call) 

代码中获取jedis客户端的连接对象进行eval,如下代码

	@SuppressWarnings({ "rawtypes", "unchecked" })public <T> T execute(final RedisScript<T> script, final List<String> keys, final Object args[]) {ute(new RedisCallback<T>(){@Overridepublic T doInRedis(RedisConnection connection) throws DataAccessException {Object nativeConnection = NativeConnection();// redis序列化key、value、lua脚本RedisSerializer keySerializer = KeySerializer();RedisSerializer valueSerializer = ValueSerializer();RedisSerializer<String> stringSerializer = StringSerializer();List<byte[]> keys_ByteArr = new ArrayList<byte[]>(keys.size()); List<byte[]> args_ByteArr = new ArrayList<byte[]>(args.length); for (int i = 0; i < keys.size(); i++) {keys_ByteArr.add(keySerializer.(i)));}for (int j = 0; j < args.length; j++) {args_ByteArr.add(valueSerializer.serialize(args[j]));}byte[] scriptByte = stringSerializer.ScriptAsString());// 集群模式if (nativeConnection instanceof JedisCluster) {return (T) ((JedisCluster) nativeConnection).eval(scriptByte, keys_ByteArr, args_ByteArr);}// 单机模式else {return (T) ((Jedis) nativeConnection).eval(scriptByte, keys_ByteArr, args_ByteArr);}}});}

先连接redis单机测试,测试通过,再连接集群环境,其他lua都没问题,就只有下边的lua报错

No way to dispatch this command to Redis Cluster because keys have different slots

(两个redis操作, 1) set集合sadd添加一个对象元素的ID  2)保存对象数据为hash结构,对象ID为key, 对象属性为hashkey,属性值为hashvalue,为了保证原子操作,使用了lua脚本,入参key两个,入参argv数组每两个一对作为hashkey-hashvalue) 

// lua脚本原子操作redisStringBuilder sb = new StringBuilder();sb.append(" local a = redis.call('sadd', KEYS[1], ARGV[1]) ");sb.append(" if (a == 1) then ");sb.append("    local hashkey ");sb.append("    for i,v in ipairs(ARGV) do ");// 循环传入的argv数组sb.append("        if (i > 1) then ");// 跳过第一个value,此后的每两个一对保存为hashkey-hashvaluesb.append("            if (i % 2 == 0) then ");sb.append("                hashkey = v ");sb.append("            else ");sb.append("                redis.call('hset', KEYS[2], hashkey, v) ");sb.append("            end ");sb.append("        end ");sb.append("    end ");sb.append(" else ");sb.append("    return 0 ");sb.append(" end ");sb.append(" return 1 ");
redis.ptions.JedisClusterException: No way to dispatch this command to Redis Cluster because keys have different slots.at redis.clients.jedis.JedisClusterCommand.runBinary(JedisClusterCommand.java:74)at redis.clients.jedis.BinaryJedisCluster.eval(BinaryJedisCluster.java:1242)at com.dw.pub.RedisUtil$1.doInRedis(RedisUtil.java:102)

再次跟踪源码到redis.clients.jedis.JedisClusterCommand.runBinary(),jedis操作lua脚本入参key经过hash计算后的slot必须是相同的,否则就报错,因为上述lua脚本入参有两个,slot不同所以报错

  public T runBinary(int keyCount, byte[][] keys) {if ((keys == null) || (keys.length == 0)) {throw new JedisClusterException("No way to dispatch this command to Redis Cluster.");}if (keys.length > 1) {int slot = Slot(keys[0]);for (int i = 1; i < keyCount; i++) {int nextSlot = Slot(keys[i]);if (slot != nextSlot) {throw new JedisClusterException("No way to dispatch this command to Redis Cluster because keys have different slots.");}}}return runWithRetries(keys[0], directions, false, false);}// 如果key中存在{},只crc大括号中的字符public static int getSlot(String key) {int s = key.indexOf("{");if (s > -1) {int e = key.indexOf("}", s + 1);if ((e > -1) && (e != s + 1)) {key = key.substring(s + 1, e);}}return getCRC16(key) & 0x3FFF;}

解决:从上方源码可以看出,计算slot只计算大括号{}中的串,因此判断如果是集群,key增加{}前缀

	private static String dealKeyPrefix(String str) {if (redisUtil.isCluster()) {str = "{cluster:}" + str;// 集群模式下使用}return str;}

生产库有密码,再在本机的集群加上密码再次测试,淦报错如下:

Jedis does not support password protected Redis Cluster configurations!

java.lang.IllegalArgumentException: Jedis does not support password protected Redis Cluster configurations!at org.tion.ateCluster(JedisConnectionFactory.java:299)at org.tion.ateCluster(JedisConnectionFactory.java:273)at org.tion.jedis.JedisConnectionFactory.afterPropertiesSet(JedisConnectionFactory.java:235)

跟源码spring-redis.jar,发现如果是集群,又发现设置了密码,直接报错如上

解决:百度之后,是jedis2.8之前不支持集群设置密码,升级jar包后解决

目前的报错jar包版本:

jedis-2.8.1.jar

spring-data-redis-1.7.1.RELEASE.jar

升级后版本:

jedis-2.9.0.jar

spring-data-redis-1.8.6.RELEASE.jar

本文发布于:2024-02-01 13:56:08,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170676696937076.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:脚本   RedisTemplate   execute   lua
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23