天天看点

ZooKeeper系统模型之序列化。使用Jute进行序列化深入Jute

        对于一个网络通信,首先需要解决的就是对数据的序列化和反序列化处理,在ZooKeeper中,使用了Jute这以序列化组件来进行数据的序列化和反序列化操作。

使用Jute进行序列化

        下面我们通过一个例子来看看如何使用Jute来完成Java对象的序列化和反序列化。假设我们有一个实体类MockReqHeader(代表了一个简单的请求头),其定义如下所示。

public class MockReqHeader implements Record {

    private long sessionId;

    private String type;

    public MockReqHeader() {

    }

    public MockReqHeader(long sessionId, String type) {

        this.sessionId = sessionId;

        this.type = type;

    }

    public long getSessionId() {

        return sessionId;

    }

    public void setSessionId(long sessionId) {

        this.sessionId = sessionId;

    }

    public String getType() {

        return type;

    }

    public void setType(String type) {

        this.type = type;

    }

    public void deserialize(InputArchive a_, String tag) throws IOException {

        a_.startRecord(tag);

        sessionId = a_.readLong("sessionId");

        type = a_.readString("type");

        a_.endRecord(tag);

    }

    public void serialize(OutputArchive a_, String tag) throws IOException {

        a_.startRecord(this, tag);

        a_.writeLong(sessionId, "sessionId");

        a_.writeString(type, "type");

        a_.endRecord(this, tag);

    }

}

         上面即为一个非常简单的请求头定义,包含了两个成员变量:sessionId和type。接下来我们看看如何使用Jute来进行序列化和反序列化。

public class MockReqHeaderTest {

    public static void main(String[] args) throws Exception {

        // 开始序列化

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos);

        new MockReqHeader(0x34221eccb92a34el, "ping").serialize(boa, "header");

        // 这里通常是TCP网络传输对象

        ByteBuffer bb = ByteBuffer.wrap(baos.toByteArray());

        // 开始反序列化

        ByteBufferInputStream bbis = new ByteBufferInputStream(bb);

        BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);

        MockReqHeader header2 = new MockReqHeader();

        header2.deserialize(bbia, "header");

        bbis.close();

        baos.close();

    }

}

        上面这个代码片段演示了如何使用Jute来对MockReqHeader对象进行序列化和反序列化,总的来说,大体可以分为4步。

  • 实体类需要实现Record接口的serialize和deserialize方法。
  • 构建一个序列化器BinaryOutputArchive。
  • 序列化
调用实体类的serialize方法,将对象序列化到指定tag中去。例如在本例中就将MockReqHeader对象序列化到header中去。
  • 反序列化
调用实体类的deserialize,从指定的tag中反序列化出数据内容。

深入Jute

        从上面的讲解中可以看出,使用Jute来进行Java对象的序列化和反序列化是非常简单的。接下来我们再通过Record序列化接口、序列化器和Jute配置文件三方面来深入了解下Jute。

Record接口

         Jute定义了自己独特的序列化格式Record,ZooKeeper中所有需要进行网络传输或是本地磁盘存储的类型定义,都实现了该接口,其结构简单明了,操作灵活可变,是Jute序列化的核心。Record接口定义了两个最基本的方法,分别是serialize和deserialize,分别用于序列化和反序列化:

  • public void serialize(OutputArchive archive, String tag) throws IOException;
  • public void deserialize(InputArchive archive, String tag) throws IOException;

        所有实体类通过实现Record接口的这个方法,来定义自己将如何被序列化和反序列化。其中archive是底层真正的序列化器和反序列化器,并且每个archive中可以包含对多个对象的序列化和反序列化,因此两个接口方法中都标记了参数tag,用于向序列化器和反序列化器标识对象自己的标记。

        我们重点来看下面中对序列化和反序列化接口的实现:

    public void deserialize(InputArchive a_, String tag) throws IOException {

        a_.startRecord(tag);

        sessionId = a_.readLong("sessionId");

        type = a_.readString("type");

        a_.endRecord(tag);

    }

    public void serialize(OutputArchive a_, String tag) throws IOException {

        a_.startRecord(this, tag);

        a_.writeLong(sessionId, "sessionId");

        a_.writeString(type, "type");

        a_.endRecord(this, tag);

    }

        我们可以看到,在这个样例实现中,serialize和deserialize的过程基本上是两个相反的过程,serialize过程就是将当前对象的各个成员变量以一定的标记(tag)写入到序列化器中去;而deserialize过程则正好相反,是从反序列化器中根据指定的标记(tag)将数据读取出来,并赋值给相应的成员变量。

OutputArchive和InputArchive

        OutputArchive和InputArchive分别是Jute底层的序列化器和反序列化器接口定义。在最新版本的Jute中,分别有BinaryOutputArchive/BinaryInputArchive、CsvOutputArchive/CsvInputArchive和XmlOutputArchive和XmlOutputArchive/XmlInputArchive三种实现。无论哪种实现,都是基于OutputStream和InputStream进行操作。

        BinaryOutputArchive对数据对象的序列化和反序列化,主要用于进行网络传输和本地磁盘的存储,是ZooKeeper底层最主要的序列化方式。CsvOutputArchive对数据的序列化,则更多的是方便数据对象的可视化展示,因此被使用在toString方法中。最后一种XmlOutputArchive,则是为了将数据对象以XML格式保存和还原,但是目前在ZooKeeper中基本没有被使用到。

zookeeper.jute

        很多读者在阅读ZooKeeper的代码的过程中,都会发现一个有趣的现象,那就是在很多ZooKeeper类的说明中,都写着“File generated by hadoop record compiler. Do not edit.”,这时因为该类并不是ZooKeeper的开发人员编写的,而是通过Jute组件在编译过程中动态生成的。在ZooKeeper的src目录下,有一个名叫zookeeper.jute的文件:

ZooKeeper系统模型之序列化。使用Jute进行序列化深入Jute

         在这个文件中定义了所有实体类的所属包名、类名以及该类的所有成员变量及其类型。例如上面的代码片段就分别定义了org.apache.zookeeper.data.Id和org.apache.zookeeper.data.ACL两个类。

        有了这个定义文件后,在源代码编译阶段,Jute会使用不同的代码生成器来为这些类定义生成实际编程语言(Java或C/C++)的类文件。以Java语言为例,Jute会使用JavaGenerator来生成相应的类文件,这些类文件都会被存放在src\java\generated目录下。需要注意的一点是,使用这种方式生成的类,都会实现Record接口。

继续阅读