Spring Session基于Redis存储的序列化问题

    在微服务开发过程中,为了使用方便经常会将频繁访问的信息如用户、权限等放置到SESSION中,便于服务访问,而且,微服务间为了共享SESSION,通常会使用Redis共享存储。但是这样就会有一个问题,Spring boot在封装Request对象时会将当前SESSION中所有属性对象反序列化,反序列化都成功以后,将SESSION对象生成。如果有一个微服务将本地的自定义Bean对象放置到SESSION中,则其他微服务都将出现反序列化失败,请求异常,服务将不能够使用了,这是一个灾难性问题。

    如何解决这个问题呢?

    需要了解一下Spring redis SESSION 是如何进行反序列化的。

    Spring session针对Web的Request请求有一个org.springframework.session.web.http.SessionRepositoryFilter过滤器,根据SESSION ID获取相应的SESSION对象。

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter{

  ...
  private final SessionRepository<S> sessionRepository;
 ...
}

     SessionRepositoryFilter会调用sessionRepository.findById(sessionId)来查找SESSION对象,对于Redis,sessionRepository的实现类为org.springframework.session.data.redis.RedisOperationsSessionRepository,通过查看源码,可以看到,该类默认的序列化类为org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.   

public class RedisOperationsSessionRepository implements
		FindByIndexNameSessionRepository<RedisOperationsSessionRepository.RedisSession>,
		MessageListener {
    ...
    private RedisSerializer<Object> defaultSerializer = new JdkSerializationRedisSerializer();
    ...
}

    通过查询JdkSerializationRedisSerializer源码,发现该类在反序列化时如果异常会抛出SerializationException异常,而SessionRepositoryFilter又没有处理异常,故如果序列化异常时就会导致请求异常。     

public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {

	private final Converter<Object, byte[]> serializer;
	private final Converter<byte[], Object> deserializer;

	/**
	 * Creates a new {@link JdkSerializationRedisSerializer} using the default class loader.
	 */
	public JdkSerializationRedisSerializer() {
		this(new SerializingConverter(), new DeserializingConverter());
	}

	/**
	 * Creates a new {@link JdkSerializationRedisSerializer} using a {@link ClassLoader}.
	 *
	 * @param classLoader
	 * @since 1.7
	 */
	public JdkSerializationRedisSerializer(ClassLoader classLoader) {
		this(new SerializingConverter(), new DeserializingConverter(classLoader));
	}

	/**
	 * Creates a new {@link JdkSerializationRedisSerializer} using a {@link Converter converters} to serialize and
	 * deserialize objects.
	 *
	 * @param serializer must not be {@literal null}
	 * @param deserializer must not be {@literal null}
	 * @since 1.7
	 */
	public JdkSerializationRedisSerializer(Converter<Object, byte[]> serializer, Converter<byte[], Object> deserializer) {

		Assert.notNull(serializer, "Serializer must not be null!");
		Assert.notNull(deserializer, "Deserializer must not be null!");

		this.serializer = serializer;
		this.deserializer = deserializer;
	}

	public Object deserialize(@Nullable byte[] bytes) {

		if (SerializationUtils.isEmpty(bytes)) {
			return null;
		}

		try {
			return deserializer.convert(bytes);
		} catch (Exception ex) {
			throw new SerializationException("Cannot deserialize", ex);
		}
	}

	@Override
	public byte[] serialize(@Nullable Object object) {
		if (object == null) {
			return SerializationUtils.EMPTY_ARRAY;
		}
		try {
			return serializer.convert(object);
		} catch (Exception ex) {
			throw new SerializationException("Cannot serialize", ex);
		}
	}
}

    这样我们就明白了异常的来源,那就想办法解决这个异常了,如何解决这个异常呢?定制Spring redis session的序列化类,替代原有的默认的JdkSerializationRedisSerializer。

    网上也有好多关于Redis序列化定制的介绍,大部分经过验证对于Spring redis Session不起作用,而且有些定制太多,直接将sessionRepository进行了定制,这样是可以,但是感觉有点大材小用了,那应该如何定制SESSION满足系统场景需要呢?

    通过查找发现,Github spring-session ISSUE中有类似定制反序列的答案,链接如下:

    https://github.com/spring-projects/spring-session/issues/691

    通过自定义springSessionDefaultRedisSerializer对象,将会替代默认的SESSION序列化对象。如何实现这个自定义的序列化对象呢?

    这样需要回到上面我们提到的Session,Redis共享面临可能出现毁灭性事故的情形,某个微服务将自定义Bean对象保存到Session中导致其他微服务请求异常。所以,在实现定制的序列化对象时,对于反序列化失败时,一定不能抛出异常,避免系统请求中断。

    如何做到自定义的序列化对象不抛异常呢?

  1.     在原有序列化JdkSerializationRedisSerializer对象的基础上,在反序列化异常时捕获这个异常,仅记录相关日志即可。
    @Component("springSessionDefaultRedisSerializer")
    public class CustomSessionDefaultRedisSerializer extends JdkSerializationRedisSerializer {
    	
    	private static final Logger LOG = LoggerFactory.getLogger(CustomSessionDefaultRedisSerializer.class);
    	
    	public Object deserialize(@Nullable byte[] bytes) {
    		Object deserialObj = null;
    		try
    		{
    			deserialObj =  super.deserialize(bytes);
    		}
    		catch(Exception e)
    		{
    			LOG.warn("deserialize session Object error!", e);
    		}
    		return deserialObj;
    	}
    
    }

     

  2. 自定义的序列化对象在序列化与序列化中,将保存到Session中的对象都转换成JSON字符串保存,这样就要求所有保存到Session中的对象都为普通的Bean对象。这种定制序列化对象方式,没有试验,理论上应该可行。

     这是当前自己能力所限制,能够想到解决这个问题的方法,也许还有更好,更完美的方式,也可以大家一起讨论。

 

  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果您在使用 Spring Session Redis 时遇到了配置序列化无效的问题,可能是因为 RedisTemplate 使用的序列化器与 Spring Session Redis 使用的不同。为了解决这个问题,您可以尝试以下步骤: 1. 确认 RedisTemplate 使用的序列化器是否与 Spring Session Redis 使用的一致。您可以在 RedisTemplate 设置序列化器的方式如下: ```java RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(connectionFactory); redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.afterPropertiesSet(); ``` 2. 如果 RedisTemplate 使用的序列化器与 Spring Session Redis 使用的不同,可以尝试在 Spring Session Redis 的配置指定使用的序列化器: ```yaml spring: session: store-type: redis redis: namespace: myapp flush-mode: on_save cleanup-cron: '0 * * * * *' serializer: jackson ``` 3. 如果仍然无法解决问题,您可以尝试自定义 RedisTemplate,并将其注入到 Spring Session Redis : ```java @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } @Bean public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, Object> redisTemplate) { return new RedisOperationsSessionRepository(redisTemplate); } } ``` 希望这些步骤可以帮助您解决问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值