简单聊聊Kryo serialization

Spark默认使用Java的序列化和反序列化机制,即使用Java的ObjectOutputStream框架,支持所有继承于java.io.Serializable对象的序列化。但在Java序列化时,并没有对序列化的byte做任何处理,序列化的元素也太多了:包括开发、类描述、字段值、类的版本、元数据、字段描述和字段值等信息,造成Java序列化速度慢,序列化后的byte数组大。

因此,Spark又提供了另一种高效的序列化方法:Kryo。Kryo是一个快速高效的Java对象图形序列化框架,原生支持Java,且在Java的序列化上甚至优于Google著名的序列化框架protobuf。那,Kryo为什么那么快呢?

Kryo为了保证序列化的高效性,不会序列化对象的field描述信息,且对int和long使用变长存储。这样,序列化后的对象大小就比较小,且由于Kryo只会在第一次序列化类时才去加载类,后续直接保存了类的信息,整体的序列化速度就会提升很明显。详细研究的话,读者可以对比上述两种序列化后的byte内容。

好了,简单了解了Kryo的基本信息后,我们来看看如何在Spark中使用Kryo serialization。
Spark中使用Kryo serialization

如下示例,在初始化SparkContext之前,需指定使用Kryo serialization。

object KryoSparkTest {
  def main(args: Array[String]) {
    val conf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("KryoSparkTest")

    // 指定使用Kryo序列化
    conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
    conf.set("spark.kryo.registrator", "com.gjl.kryotest.MyRegistrator")

    val sc = new SparkContext(conf)
    val rdd = sc.textFile("src/main/resources/hello.txt")
    val srcMap = rdd.map(f => {
      val line = f.split(",")
      val hello = new Hello
      hello.setA(line(0).toInt)
      hello.setId(line(1).toLong)
      hello.setWords(line(2))
      hello
    })
    srcMap.persist(StorageLevel.MEMORY_AND_DISK_SER)

    println(srcMap.count())
  }
}

其中,MyRegistrator的定义如下:

public class MyRegistrator implements KryoRegistrator {
    @Override
    public void registerClasses(Kryo kryo) {
        kryo.register(Hello.class);
    }
}

Hello类的定义如下:

public class Hello implements Serializable {
    private int a;
    private long id;
    private String words;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getWords() {
        return words;
    }

    public void setWords(String words) {
        this.words = words;
    }
}

运行KryoSparkTest,查看输出日志,如:

16/04/05 10:18:13 INFO MemoryStore: Block rdd_2_0 stored as bytes in memory (estimated size 37.0 B, free 118.8 KB)

如果不使用Kryo serialization,运行结果为:

16/04/05 10:46:00 INFO MemoryStore: Block rdd_2_0 stored as bytes in memory (estimated size 160.0 B, free 118.9 KB)

从上述运行结果中可以看出Kryo serialization的存储优势。由于hello.txt文本数比较小,仅说明问题,不做速度压测验证。
Scala如何使用Kryo?

Chill是Kryo serialization库的Scala扩展。为什么使用Chill,可参加官网说明:

    Scala classes often have a number of properties that distinguish them from usual Java classes. Often scala classes are immutable, and thus have no zero argument constructor. Secondly, object in scala is a singleton that needs to be carefully serialized. Additionally, scala classes often have synthetic (compiler generated) fields that need to be serialized, and by default Kryo does not serialize those.

下面是一个简单的使用示例:

object ChillTest {
  def main(args: Array[String]) {
    val src = Source.fromFile("src/main/resources/hello.txt").getLines().map(f => {
      val line = f.split(",")
      val hello = new Hello
      hello.setA(line(0).toInt)
      hello.setId(line(1).toLong)
      hello.setWords(line(2))
      hello
    }).toList

    val instantiator = new ScalaKryoInstantiator
    instantiator.setRegistrationRequired(false)

    val kryo = instantiator.newKryo()
    val baos = new ByteArrayOutputStream()
    val output = new Output(baos, 4096)
    kryo.writeObject(output, src)

    val input = new Input(baos.toByteArray)
    val deser = kryo.readObject(input, classOf[List[Hello]])
    assert(deser.size == 3)
  }
}

结语

前文讲过,Kryo为了做到高效序列化,并不序列化对象的field信息,因此,在使用Kryo serialization之前要记得自己加载需要序列化的类。如果Spark中设置了Kryo序列化,但忘记注册类了,运行时会直接报错。另外,序列化类的ID信息也要合理设计,兼容版本和连续性。

另外,本文对序列化的底层技术细节没有过多涉及,也没有详细解析Kryo serialization后的数组内容和Kryo的源码,有兴趣的读者可以自行研究下。文中有陈述不完善的地方,也请批评指正,共同提高,多谢。

你可能感兴趣的:(简单聊聊Kryo serialization)