摘要

我们大概清楚了使用 ZooKeeper 实现一些功能的主要方式,也就是通过客户端与服务端之间的相互通信。那么首先要解决的问题就是通过网络传输数据,而要想通过网络传输我们定义好的 Java 对象数据,必须要先对其进行序列化。例如,我们通过 ZooKeeper 客户端发送 ACL 权限控制请求时,需要把请求信息封装成 packet 类型,经过序列化后才能通过网络将 ACL 信息发送给 ZooKeeper 服务端进行处理。

一、序列化和反序列化定义

序列化是指将我们定义好的 Java 类型转化成数据流的形式。之所以这么做是因为在网络传输过程中,TCP 协议采用“流通信”的方式,提供了可以读写的字节流。而这种设计的好处在于避免了在网络传输过程中经常出现的问题:比如消息丢失、消息重复和排序等问题。

那么什么时候需要序列化呢?如果我们需要通过网络传递对象或将对象信息进行持久化的时候,就需要将该对象进行序列化。

我们较为熟悉的序列化操作是在 Java中,当我们要序列化一个对象的时候,首先要实现一个Serializable 接口。

public class User implements Serializable{private static final long serialVersionUID = 1L;private Long ids;private String name;...}

实现了 Serializable 接口后其实没有做什么实际的工作,它是一个没有任何内容的空接口,起到的作用就是标识该类是需要进行序列化的,这个就与我们后边要重点讲解的 ZooKeeper 序列化实现方法有很大的不同,这里请你先记住当前的写法,后边我们会展开讲解。

public interface Serializable {}

定义好序列化接口后,我们再看一下如何进行序列化和反序列化的操作。Java 中进行序列化和反序列化的过程中,主要用到了 ObjectInputStream 和 ObjectOutputStream 两个 IO 类。

ObjectOutputStream 负责将对象进行序列化并存储到本地。而 ObjectInputStream 从本地存储中读取对象信息反序列化对象。

//序列化ObjectOutputStream oo = new ObjectOutputStream()oo.writeObject(user);//反序列化ObjectInputStream ois = new ObjectInputStream();User user = (User) ois.readObject();

二、ZooKeeper的序列化方案

ZooKeeper 中并没有采用和 Java 一样的序列化方式,而是采用了一个 Jute 的序列解决方案作为 ZooKeeper 框架自身的序列化方式,说到 Jute 框架,它最早作为 Hadoop 中的序列化组件。之后 Jute 从 Hadoop 中独立出来,成为一个独立的序列化解决方案。ZooKeeper 从最开始就采用 Jute 作为其序列化解决方案,直到其最新的版本依然没有更改。

虽然 ZooKeeper 一直将 Jute 框架作为序列化解决方案,但这并不意味着 Jute 相对其他框架性能更好,反倒是 Apache Avro、Thrift 等框架在性能上优于前者。之所以 ZooKeeper 一直采用 Jute 作为序列化解决方案,主要是新老版本的兼容等问题,这里也请你注意,也许在之后的版本中,ZooKeeper 会选择更加高效的序列化解决方案。

2.1 使用 Jute 实现序列化

简单介绍了 Jute 框架的发展过程,下面我们来看一下如何使用 Jute 在 ZooKeeper 中实现序列化。如果我们要想将某个定义的类进行序列化,首先需要该类实现 Record 接口的 serilize 和 deserialize 方法,这两个方法分别是序列化和反序列化方法。下边这段代码给出了我们一般在 ZooKeeper 中进行序列化的具体实现:首先,我们定义了一个 test_jute 类,为了能够对它进行序列化,需要该 test_jute 类实现 Record 接口,并在对应的 serialize 序列化方法和 deserialize 反序列化方法中编辑具体的实现逻辑。

class test_jute implements Record{private long ids;private String name;...public void serialize(OutpurArchive a_,String tag){...}public void deserialize(INputArchive a_,String tag){...}}

而在序列化方法 serialize 中,我们要实现的逻辑是,首先通过字符类型参数 tag 传递标记序列化标识符,之后使用 writeLong 和 writeString 等方法分别将对象属性字段进行序列化。

public void serialize(OutpurArchive a_,String tag) throws ...{a_.startRecord(this.tag);a_.writeLong(ids,"ids");a_.writeString(type,"name");a_.endRecord(this,tag);}

而调用 derseralize 在实现反序列化的过程则与我们上边说的序列化过程正好相反。

public void deserialize(INputArchive a_,String tag) throws {a_.startRecord(tag);ids = a_.readLong("ids");name = a_.readString("name");a_.endRecord(tag);}

到这里我们就介绍完了如何在 ZooKeeper 中使用 Jute 实现序列化,需要注意的是,在实现了Record 接口后,具体的序列化和反序列化逻辑要我们自己在 serialize 和 deserialize 函数中完成

序列化和反序列化的实现逻辑编码方式相对固定,首先通过 startRecord 开启一段序列化操作,之后通过 writeLong、writeString 或 readLong、 readString 等方法执行序列化或反序列化。本例中只是实现了长整型和字符型的序列化和反序列化操作,除此之外 ZooKeeper 中的 Jute 框架还支持 整数类型(Int)、布尔类型(Bool)、双精度类型(Double)以及 Byte/Buffer 类型。

三、Jute 在 ZooKeeper 中的底层实现

正因为 ZooKeeper 的设计目的是将复杂的底层操作封装成简单易用的接口,从而方便用户调用,也使得我们在使用 ZooKeeper 实现序列化的时候能够更加容易。

学会了利用 Jute 实现序列化和反序列化后,我们深入底层,看一下 ZooKeeper 框架具体是如何实现序列化操作的。正如上边我们提到的,通过简单的实现 Record 接口就可以实现序列化,那么我们接下来就以这个接口作为入口,详细分析其底层原理。

Record 接口可以理解为 ZooKeeper 中专门用来进行网络传输或本地存储时使用的数据类型。因此所有我们实现的类要想传输或者存储到本地都要实现该 Record 接口。

public interface Record{public void serialize(OutputArchive archive, String tag)throws IOException;public void deserialize(InputArchive archive, String tag)throws IOException;}

Record 接口的内部实现逻辑非常简单,只是定义了一个 序列化方法 serialize 和一个反序列化方法 deserialize 。而在 Record 起到关键作用的则是两个重要的类:OutputArchive 和 InputArchive ,其实这两个类才是真正的序列化和反序列化工具类。

在 OutputArchive 中定义了可进行序列化的参数类型,根据不同的序列化方式调用不同的实现类进行序列化操作。如下图所示,Jute 可以通过 Binary 、 Csv 、Xml 等方式进行序列化操作。

而对应于序列化操作,在反序列化时也会相应调用不同的实现类,来进行反序列化操作。 如下图所示:

注意:无论是序列化还是反序列化,都可以对多个对象进行操作,所以当我们在定义序列化和反序列化方法时,需要字符类型参数 tag 表示要序列化或反序列化哪个对象。

如果说序列化就是将对象转化成字节流的格式,那么为什么 ZooKeeper 的 Jute 序列化框架还提供了对 Byte/Buffer 这两种类型的序列化操作呢?

其实这其中最关键的作用就是在不同操作系统中存在大端和小端的问题,为了避免不同操作系统环境的差异,在传输字节类型时也要进行序列化和反序列化。这里你需要在日常使用中多注意。

3.1 Binary 方式的序列化

Binary 序列化方式,即二进制的序列化方式。正如我们前边所提到的,采用这种方式的序列化就是将 Java 对象信息转化成二进制的文件格式。

在 Jute 中实现 Binary 序列化方式的类是 BinaryOutputArchive。该 BinaryOutputArchive 类通过实现 OutPutArchive 接口,在 Jute 框架采用二进制的方式实现序列化的时候,采用其作为具体的实现类。

在这里我们通过调用 Record 接口中的 writeString 方法为例,该方法是将 Java 对象的 String 字符类型进行序列化。当调用 writeString 方法后,首先判断所要进行序列化的字符串是否为空。如果是空字符串则采用 writeInt 方法,将空字符串当作值为 -1 的数字类型进行序列化;如果不为空,则调用 stringtoByteBuffer 方法对字符串进行序列化操作。

void writeString (String s, Sring tag){if(s==null){writeInt(-1,"len");return }ByteBuffer bb = stringtoByteBuffer(s);...}

而 stringToByteBuffer 方法也是 BinaryOutputArchive 类的内部核心方法,除了 writeString 序列化方法外,其他的比如 writeInt、wirteDoule 等序列化方法则是调用 DataOutPut 接口中的相关方法来实现具体的序列化操作。

在调用 BinaryOutputArchive 类的 stringToByteBuffer 方法时,在将字符串转化成二进制字节流的过程中,首选将字符串转化成字符数组 CharSequence 对象,并根据 ascii 编码判断字符类型,如果是字母等则使用1个 byte 进行存储。如果是诸如 “¥” 等符号则采用两个 byte 进程存储。如果是汉字则采用3个 byte 进行存储。

private ByteBuffer bb = ByteBuffer.allocate(1024);ByteBuffer stringToByteBuffer(CharSeuquece s){if (c < 0x80) {bb.put((byte) c);} else if (c < 0x800) {bb.put((byte) (0xc0 | (c >> 6)));bb.put((byte) (0x80 | (c & 0x3f)));} else {bb.put((byte) (0xe0 | (c >> 12)));bb.put((byte) (0x80 | ((c >> 6) & 0x3f)));bb.put((byte) (0x80 | (c & 0x3f)));}}

Binary 二进制序列化方式的底层实现相对简单,只是采用将对应的 Java 对象转化成二进制字节流的方式。Binary 方式序列化的优点有很多:无论是 Windows 操作系统、还是 Linux 操作系统或者是苹果的 macOS 操作系统,其底层都是对二进制文件进行操作,而且所有的系统对二进制文件的编译与解析也是一样的,所有操作系统都能对二进制文件进行操作,跨平台的支持性更好。而缺点则是会存在不同操作系统下,产生大端小端的问题。

3.2 XML 方式的序列化

说完了 Binary 的序列化方式,我们再来看看 Jute 中的另一种序列化方式 XML 方式。XML 是一种可扩展的标记语言。当初设计的目的就是用来传输和存储数据,很像我们都很熟悉的 HTML 语言,而与 HTML 语言不同的是我们需要自己定义标签。在 XML 文件中每个标签都是我们自己定义的,而每个标签就对应一项内容。一个简单的 XML 的格式如下面这段代码所示:

<note><to>学生</to><from>老师</form><heading>上课提醒</heading><body>记得9:00来上课</body></note>

大概了解了 XML 文件,接下来我们看一下 Jute 框架中是如何采用 XML 方式进行序列化操作的。在 Jute 中使用 XmlOutPutArchive 类作用 XML 方式序列化的具体实现类。与上面讲解二进制的序列化实现一样 ,这里我们还是以 writeString 方法的 XML 序列化方式的实现为例。 首先,当采用XML 方式进行序列化时,调用 writeString 方法将 Java 中的 String 字符串对象进行序列化时,在 writeString 内部首先调用 printBeginEnvelope 方法并传入 tag 参数,标记我们要序列化的字段名称。之后采用“”和“”作用自定义标签,封装好传入的 Java 字符串。

void writeString(String s, String tag){printBeginEnvelope(tag);stream.print("<string>");stream.print(Utils.toXMLString(s));stream.print("</string>");printEndEnvelope(tag);}

而在 printBeginEnvelope 方法中,其主要作用就是添加该字段的名称、字段值等信息,用于之后反序列化的过程中。

void printBeginEnvelope (String tag){...if ("struct".equals(s)) {putIndent();stream.print("<member>\n");addIndent();putIndent();stream.print("<name>"+tag+"</name>\n");putIndent();stream.print("<value>");} else if ("vector".equals(s)) {stream.print("<value>");} else if ("map".equals(s)) {stream.print("<value>");}} else {stream.print("<value>");}}

Jute 框架中,对采用 XML 方式序列化的实现类:XmlOutPutArchive 中的底层实现过程分析,我们可以了解到其实现的基本原理,也就是根据 XML 格式的要求,解析传入的序列化参数,并将参数按照 Jute 定义好的格式,采用设定好的默认标签封装成对应的序列化文件。

而采用 XML 方式进行序列化的优点则是,通过可扩展标记协议,不同平台或操作系统对序列化和反序列化的方式都是一样的,不存在因为平台不同而产生的差异性,也不会出现如 Binary 二进制序列化方法中产生的大小端的问题。而缺点则是序列化和反序列化的性能不如二进制方式。在序列化后产生的文件相比与二进制方式,同样的信息所产生的文件更大。

3.3 CSV 方式的序列化

Csv,它和 XML 方式很像,只是所采用的转化格式不同,Csv 格式采用逗号将文本进行分割,我们日常使用中最常用的 Csv 格式文件就是 Excel 文件。

在 Jute 框架中实现 Csv 序列化的类是 CsvOutputArchive,我们还是以 String 字符对象序列化为例,在调用 CsvOutputArchive 的 writeString 方法时,writeString 方法首先调用 printCommaUnlessFirst 方法生成一个逗号分隔符,之后将要序列化的字符串值转换成 CSV 编码格式追加到字节数组中。

void writeString(String s, String tag){printCommaUnlessFirst();stream.print(Utils.toCSVString(s));throwExceptionOnError(tag);}

到这里我们已经对 Jute 框架的 3 种序列化方式的底层实现有了一个整体了解,这 3 种方式相比,二进制底层的实现方式最为简单,性能也最好。而 XML 作为可扩展的标记语言跨平台性更强。而 CSV 方式介于两者之间实现起来也相比 XML 格式更加简单。

在 ZooKeeper 中,默认的序列化实现方式是哪种?

在 ZooKeeper 中默认的序列化实现方式是 Binary 二进制方式。这是因为二进制具有更好的性能,以及大多数平台对二进制的实现都不尽相同。

博文参考

Zookeeper——序列化与反序列化原理相关推荐

  1. 序列化与反序列化原理

    一,Java 领域的对象如何传输 1,基于 socket 进行对象传输 User @Data public class User {private String name; } SocketServe ...

  2. 反序列化对象列表发生异常_面试官:你知道Java对象的序列化与反序列化背后的原理吗?...

    序列化与反序列化 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程.一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等.在网络传输过程中,可以是字节或是 ...

  3. OWASP TOP 10(六)反序列化漏洞(序列化和反序列化、漏洞原理、PHP中的序列化和反序列化、魔术方法、Typecho_v1.0中的反序列化漏洞)

    文章目录 反序列化漏洞 一.概述 1. 序列化和反序列化 2. 序列化的目的 二.PHP中的序列化与反序列化 1. 概述 2. 示例序列化与反序列化 3. 反序列化漏洞 - PHP中的魔术方法 - T ...

  4. zookeeper使用Jute进行序列化及反序列化

    Zookeeper在网络传输时使用的是Jute进行序列化和反序列化,官方也提出过要使用类似于Apache Avro.Thrift或是Google的protobuf这样的组件来替换Jute,但考虑到新老 ...

  5. 序列化和反序列化的对单例破坏的防止及其原理

    首先我们来看一下序列化和反序列化是怎么破坏单例的.看代码 public class HungrySingleton implements Serializable{private final stat ...

  6. 序列化和反序列化的底层实现原理是什么?

    序列化和反序列化作为Java里一个较为基础的知识点,大家心里也有那么几句要说的,但我相信很多小伙伴掌握的也就是那么几句而已,如果再深究问一下Java如何实现序列化和反序列化的,就可能不知所措了!遥记当 ...

  7. 序列化和反序列化的底层实现原理是什么

    文章目录 一.基本概念 二.Java如何实现序列化和反序列化 三.相关注意事项 四.总结 序列化和反序列化作为Java里一个较为基础的知识点,大家心里也有那么几句要说的,但我相信很多小伙伴掌握的也就是 ...

  8. 序列化和反序列化(转)

    转载:http://kb.cnblogs.com/page/515982/ 摘要 序列化和反序列化几乎是工程师们每天都要面对的事情,但是要精确掌握这两个概念并不容易:一方面,它们往往作为框架的一部分出 ...

  9. C#: .net序列化及反序列化 [XmlElement(“节点名称”)] [XmlAttribute(“节点属性”)] (上篇)...

    .net序列化及反序列化 序列化是指一个对象的实例可以被保存,保存成一个二进制串,当然,一旦被保存成二进制串,那么也可以保存成文本串了. 比如,一个计数器,数值为2,我们可以用字符串"2&q ...

最新文章

  1. 如何将重复的数据标红_python如何处理重复值数据?
  2. 半导体基础知识(4):无源,有源和机电组件
  3. react服务端/客户端,同构代码心得
  4. JAVA的知识点4——字符型变量/常量 boolean类型变量/常量
  5. SQL之inner join/left join/right join
  6. 欧洲语言学习统一标准C1C2音频,北京通州区有没有西班牙语培训班(为何选择西班牙语)...
  7. erp系统是什么软件有哪些
  8. Express框架简介(详细)
  9. android自定义进度条_Android中的自定义进度栏
  10. 修复Linux系统内核TCP漏洞,Linux 内核中TCP SACK机制远程Dos漏洞处理方法(CVE-2019-11477) | 聂扬帆博客...
  11. IT界那些性感的让人尖叫的程序员
  12. UEFI 文件类型 .efi (二)
  13. MATLAB Simulink工具箱
  14. 倍加福(P+F)R2000修改雷达IP
  15. 计算机在化学中论文3000字,计算机在化学中的应用
  16. 《嵌入式 - 语音识别TWen-ASR-ONE开发笔记》第3章 TWen-ASR-ONE 多线程和消息队列
  17. bmp怎样转成jpg,bmp格式换jpg
  18. NowCoder--Protoss and Zerg
  19. Echarts 坐标轴刻度间隔/全部显示
  20. 悲观锁和乐观锁的理解以及实现方式-学习笔记

热门文章

  1. U盘安装centos系统(避坑)
  2. 51单片机之数码管动态循环左移显示手机号
  3. 阿里云ECS服务器搭建
  4. linux命令pri,linux基本命令
  5. 科讯云:致力于成为软件开发行业的可信赖服务商
  6. android 开发必备软件
  7. Thinkpad T60安装Snow Leopard
  8. android第十二章简易打地鼠游戏,MouseGame
  9. Windows 安装babun教程以及没有文件扩展“.vbs”的脚本引擎的解决方案
  10. 零基础可以学3d建模吗?没有美术基础多久可以学会建模入行呢?