SpringBoot修改Redis序列化方式
前言
由于Springboot默认提供了序列化方式并不是非常理想,对于高要求的情况下,序列化的速度和序列化之后大小有要求的情况下,不能满足,所以可能需要更换序列化的方式。
这里主要记录更换序列化的方式以及其中一些出现问题。
坑坑坑坑坑坑!!!
这次踩的坑坑。
序列化方式更换
第一步,加入依赖
//protostuff序列化依赖
compile group: 'io.protostuff', name: 'protostuff-runtime', version: '1.6.0'
compile group: 'io.protostuff', name: 'protostuff-core', version: '1.6.0'
第二步,加入序列化工具
import io.protostuff.LinkedBuffer; import io.protostuff.ProtostuffIOUtil; import io.protostuff.Schema; import io.protostuff.runtime.RuntimeSchema; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; /** * ProtoStuff序列化工具 * @author LinkinStar */ public class ProtostuffSerializer implements RedisSerializer { private boolean isEmpty(byte[] data) { return (data == null || data.length == 0); } private final Schema<ProtoWrapper> schema; private final ProtoWrapper wrapper; private final LinkedBuffer buffer; public ProtostuffSerializer() { this.wrapper = new ProtoWrapper(); this.schema = RuntimeSchema.getSchema(ProtoWrapper.class); this.buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); } @Override public byte[] serialize(Object t) throws SerializationException { if (t == null) { return new byte[0]; } wrapper.data = t; try { return ProtostuffIOUtil.toByteArray(wrapper, schema, buffer); } finally { buffer.clear(); } } @Override public T deserialize(byte[] bytes) throws SerializationException { if (isEmpty(bytes)) { return null; } ProtoWrapper newMessage = schema.newMessage(); ProtostuffIOUtil.mergeFrom(bytes, newMessage, schema); return (T) newMessage.data; } private static class ProtoWrapper { private Object data; } }
第三步,对RedisTemplate进行封装
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * Redis操作工具类 * @author LinkinStar */ @Component public class RedisUtil { @Autowired @Qualifier("protoStuffTemplate") private RedisTemplate protoStuffTemplate; /** * 设置过期时间,单位秒 * @param key 键的名称 * @param timeout 过期时间 * @return 成功:true,失败:false */ public boolean setExpireTime(String key, long timeout) { return protoStuffTemplate.expire(key, timeout, TimeUnit.SECONDS); } /** * 通过键删除一个值 * @param key 键的名称 */ public void delete(String key) { protoStuffTemplate.delete(key); } /** * 判断key是否存在 * @param key 键的名称 * @return 存在:true,不存在:false */ public boolean hasKey(String key) { return protoStuffTemplate.hasKey(key); } /** * 数据存储 * @param key 键 * @param value 值 */ public void set(String key, Object value) { protoStuffTemplate.boundValueOps(key).set(value); } /** * 数据存储的同时设置过期时间 * @param key 键 * @param value 值 * @param expireTime 过期时间 */ public void set(String key, Object value, Long expireTime) { protoStuffTemplate.boundValueOps(key).set(value, expireTime, TimeUnit.SECONDS); } /** * 数据取值 * @param key 键 * @return 查询成功:值,查询失败,null */ public Object get(String key) { return protoStuffTemplate.boundValueOps(key).get(); } }
第四步,修改默认序列化方式
import com.linkinstars.springBootTemplate.util.ProtostuffSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; /** * redisTemplate初始化,开启spring-session redis存储支持 * @author LinkinStar */ @Configuration @EnableRedisHttpSession public class RedisConfig { /** * redisTemplate 序列化使用的Serializeable, 存储二进制字节码, 所以自定义序列化类 * @Rparam redisConnectionFactory * @return redisTemplate */ @Bean public RedisTemplate<Object, Object> protoStuffTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); // redis value使用的序列化器 template.setValueSerializer(new ProtostuffSerializer()); // redis key使用的序列化器 template.setKeySerializer(new StringRedisSerializer()); template.afterPropertiesSet(); return template; } }
第五步,相应测试
//测试redis UserEntity user = new UserEntity(); user.setId(1); user.setVal("xxx"); redisUtil.set("xxx", user); Object object = redisUtil.get("xxx"); UserEntity userTemp = (UserEntity) object; System.out.println("redis数据获取为: " + userTemp); redisUtil.delete("xxx"); System.out.println("redis删除数据之后获取为: " + redisUtil.get("xxx"));
遇到问题
问题描述:序列化之后反序列化没有问题,但是强制转换成对应的类出现问题,抛出无法强制转换的异常。
问题分析:无法强制转换说明可能两个类的类加载器不一样,所以打印两者类加载器发现确实不一样。
发现其中一个类的类加载器出现了devtool的字样
所以联想到可能是热部署机制导致这样问题的产生。
然后查询网络资料,并在多台机器上进行测试,发现有的机器不会出现这样的问题,而有的机器就会出现。
如果使用Kryo进行序列化的话,第一次就会出现上述问题,而使用protostuff热部署之后才会出现上述问题。
问题解决:最后为了免除后续可能出现的问题,注释了热部署的devtool的相应依赖和相应的配置得以解决。
完整代码
github:https://github.com/LinkinStars/springBootTemplate
参考博客:
https://www.spldeolin.com/posts/redis-template-protostuff/
https://blog.csdn.net/wsywb111/article/details/79612081
https://github.com/protostuff/protostuff