learn.lianglianglee.com/专栏/ZooKeeper源码分析与实战-完/05 深入分析 Jute 的底层实现原理.md.html
2022-05-11 18:57:05 +08:00

1041 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>05 深入分析 Jute 的底层实现原理.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/00 开篇词:选择 ZooKeeper一步到位掌握分布式开发.md.html">00 开篇词:选择 ZooKeeper一步到位掌握分布式开发.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/01 ZooKeeper 数据模型:节点的特性与应用.md.html">01 ZooKeeper 数据模型:节点的特性与应用.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/02 发布订阅模式:如何使用 Watch 机制实现分布式通知.md.html">02 发布订阅模式:如何使用 Watch 机制实现分布式通知.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/03 ACL 权限控制:如何避免未经授权的访问?.md.html">03 ACL 权限控制:如何避免未经授权的访问?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/04 ZooKeeper 如何进行序列化?.md.html">04 ZooKeeper 如何进行序列化?.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/ZooKeeper源码分析与实战-完/05 深入分析 Jute 的底层实现原理.md.html">05 深入分析 Jute 的底层实现原理.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/06 ZooKeeper 的网络通信协议详解.md.html">06 ZooKeeper 的网络通信协议详解.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/07 单机模式:服务器如何从初始化到对外提供服务?.md.html">07 单机模式:服务器如何从初始化到对外提供服务?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/08 集群模式:服务器如何从初始化到对外提供服务?.md.html">08 集群模式:服务器如何从初始化到对外提供服务?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/09 创建会话:避开日常开发的那些“坑”.md.html">09 创建会话:避开日常开发的那些“坑”.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/10 ClientCnxn客户端核心工作类工作原理解析.md.html">10 ClientCnxn客户端核心工作类工作原理解析.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/11 分桶策略:如何实现高效的会话管理?.md.html">11 分桶策略:如何实现高效的会话管理?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/12 服务端是如何处理一次会话请求的?.md.html">12 服务端是如何处理一次会话请求的?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/13 Curator如何降低 ZooKeeper 使用的复杂性?.md.html">13 Curator如何降低 ZooKeeper 使用的复杂性?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/14 Leader 选举:如何保证分布式数据的一致性?.md.html">14 Leader 选举:如何保证分布式数据的一致性?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/15 ZooKeeper 究竟是怎么选中 Leader 的?.md.html">15 ZooKeeper 究竟是怎么选中 Leader 的?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/16 ZooKeeper 集群中 Leader 与 Follower 的数据同步策略.md.html">16 ZooKeeper 集群中 Leader 与 Follower 的数据同步策略.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/17 集群中 Leader 的作用:事务的请求处理与调度分析.md.html">17 集群中 Leader 的作用:事务的请求处理与调度分析.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/18 集群中 Follow 的作用:非事务请求的处理与 Leader 的选举分析.md.html">18 集群中 Follow 的作用:非事务请求的处理与 Leader 的选举分析.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/19 Observer 的作用与 Follow 有哪些不同?.md.html">19 Observer 的作用与 Follow 有哪些不同?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/20 一个运行中的 ZooKeeper 服务会产生哪些数据和文件?.md.html">20 一个运行中的 ZooKeeper 服务会产生哪些数据和文件?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/21 ZooKeeper 分布式锁:实现和原理解析.md.html">21 ZooKeeper 分布式锁:实现和原理解析.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/22 基于 ZooKeeper 命名服务的应用:分布式 ID 生成器.md.html">22 基于 ZooKeeper 命名服务的应用:分布式 ID 生成器.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/23 使用 ZooKeeper 实现负载均衡服务器功能.md.html">23 使用 ZooKeeper 实现负载均衡服务器功能.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/24 ZooKeeper 在 Kafka 和 Dubbo 中的工业级实现案例分析.md.html">24 ZooKeeper 在 Kafka 和 Dubbo 中的工业级实现案例分析.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/25 如何搭建一个高可用的 ZooKeeper 生产环境?.md.html">25 如何搭建一个高可用的 ZooKeeper 生产环境?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/26 JConsole 与四字母命令:如何监控服务器上 ZooKeeper 的运行状态?.md.html">26 JConsole 与四字母命令:如何监控服务器上 ZooKeeper 的运行状态?.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/27 crontab 与 PurgeTxnLog线上系统日志清理的最佳时间和方式.md.html">27 crontab 与 PurgeTxnLog线上系统日志清理的最佳时间和方式.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/28 彻底掌握二阶段提交三阶段提交算法原理.md.html">28 彻底掌握二阶段提交三阶段提交算法原理.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/29 ZAB 协议算法:崩溃恢复和消息广播.md.html">29 ZAB 协议算法:崩溃恢复和消息广播.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/30 ZAB 与 Paxos 算法的联系与区别.md.html">30 ZAB 与 Paxos 算法的联系与区别.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/31 ZooKeeper 中二阶段提交算法的实现分析.md.html">31 ZooKeeper 中二阶段提交算法的实现分析.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/32 ZooKeeper 数据存储底层实现解析.md.html">32 ZooKeeper 数据存储底层实现解析.md.html</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/33 结束语 分布技术发展与 ZooKeeper 应用前景.md.html">33 结束语 分布技术发展与 ZooKeeper 应用前景.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>05 深入分析 Jute 的底层实现原理</h1>
<p>上个课时我们讲解了 ZooKeeper 中采用 Jute 作为序列化解决的方案,并介绍了其应用层的使用技巧。本课时我们就深入 Jute 框架的内部核心,来看一看其内部的实现原理和算法。而通过研究 Jute 序列化框架的内部的实现原理,能够让我们在日常工作中更加高效安全地使用 Jute 序列化框架。</p>
<h3>简述 Jute 序列化</h3>
<p>通过前面的课时我们知道了序列化就是将 Java 对象转化成字节码的形式,从而方便进行网络传输和本地化存储,那么具体的序列化方法都有哪些呢?这里我们结合 ZooKeeper 中使用到的序列化解决方案 Jute 来进行介绍Jute 框架给出了 3 种序列化方式,分别是 Binary 方式、Csv 方式、XML 方式。序列化方式可以通俗地理解成我们将 Java 对象通过转化成特定的格式,从而更加方便在网络中传输和本地化存储。之所以采用这 3 种方式的格式化文件,也是因为这 3 种方式具有跨平台和普遍的规约特性,后面我将会对这三种方法的特性进行具体讲解。接下来我将深入 Jute 的底层,看一下这 3 种实现方式的底层实现过程。</p>
<h3>Jute 内部核心算法</h3>
<p>上个课时中我们提到过ZooKeeper 在实现序列化的时候要实现 Record 接口,而在 Record 接口的内部,真正起作用的是两个工具类,分别是 OutPutArchive 和 InputArchive。下边我们分别来看一下它们在 Jute 内部是如何实现的。</p>
<p>OutPutArchive 是一个接口规定了一系列序列化相关的操作。而要实现具体的相关操作Jute 是通过三个具体实现类分别实现了 Binary、Csv、XML 三种方式的序列化操作。而这三种方式有什么不同,我们在日常工作中应该如何选择呢?带着这些问题我们来深入到 Jute 的内部实现来找寻答案</p>
<h4>Binary 方式的序列化</h4>
<p>首先我们来看一下 Jute 中的第 1 种序列化方式Binary 序列化方式,即二进制的序列化方式。正如我们前边所提到的,采用这种方式的序列化就是将 Java 对象信息转化成二进制的文件格式。</p>
<p>在 Jute 中实现 Binary 序列化方式的类是 BinaryOutputArchive。该 BinaryOutputArchive 类通过实现 OutPutArchive 接口,在 Jute 框架采用二进制的方式实现序列化的时候,采用其作为具体的实现类。</p>
<p>在这里我们通过调用 Record 接口中的 writeString 方法为例,该方法是将 Java 对象的 String 字符类型进行序列化。当调用 writeString 方法后,首先判断所要进行序列化的字符串是否为空。如果是空字符串则采用 writeInt 方法,将空字符串当作值为 -1 的数字类型进行序列化;如果不为空,则调用 stringtoByteBuffer 方法对字符串进行序列化操作。</p>
<pre><code>void writeString (String s, Sring tag){
if(s==null){
writeInt(-1,&quot;len&quot;);
return
}
ByteBuffer bb = stringtoByteBuffer(s);
...
}
</code></pre>
<p>而 stringToByteBuffer 方法也是 BinaryOutputArchive 类的内部核心方法,除了 writeString 序列化方法外,其他的比如 writeInt、wirteDoule 等序列化方法则是调用 DataOutPut 接口中的相关方法来实现具体的序列化操作。</p>
<p>在调用 BinaryOutputArchive 类的 stringToByteBuffer 方法时,在将字符串转化成二进制字节流的过程中,首选将字符串转化成字符数组 CharSequence 对象,并根据 ascii 编码判断字符类型如果是字母等则使用1个 byte 进行存储。如果是诸如 “¥” 等符号则采用两个 byte 进程存储。如果是汉字则采用3个 byte 进行存储。</p>
<pre><code>...
private ByteBuffer bb = ByteBuffer.allocate(1024);
...
ByteBuffer stringToByteBuffer(CharSeuquece s){
if (c &lt; 0x80) {
bb.put((byte) c);
} else if (c &lt; 0x800) {
bb.put((byte) (0xc0 | (c &gt;&gt; 6)));
bb.put((byte) (0x80 | (c &amp; 0x3f)));
} else {
bb.put((byte) (0xe0 | (c &gt;&gt; 12)));
bb.put((byte) (0x80 | ((c &gt;&gt; 6) &amp; 0x3f)));
bb.put((byte) (0x80 | (c &amp; 0x3f)));
}
}
</code></pre>
<p>Binary 二进制序列化方式的底层实现相对简单,只是采用将对应的 Java 对象转化成二进制字节流的方式。Binary 方式序列化的优点有很多:无论是 Windows 操作系统、还是 Linux 操作系统或者是苹果的 macOS 操作系统,其底层都是对二进制文件进行操作,而且所有的系统对二进制文件的编译与解析也是一样的,所有操作系统都能对二进制文件进行操作,跨平台的支持性更好。而缺点则是会存在不同操作系统下,产生大端小端的问题。</p>
<h4>XML 方式的序列化</h4>
<p>说完了 Binary 的序列化方式,我们再来看看 Jute 中的另一种序列化方式 XML 方式。XML 是一种可扩展的标记语言。当初设计的目的就是用来传输和存储数据,很像我们都很熟悉的 HTML 语言,而与 HTML 语言不同的是我们需要自己定义标签。在 XML 文件中每个标签都是我们自己定义的,而每个标签就对应一项内容。一个简单的 XML 的格式如下面这段代码所示:</p>
<pre><code>&lt;note&gt;
&lt;to&gt;学生&lt;/to&gt;
&lt;from&gt;老师&lt;/form&gt;
&lt;heading&gt;上课提醒&lt;/heading&gt;
&lt;body&gt;记得9:00来上课&lt;/body&gt;
&lt;/note&gt;
</code></pre>
<p>大概了解了 XML 文件,接下来我们看一下 Jute 框架中是如何采用 XML 方式进行序列化操作的。在 Jute 中使用 XmlOutPutArchive 类作用 XML 方式序列化的具体实现类。与上面讲解二进制的序列化实现一样 ,这里我们还是以 writeString 方法的 XML 序列化方式的实现为例。
首先当采用XML 方式进行序列化时,调用 writeString 方法将 Java 中的 String 字符串对象进行序列化时,在 writeString 内部首先调用 printBeginEnvelope 方法并传入 tag 参数,标记我们要序列化的字段名称。之后采用“”和“”作用自定义标签,封装好传入的 Java 字符串。</p>
<pre><code>void writeString(String s, String tag){
printBeginEnvelope(tag);
stream.print(&quot;&lt;string&gt;&quot;);
stream.print(Utils.toXMLString(s));
stream.print(&quot;&lt;/string&gt;&quot;);
printEndEnvelope(tag);
}
</code></pre>
<p>而在 printBeginEnvelope 方法中,其主要作用就是添加该字段的名称、字段值等信息,用于之后反序列化的过程中。</p>
<pre><code>void printBeginEnvelope (String tag){
...
if (&quot;struct&quot;.equals(s)) {
putIndent();
stream.print(&quot;&lt;member&gt;\n&quot;);
addIndent();
putIndent();
stream.print(&quot;&lt;name&gt;&quot;+tag+&quot;&lt;/name&gt;\n&quot;);
putIndent();
stream.print(&quot;&lt;value&gt;&quot;);
} else if (&quot;vector&quot;.equals(s)) {
stream.print(&quot;&lt;value&gt;&quot;);
} else if (&quot;map&quot;.equals(s)) {
stream.print(&quot;&lt;value&gt;&quot;);
}
} else {
stream.print(&quot;&lt;value&gt;&quot;);
}
}
</code></pre>
<p>通过上面在 Jute 框架中,对采用 XML 方式序列化的实现类XmlOutPutArchive 中的底层实现过程分析,我们可以了解到其实现的基本原理,也就是根据 XML 格式的要求,解析传入的序列化参数,并将参数按照 Jute 定义好的格式,采用设定好的默认标签封装成对应的序列化文件。</p>
<p>而采用 XML 方式进行序列化的优点则是,通过可扩展标记协议,不同平台或操作系统对序列化和反序列化的方式都是一样的,不存在因为平台不同而产生的差异性,也不会出现如 Binary 二进制序列化方法中产生的大小端的问题。而缺点则是序列化和反序列化的性能不如二进制方式。在序列化后产生的文件相比与二进制方式,同样的信息所产生的文件更大。</p>
<h4>Csv 方式的序列化</h4>
<p>最后我们来学习一下 Jute 序列化框架的最后一种序列化方式Csv它和 XML 方式很像只是所采用的转化格式不同Csv 格式采用逗号将文本进行分割,我们日常使用中最常用的 Csv 格式文件就是 Excel 文件。</p>
<p>在 Jute 框架中实现 Csv 序列化的类是 CsvOutputArchive我们还是以 String 字符对象序列化为例,在调用 CsvOutputArchive 的 writeString 方法时writeString 方法首先调用 printCommaUnlessFirst 方法生成一个逗号分隔符,之后将要序列化的字符串值转换成 CSV 编码格式追加到字节数组中。</p>
<pre><code>void writeString(String s, String tag){
printCommaUnlessFirst();
stream.print(Utils.toCSVString(s));
throwExceptionOnError(tag);
}
</code></pre>
<p>到这里我们已经对 Jute 框架的 3 种序列化方式的底层实现有了一个整体了解,这 3 种方式相比,二进制底层的实现方式最为简单,性能也最好。而 XML 作为可扩展的标记语言跨平台性更强。而 CSV 方式介于两者之间实现起来也相比 XML 格式更加简单。</p>
<h3>结束</h3>
<p>本课时我们对 Jute 序列化的底层实现过程做了进一步地深度分析,和开篇中提到的一样,我们之所以深入底层学习 Jute 的底层实现原理,是为了在日常开发中更好的使用 ZooKeeper在发生诸如客户端与服务端通信中信息发送不完整或解析错误等情况时能通过底层分析从序列化模块入手排查信息错误的原因进一步提高我们在日常设计和解决问题的能力。</p>
<p>接下来请你思考这样一个问题,在 ZooKeeper 中,默认的序列化实现方式是哪种?</p>
<p>在 ZooKeeper 中默认的序列化实现方式是 Binary 二进制方式。这是因为二进制具有更好的性能,以及大多数平台对二进制的实现都不尽相同。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/ZooKeeper源码分析与实战-完/04 ZooKeeper 如何进行序列化?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/ZooKeeper源码分析与实战-完/06 ZooKeeper 的网络通信协议详解.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709975cd3bb63cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>