Kryo:反序列化旧版本的类

时间:2016-08-23 15:22:48

标签: scala serialization apache-spark spark-streaming kryo

我需要通过添加两个新参数来修改类。这个类是用Kryo序列化的。 我每次停止播放时,目前都会将这个类的相关信息保存为RDD。 当我重新启动流时,我加载了之前持久化的信息,并使用它们在我停止和重新启动之间保持一致。

由于我坚持的课程需要这些新参数,我通过为新参数添加新的kryo.writeObject(output, object, ObjectSerializer)kryo.readObject(input, classOf[Object], ObjectSerializer)来更改了类和序列化程序。

现在,每当我重新启动我的流时,我都会获得一个例外:"遇到未注册的班级......"。

这似乎是显而易见的,因为我试图反序列化一个对象,这个对象在我停止流时保留的信息中没有包含。 如果我删除这些数据并启动流,就好像它没有任何先前的运行一样,那么就不会发生异常。

有没有办法避免这种异常? 也许通过指定一些默认值以防这些参数丢失?

谢谢

修改

我找到了一些我以前没见过的有用的东西: Kryo issue 194

这个人通过简单地插入一个很长的定义他应该使用哪个版本的反序列化器来实现版本控制。 这是一个简单的解决方案但是,由于编写我正在编写的代码的公司没有考虑向前兼容性,我想我必须抛弃所有数据的窗口在新的序列化程序之前一直存在。

如果有人能提出更好的解决方案,请告诉我。

编辑2:

仍有这种情况的问题。 我尝试使用此处描述的CompatibleFieldSerializer:CompatibleFieldSerializer Example 因此,通过注册此序列化程序而不是先前使用的自定义序列化程序。 结果是,现在,当重新加载持久数据时,它会给出java.lang.NullPointerException。 如果之前没有数据持续存在,仍然没有问题。我可以启动我的流,序列化新数据,停止流,反序列化并重新启动我的流。 对决议仍然没有任何线索。

1 个答案:

答案 0 :(得分:3)

问题的解决方案是在几个月前发现的。所以我想尽快发布这个问题的答案。 问题在于,由于代码中的错误,该类被标准化的Kryo FieldSerializer序列化,该标准不是向前兼容的。 我们必须执行以下操作来反序列化旧类并将其转换为新的序列化类。

情况是:

BankList

它是这样序列化的:

case class ClassA(field1 : Long, field2 : String)

循环包含要用序列化程序序列化的类的Seq,以便为所有类注册所有序列化程序。

object ClassASerializer extends Serializer[ClassA] with Serializable{
  override def write(kryo: Kryo, output: Output, t: ClassA) = {
      output.writeLong    { t.field1 }
      output.writeString  { t.field2 }
 }
  override def read(kryo: Kryo, input: Input, aClass: Class[ClassA]) = 
       classA( 
           field1 = input.readLong(),
           field2 = input.readLong()
       )

需要通过添加新字段来修改类,该字段是另一个案例类的实例。

为了执行此类更改,我们必须使用与Kryo库“可选”相关的注释,

    protected def registry: Seq[aClass: Class[A], serializer: Serializer[A]] = ...
    final def register(kryo: Kryo) = {
         registry.foreach { registrable => kryo.register(registrable.aClass, registrable.serializer) }
    }

修改了序列化程序,例如在读取旧的序列化类时,它可以使用默认值实例化field3,并在写入时写入这样的默认值:

...
import com.esotericsoftware.kryo.serializers.FieldSerializer.Optional
import scala.annotation.meta.field
...

case class ClassA(field1 : Long, field2 : String,  @(Optional @field)("field3") field3 : ClassB)

还修改了kryo序列化程序注册以注册可选字段:

object ClassASerializer extends Serializer[ClassA] with Serializable{
  override def write(kryo: Kryo, output: Output, t: ClassA) = {
      output.writeLong    { t.field1 }
      output.writeString  { t.field2 }
      kryo.writeObject(output, Option { t.field3 } getOrElse ClassB.default, ClassBSerializer)

 }
  override def read(kryo: Kryo, input: Input, aClass: Class[ClassA]) = 
       ClassA( 
           field1 = input.readLong(),
           field2 = input.readLong(),
           field3 = ClassB.default
       )

结果我们能够编写序列化类的新版本。 之后,我们必须删除可选的注释,修改序列化程序以便从新的序列化类中读取实际字段,并删除可选的序列化程序注册并将其添加到注册表Seq。

与此同时,我们纠正了强制通过FieldSerializer进行序列化的代码中的错误,但这不在问题的范围内。