cache调用hincrBy返回 ERR hash value is not an integer

站长,我找到了一个简便解决方法:

cache中所有的方法都是用了ISerializer进行序列化,默认使用的是FstSerializer,但是使用FstSerializer序列化时就会遇到

hmset(Object key, Map<Object, Object> hash)和

hincrBy(Object key, Object field, long value)方法冲突问题

问题详情:http://www.jfinal.com/feedback/1801

问题原因:

hmset设置的value为序列化后的value,hincrBy()获取与field对应的value时无法将其转换为integer,出错

即使使用原始jedis操作,导致所有的hash键必须使用jedis.hmset(key,hash)设置,则所有与此key相关的方法都需要使用jedis,而不能使用cache,就丧失了JFinal框架封装jedis的意义,代码也大量充斥着重复代码:

Cache cache=Redis.user();
Jedis jedis=cache.getJedis();
...
cache.close(jedis);

不仅不美观,而且难以维护

解决方案:

编写自定义的ISerializer,再FstSerializer上做了一层简单封装,只有value为Integer时,才不使用FstSerializer

代码如下:

/**
 * 由于使用FstSerializer导致hmset与hincrBy冲突,当value为Integer时,转换为使用keyToBytes()
 */
public class MyFstSerializer implements ISerializer {
	@Override
	public byte[] keyToBytes(String key) {
		return FstSerializer.me.keyToBytes(key);
	}

	@Override
	public String keyFromBytes(byte[] bytes) {
		return FstSerializer.me.keyFromBytes(bytes);
	}

	@Override
	public byte[] fieldToBytes(Object field) {
		return valueToBytes(field);
	}

	@Override
	public Object fieldFromBytes(byte[] bytes) {
		return valueFromBytes(bytes);
	}

	@Override
	public byte[] valueToBytes(Object value) {
		if (value instanceof String) {
			return keyToBytes((String) value);
		}
		if (value instanceof Integer) {
			return keyToBytes(String.valueOf(value));
		}
		return FstSerializer.me.valueToBytes(value);
	}

	@Override
	public Object valueFromBytes(byte[] bytes) {
		return FstSerializer.me.valueFromBytes(bytes);
	}
}

而后在

public void configPlugin(Plugins me)

方法中加入

redisPlugin.setSerializer(new MyFstSerializer());

即可

缺点:由于使用jedis存放的数据中多数类型为非Integer,加了一次判断可能会导致性能有点下降,但是多了一个instanceof判断的开销,少了一个使用FstSerializer序列化Integer的开销,性能应该不会下降很多


评论区

pfjia

2017-08-22 14:53

1.上面源码有错误,错误在于valueFromBytes()直接使用FstSerializer进行反序列化,但是如果使用keyToBytes()序列化的value,反序列化使用valueFromBytes报错

pfjia

2017-08-22 14:57

源代码修改如下:
public class MyFstSerializer implements ISerializer {
public static final ISerializer me = new MyFstSerializer();
private ISerializer subject = FstSerializer.me;

private static final Set NOT_SERIALIZER_SET = new HashSet() {
private static final long serialVersionUID = 2303504492407898281L;
{
add(Integer.class);
add(Integer.TYPE);
add(Long.class);
add(Long.TYPE);
add(Float.class);
add(Float.TYPE);
add(Double.class);
add(Double.TYPE);
}
};

@Override
public byte[] keyToBytes(String key) {
return FstSerializer.me.keyToBytes(key);
}

@Override
public String keyFromBytes(byte[] bytes) {
return subject.keyFromBytes(bytes);
}

@Override
public byte[] fieldToBytes(Object field) {
return valueToBytes(field);
}

@Override
public Object fieldFromBytes(byte[] bytes) {
return valueFromBytes(bytes);
}

@Override
public byte[] valueToBytes(Object value) {
if (NOT_SERIALIZER_SET.contains(value.getClass())) {
return keyToBytes(String.valueOf(value));
}
return subject.valueToBytes(value);
}

/**
* 反序列化,利用FstSerializer解析,出错则利用keyFromBytes解析
* @param bytes
* @return
*/
@Override
public Object valueFromBytes(byte[] bytes) {
try {
return subject.valueFromBytes(bytes);
} catch (Exception e) {
return keyFromBytes(bytes);
}
}
}
修改:
1.使用了NOT_SERIALIZER_SET,仿照RestInterceptor,方便修改
2.将ISerializer设置为一变量,方便修改

JFinal

2017-08-22 15:41

这个愿忘是挺好的,就是担心可能有副作用,例如,先存入一个 Integer 型,然后取出来的时候是不是要对这个 Integer 进行反序列化

由于存的时候没有序列化,所以取出来的时候就不能反序列化,但此时怎么知道这个值是不是要反序列化?

感谢你分享

pfjia

2017-08-25 11:05

@JFinal 今天刚发现这个问题,
如果使用FstSerializer,虽然无法与hincrBy()混合使用,但是序列化与反序列化时保存了"类型信息",存一个Integer,使用hgetAll()时类型一定是Integer
如果使用MyFstSerializer,虽然可以与hincrBy()混合使用,若存一个Integer,但是序列化时:keyToBytes(String.valueOf(value))存放的是String,自然hgetAll()返回的也是String
又看了一下Jedis的方法,方法签名为: public String hmset(final String key, final Map hash),也即Jedis存放的值全部当作String,无类型区分
总结:cache使用FstSerializer就是"封装"了使用Jedis存取数据时的"类型转换",缺点是无法与hincrBy()等方法混合使用,现在使用MyFstSerializer等于将cache封装的"类型转换"暴露出来,由应用程序处理,有利也有弊

JFinal

2017-08-25 11:29

@pfjia 所有记数的 api,要读数据的话,使用 getCounter(key) 就好

pfjia

2017-08-25 17:53

@JFinal 做的是一个社交系统,如关注数这些信息存放在一个hash里,平时使用hincrBy()进行增加,不能用getCounter(key)直接获取
目前尝试写一个Rediskit.hincrBy(),使用hget()和hset()两次操作更新hash中某个域的值,效率会低一点,但是保留cache封装"类型转换"的特点

JFinal

2017-08-25 21:34

@pfjia 自定义一个 RedisExt 扩展工具类就好,好多人都是这么做的,用 jfinal 提供的 redis 插件完成绝大部分功能,无法实现的功能通过 RedisExt 完成

RedisExt 内部主要是通过 Redis.use().getJedis() 获取底层连接来实现想要的功能,注意在用完 getJedis() 连接后,要在 finally 块中 close() 掉,回收资源

pfjia

2017-08-26 12:59

@JFinal 嗯嗯,明白了,多谢站长~~

热门分享

扫码入社