暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring mvc redis 扩展 Kyro 序列化方式

古时的风筝 2021-04-25
1532

Redis 可以说是服务端开发技术的标配,任何一个在线并有一些访问量的系统,例如电商系统、内容网站等,都需要用到 Redis 。Redis 的作用不仅仅是我们熟知的缓存,由于其实内存型数据库并且拥有丰富的数据类型,它还可以用于其他场景,例如消息队列、Pub/Sub 模式的实时聊天、计数器、数据重拍等。所以说,你不懂 Redis 都不好意说是自己是一个服务端开发攻城狮。

Redis 有多种语言版本的客户端,Java 版本中,最流行的就是 Jedis。而 Spring 在 Jedis 的基础上,封装了 spring-data-redis,使用 spring-data-redis 只需要配置连接池、配置连接工厂、配置 RedisTemplate 然后直接使用即可。这里简单说下配置:

1.在 propertites 配置文件中生命 redis 参数

 redis.maxIdle=300
redis.maxWait=3000
redis.testOnBorrow=false
redis.timeout=3000
redis.host=127.0.0.1
redis.port=32768
redis.password=yourpassword

2.新建一个 redis.xml 配置文件,配置连接池、连接工厂bean、RedisTemplate 等

首先配置 JedisPoolConfig ,表示使用连接池;

接着配置 JedisConnectionFactory,这是连接工厂类配置;

最后配置 RedisTemplate,这里指定使用的连接工厂和各种类型的序列化方式(这是本文的中点,稍后说到

 <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="maxIdle" value="${redis.maxIdle}"/>
    <property name="maxWaitMillis" value="${redis.maxWait}"/>
    <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean>
<bean id="jedisConnFactory"
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
    p:use-pool="true"
    p:poolConfig-ref="poolConfig"
    p:port="${redis.port}"
    p:password="${redis.password}"
    p:hostName="${redis.host}"
    p:database="1"
    p:timeout="${redis.timeout}"/>
<bean id="redisTemplate"
    class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnFactory"
    p:keySerializer-ref="stringRedisSerializer"
    p:hashKeySerializer-ref="stringRedisSerializer"
    p:valueSerializer-ref="jdkRedisSerializer"
    p:hashValueSerializer-ref="jdkRedisSerializer"/>
<bean id="jdkRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>

3.在 spring mvc 配置文件中引用 redis.xml。

 <import resource="redis.xml"></import>

配置完成,接下来就是使用,由于 RedisTemplate 就是普通的 bean,所以直接自动注入就可以了。新建 RedisCache.java 的类,内容如下:

 @Repository
public class RedisCache{
    @Resource(name = "redisTemplate")
    private RedisTemplate<String,Object> redisTemplate;
    public <T> void put(String key, T value, long seconds){
        redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
    }
    public <T> T get(String key, Class<T> type){
        Object obj = redisTemplate.opsForValue().get(key);
        return obj != null ? (T)obj : null;
    }
    public void remove(String key){
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }
    public boolean exists(final String key){
        return redisTemplate.hasKey(key);
    }
}

以上整个配置和使用很简单,此处只是考虑单实例 redis 的情况,不包括集群,集群的使用方式可以查看我之前的一篇文章(http://www.fengzheng.pub/archives/120.html)

接下来,说到本文的重点了。回想我们用到 redis 的场景,大多数情况下并不是存储基本数据类型,而是存储一些自定义实体对象。为了压缩数据的大小,会用到序列化。 spring-data-redis 主要提供了 JdkSerializationRedisSerializer、GenericJackson2JsonRedisSerializer 两种序列化的方式,但是性能并不是最好的,尤其 JdkSerializationRedisSerializer,无论是序列化后的大小还是序列化速度,都比较差。基于这种情况,我们扩展 Kryo 这种性能比较好的序列化方式进来。

通过查看上面提到的两个序列化类,扩展第三方序列化只需要做很简单的实现就可以。

1.实现 RedisSerializer 接口

2.实现序列化方法 byte[] serialize(T var1) throws SerializationException

3.实现反序列化方法 T deserialize(byte[] var1) throws SerializationException

基于以上分析,我们最终实现的序列化类 KryoSerizlizationRedisSerialize.java 内容如下:

 package com.kite.redis;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.ByteBufferOutput;
import com.esotericsoftware.kryo.io.Input;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
/**
 * KryoSerizlizationRedisSerializer
 *
 * @author fengzheng
 */
public class KryoSerizlizationRedisSerializer<T> implements RedisSerializer<T> {
    private Kryo serializer = new Kryo();
    private Kryo deserializer = serializer;
    private final byte[] EMPTY_BYTE = new byte[0];
    public T deserialize(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return null;
        } else {
            try {
                Input input = new Input(bytes);
                return (T) this.deserializer.readClassAndObject(input);
            } catch (Exception var3) {
                throw new SerializationException("Cannot deserialize", var3);
            }
        }
    }
    public byte[] serialize(Object object) {
        if (object == null) {
            return EMPTY_BYTE;
        } else {
            try {
                ByteBufferOutput bufferOutput = new ByteBufferOutput(1,4096);
                System.out.println(this.serializer == null);
                this.serializer.writeClassAndObject(bufferOutput, object);
                byte[] result = bufferOutput.toBytes();
                bufferOutput.flush();
                return result;
            } catch (Exception var3) {
                throw new SerializationException("Cannot serialize", var3);
            }
        }
    }
}

回过头来在 redis.xml 中修改序列化方式,看一下上面配置的 RedisTemplate 。

 <bean id="redisTemplate"
          class="org.springframework.data.redis.core.RedisTemplate"
          p:connection-factory-ref="jedisConnFactory"
          p:keySerializer-ref="stringRedisSerializer"
          p:hashKeySerializer-ref="stringRedisSerializer"
          p:valueSerializer-ref="jdkRedisSerializer"
          p:hashValueSerializer-ref="jdkRedisSerializer"/>
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    <bean id="jdkRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
    

其中 keySerializer-ref 和 hashKeySerializer-ref 都是用的 StringRedisSerializer,而 valueSerializer-ref 和 hashValueSerializer-ref 用的是 JdkSerializationRedisSerializer。

我们将 Kyro 序列化类引入

 
<bean id="kryoRedisSerializer" class="com.ebanghu.redis.KryoSerizlizationRedisSerializer"/>

然后修改 valueSerializer-ref 和 hashValueSerializer-ref 的引用

 <bean id="jacksonRedisTemplate"
          class="org.springframework.data.redis.core.RedisTemplate"
          p:connection-factory-ref="jedisConnFactory"
          p:keySerializer-ref="stringRedisSerializer"
          p:hashKeySerializer-ref="stringRedisSerializer"
          p:valueSerializer-ref="kryoRedisSerializer"
          p:hashValueSerializer-ref="kryoRedisSerializer"/>

之后的调用方式不变。

Redis 自动序列化原理

关键就在 RedisTemplate 类,在调用 RedisTemplate 提供的各种数据类型的 set 方法时,会先看有没有指定序列化器(例如 hashValueSerializer、hashKeySerializer ),如果有指定就用这个序列化方式,否则用默认序列化方式,默认的序列化就是 JdkSerializationRedisSerializer。

同理,get 方法也是同样,如果有提供就用指定的方式做反序列化,否则用默认的 JdkSerializationRedisSerializer。

古时的风筝 【微信号】gushidefengzheng

文章转载自古时的风筝,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论