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

原文地址:https://www.cnblogs.com/linkstar/p/9488733.html