在微服务开发过程中,为了使用方便经常会将频繁访问的信息如用户、权限等放置到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对象。这种定制序列化对象方式,没有试验,理论上应该可行。

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

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐