spring data redis 通过kryo进行序列化遇到的问题和解决问题的过程
改造项目redis的用法,采用spring data redis来处理redis的存取。
问题1:
主要问题是redisTemplate对key和value的序列化,对key采用了spring的StringRedisSerializer。对value采用了kryo的序列化方法。在对value的取值时,遇到了问题,成功的存入了redis,但取出来的byte无法反序列化。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<description>
Configuration for the default TicketRegistry which stores
the tickets in-memory and
cleans them out as specified intervals.
</description>
<!-- redis数据源 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲数 -->
<property name="maxIdle" value="20" />
<!-- 最大空连接数 -->
<property name="maxTotal" value="1000" />
<!-- 最大等待时间 -->
<property name="maxWaitMillis" value="3000" />
</bean>
<!-- Spring-redis连接池管理工厂 -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:poolConfig-ref="poolConfig"
p:usePool="true"
p:hostName="${siam.redis.host}"
p:port="${siam.redis.port}"
p:password="#{password == '' ? null : password}"
p:database="0">
</bean>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="com.bizenit.idm.siam.redis.KryoRedisSerializer" >
<constructor-arg>
<value>com.bizenit.idm.siam.ticket.Ticket</value>
</constructor-arg>
</bean>
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
<!--开启事务 -->
<property name="enableTransactionSupport" value="true"></property>
</bean>
<!-- Ticket Registry -->
<bean id="ticketRegistry" class="com.bizenit.idm.siam.ticket.registry.RedisCacheTicketRegistry"
c:redisTemplate-ref = "redisTemplate"
c:tgtTimeout="${tgt.timeToKillInSeconds}"
c:stTimeout="${st.timeToKillInSeconds}"
c:ltTimeout="${lt.timeToKillInSeconds}" />
因为项目做单点登录,前期的票据LT比较简单,可以成功的序列化存储值,但在对TGT的反序列化则失败了。最主要的异常可以参看如下:
Caused by: java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableMap.put(Collections.java:1457) ~[?:1.8.0_172]
at com.esotericsoftware.kryo.serializers.MapSerializer.read(MapSerializer.java:162) ~[kryo-4.0.2.jar:?]
at com.esotericsoftware.kryo.serializers.MapSerializer.read(MapSerializer.java:39) ~[kryo-4.0.2.jar:?]
at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:734) ~[kryo-4.0.2.jar:?]
at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:?]
... 195 more
查看代码发现是TGT类中的参数中attributes采用的Map是经过Collections.unmodifiableMap()处理的,这样的Map是不允许修改的。所以在反序列化的时候,反序列化了这个不可修改的Map,Map中的数据是无法写入到这个反序列化的Map中的,就会丢出异常java.lang.UnsupportedOperationException。
在网上差了些资料,在kryo的反序列化时,UnmodifiableCollectionsSerializer.registerSerializers(kryo)通过这样一段代码来将这种不可修改的Map,List或Set进行处理。这样处理后,问题得到了解决。
kryo的序列化和反序列化
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return EMPTY_BYTE_ARRAY;
}
Kryo kryo = kryos.get();
kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
kryo.setReferences(false);
kryo.register(clazz);
UnmodifiableCollectionsSerializer.registerSerializers(kryo);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos)) {
kryo.writeClassAndObject(output, t);
output.flush();
return baos.toByteArray();
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
return EMPTY_BYTE_ARRAY;
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
Kryo kryo = kryos.get();
kryo.setReferences(false);
kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
kryo.register(clazz);
UnmodifiableCollectionsSerializer.registerSerializers(kryo);
try (Input input = new Input(bytes)) {
return (T) kryo.readClassAndObject(input);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
return null;
}
这其中引用的kryo版本为:
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
<version>0.42</version>
</dependency>
kryo-serializers是用来解决上面所述问题的。
问题2:
单点登录的过程是需要多次取票据TGT的,在解决上述问题后,第一次成功取到后,第二次又无法取到值了,key值是没有问题的,直接通过redis-cli是可以取到redis中的数据的,说明数据没有问题。
通过跟踪代码,看源码是这样一段代码:
public byte[] get(byte[] key) {
try {
if (isPipelined()) {
pipeline(new JedisResult(pipeline.get(key)));
return null;
}
if (isQueueing()) {
transaction(new JedisResult(transaction.get(key)));
return null;
}
return jedis.get(key);
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
在执行第二个if时,进入到了if中,返回了null值,没有取到数据。看判断条件
public boolean isQueueing() {
return client.isInMulti();
}
查了一下跟事务有关,在redisTemplate配置的xml中是开启了事务的
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="com.bizenit.idm.siam.redis.KryoRedisSerializer" >
<constructor-arg>
<value>com.bizenit.idm.siam.ticket.Ticket</value>
</constructor-arg>
</bean>
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
<!--开启事务 -->
<property name="enableTransactionSupport" value="true"></property>
</bean>
将事务关闭,enableTransactionSupport调整为false,经过测试是可以取到需要的数据的。
会不会引起新的问题还待进一步的测试确认。
技术有限,有什么不对的欢迎指正,有需要的朋友可以多多交流。qq:740273040
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)