learn.lianglianglee.com/专栏/ZooKeeper源码分析与实战-完/20 一个运行中的 ZooKeeper 服务会产生哪些数据和文件?.md.html
2022-09-06 22:30:37 +08:00

355 lines
24 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>20 一个运行中的 ZooKeeper 服务会产生哪些数据和文件?.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一步到位掌握分布式开发</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/01 ZooKeeper 数据模型:节点的特性与应用.md.html">01 ZooKeeper 数据模型:节点的特性与应用</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/02 发布订阅模式:如何使用 Watch 机制实现分布式通知.md.html">02 发布订阅模式:如何使用 Watch 机制实现分布式通知</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/03 ACL 权限控制:如何避免未经授权的访问?.md.html">03 ACL 权限控制:如何避免未经授权的访问?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/04 ZooKeeper 如何进行序列化?.md.html">04 ZooKeeper 如何进行序列化?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/05 深入分析 Jute 的底层实现原理.md.html">05 深入分析 Jute 的底层实现原理</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/06 ZooKeeper 的网络通信协议详解.md.html">06 ZooKeeper 的网络通信协议详解</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/07 单机模式:服务器如何从初始化到对外提供服务?.md.html">07 单机模式:服务器如何从初始化到对外提供服务?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/08 集群模式:服务器如何从初始化到对外提供服务?.md.html">08 集群模式:服务器如何从初始化到对外提供服务?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/09 创建会话:避开日常开发的那些“坑”.md.html">09 创建会话:避开日常开发的那些“坑”</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/10 ClientCnxn客户端核心工作类工作原理解析.md.html">10 ClientCnxn客户端核心工作类工作原理解析</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/11 分桶策略:如何实现高效的会话管理?.md.html">11 分桶策略:如何实现高效的会话管理?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/12 服务端是如何处理一次会话请求的?.md.html">12 服务端是如何处理一次会话请求的?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/13 Curator如何降低 ZooKeeper 使用的复杂性?.md.html">13 Curator如何降低 ZooKeeper 使用的复杂性?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/14 Leader 选举:如何保证分布式数据的一致性?.md.html">14 Leader 选举:如何保证分布式数据的一致性?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/15 ZooKeeper 究竟是怎么选中 Leader 的?.md.html">15 ZooKeeper 究竟是怎么选中 Leader 的?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/16 ZooKeeper 集群中 Leader 与 Follower 的数据同步策略.md.html">16 ZooKeeper 集群中 Leader 与 Follower 的数据同步策略</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/17 集群中 Leader 的作用:事务的请求处理与调度分析.md.html">17 集群中 Leader 的作用:事务的请求处理与调度分析</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/18 集群中 Follow 的作用:非事务请求的处理与 Leader 的选举分析.md.html">18 集群中 Follow 的作用:非事务请求的处理与 Leader 的选举分析</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/19 Observer 的作用与 Follow 有哪些不同?.md.html">19 Observer 的作用与 Follow 有哪些不同?</a>
</li>
<li>
<a class="current-tab" href="/专栏/ZooKeeper源码分析与实战-完/20 一个运行中的 ZooKeeper 服务会产生哪些数据和文件?.md.html">20 一个运行中的 ZooKeeper 服务会产生哪些数据和文件?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/21 ZooKeeper 分布式锁:实现和原理解析.md.html">21 ZooKeeper 分布式锁:实现和原理解析</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/22 基于 ZooKeeper 命名服务的应用:分布式 ID 生成器.md.html">22 基于 ZooKeeper 命名服务的应用:分布式 ID 生成器</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/23 使用 ZooKeeper 实现负载均衡服务器功能.md.html">23 使用 ZooKeeper 实现负载均衡服务器功能</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/24 ZooKeeper 在 Kafka 和 Dubbo 中的工业级实现案例分析.md.html">24 ZooKeeper 在 Kafka 和 Dubbo 中的工业级实现案例分析</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/25 如何搭建一个高可用的 ZooKeeper 生产环境?.md.html">25 如何搭建一个高可用的 ZooKeeper 生产环境?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/26 JConsole 与四字母命令:如何监控服务器上 ZooKeeper 的运行状态?.md.html">26 JConsole 与四字母命令:如何监控服务器上 ZooKeeper 的运行状态?</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/27 crontab 与 PurgeTxnLog线上系统日志清理的最佳时间和方式.md.html">27 crontab 与 PurgeTxnLog线上系统日志清理的最佳时间和方式</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/28 彻底掌握二阶段提交三阶段提交算法原理.md.html">28 彻底掌握二阶段提交三阶段提交算法原理</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/29 ZAB 协议算法:崩溃恢复和消息广播.md.html">29 ZAB 协议算法:崩溃恢复和消息广播</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/30 ZAB 与 Paxos 算法的联系与区别.md.html">30 ZAB 与 Paxos 算法的联系与区别</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/31 ZooKeeper 中二阶段提交算法的实现分析.md.html">31 ZooKeeper 中二阶段提交算法的实现分析</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/32 ZooKeeper 数据存储底层实现解析.md.html">32 ZooKeeper 数据存储底层实现解析</a>
</li>
<li>
<a href="/专栏/ZooKeeper源码分析与实战-完/33 结束语 分布技术发展与 ZooKeeper 应用前景.md.html">33 结束语 分布技术发展与 ZooKeeper 应用前景</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>20 一个运行中的 ZooKeeper 服务会产生哪些数据和文件?</h1>
<p>之前的课程我们都在介绍 ZooKeeper 框架能够实现的功能,而无论是什么程序,其本质就是对数据的操作。比如 MySQl 数据库操作的是数据表Redis 数据库操作的是存储在内存中的 Key-Value 值。不同的数据格式和存储方式对系统运行的效率和处理能力都有很大影响。本课时就来学习,在 ZooKeeper 程序运行期间,都会处理哪些数据,以及他们的存储格式和存储位置。</p>
<p>ZooKeeper 服务提供了创建节点、添加 Watcher 监控机制、集群服务等丰富的功能。这些功能服务的实现离不开底层数据的支持。从数据存储地点角度讲ZooKeeper 服务产生的数据可以分为内存数据和磁盘数据。而从数据的种类和作用上来说,又可以分为事务日志数据和数据快照数据。</p>
<h3>内存数据</h3>
<p>首先,我们介绍一下什么是内存数据。在专栏的基础篇中,主要讲解了通过 ZooKeeper 数据节点的特性,来实现一些像发布订阅这样的功能。而这些数据节点实际上就是 ZooKeeper 在服务运行过程中所操作的数据。</p>
<p>我在基础篇中提到过ZooKeeper 的数据模型可以看作一棵<strong>树形结构</strong>而数据节点就是这棵树上的叶子节点。从数据存储的角度看ZooKeeper 的数据模型是存储在内存中的。我们可以把 ZooKeeper 的数据模型看作是存储在内存中的数据库,而这个数据库不但存储数据的节点信息,还存储每个数据节点的 ACL 权限信息以及 stat 状态信息等。</p>
<p>而在底层实现中ZooKeeper 数据模型是通过 DataTree 类来定义的。如下面的代码所示DataTree 类定义了一个 ZooKeeper 数据的内存结构。DataTree 的内部定义类 nodes 节点类型、root 根节点信息、子节点的 WatchManager 监控信息等数据模型中的相关信息。可以说,一个 DataTree 类定义了 ZooKeeper 内存数据的逻辑结构。</p>
<pre><code>public class DataTree {
private DataNode root
private final WatchManager dataWatches
private final WatchManager childWatches
private static final String rootZookeeper = &quot;/&quot;;
}
</code></pre>
<h3>事务日志</h3>
<p>在介绍 ZooKeeper 集群服务的时候,我们介绍过,为了整个 ZooKeeper 集群中数据的一致性Leader 服务器会向 ZooKeeper 集群中的其他角色服务发送数据同步信息,在接收到数据同步信息后, ZooKeeper 集群中的 Follow 和 Observer 服务器就会进行数据同步。而这两种角色服务器所接收到的信息就是 Leader 服务器的事务日志。在接收到事务日志后,并在本地服务器上执行。这种数据同步的方式,避免了直接使用实际的业务数据,减少了网络传输的开销,提升了整个 ZooKeeper 集群的执行性能。</p>
<p>在我们启动一个 ZooKeeper 服务器之前,首先要创建一个 zoo.cfg 文件并进行相关配置,其中有一项配置就是 dataLogDir 。在这项配置中,我们会指定该台 ZooKeeper 服务器事务日志的存放位置。</p>
<p>在 ZooKeeper 服务的底层实现中,是通过 FileTxnLog 类来实现事务日志的底层操作的。如下图代码所示,在 FileTxnLog 类中定义了一些属性字段,分别是:</p>
<ul>
<li>preAllocSize可存储的日志文件大小。如用户不进行特殊设置默认的大小为 65536*1024 字节。</li>
<li>TXNLOG_MAGIC设置日志文件的魔数信息为ZKLG。</li>
<li>VERSION设置日志文件的版本信息。</li>
<li>lastZxidSeen最后一次更新日志得到的 ZXID。</li>
</ul>
<p><img src="assets/Ciqc1F8IC-uAcS1bAABJoZ4awKg473.png" alt="png" /></p>
<p>定义了事务日志操作的相关指标参数后,在 FileTxnLog 类中调用 static 静态代码块,来将这些配置参数进行初始化。比如读取 preAllocSize 参数分配给日志文件的空间大小等操作。</p>
<pre><code>static {
LOG = LoggerFactory.getLogger(FileTxnLog.class);
String size = System.getProperty(&quot;zookeeper.preAllocSize&quot;);
if (size != null) {
try {
preAllocSize = Long.parseLong(size) * 1024;
} catch (NumberFormatException e) {
LOG.warn(size + &quot; is not a valid value for preAllocSize&quot;);
}
}
Long fsyncWarningThreshold;
if ((fsyncWarningThreshold = Long.getLong(&quot;zookeeper.fsync.warningthresholdms&quot;)) == null)
fsyncWarningThreshold = Long.getLong(&quot;fsync.warningthresholdms&quot;, 1000);
fsyncWarningThresholdMS = fsyncWarningThreshold;
</code></pre>
<p>经过参数定义和日志文件的初始化创建后,在 ZooKeeper 服务器的 dataDir 路径下就生成了一个用于存储事务性操作的日志文件。我们知道在 ZooKeeper 服务运行过程中,会不断地接收和处理来自客户端的事务性会话请求,这就要求每次在处理事务性请求的时候,都要记录这些信息到事务日志中。</p>
<p>如下面的代码所示,在 FileTxnLog 类中,实现记录事务操作的核心方法是 append。从方法的命名中可以看出ZooKeeper 采用末尾追加的方式来维护新的事务日志数据到日志文件中。append 方法首先会解析事务请求的头信息,并根据解析出来的 zxid 字段作为事务日志的文件名,之后设置日志的文件头信息 magic、version、dbid 以及日志文件的大小 。</p>
<pre><code>public synchronized boolean append(TxnHeader hdr, Record txn)
throws IOException
{
if (hdr == null) {
return false;
}
if (hdr.getZxid() &lt;= lastZxidSeen) {
LOG.warn(&quot;Current zxid &quot; + hdr.getZxid()
+ &quot; is &lt;= &quot; + lastZxidSeen + &quot; for &quot;
+ hdr.getType());
} else {
lastZxidSeen = hdr.getZxid();
}
if (logStream==null) {
if(LOG.isInfoEnabled()){
LOG.info(&quot;Creating new log file: log.&quot; +
Long.toHexString(hdr.getZxid()));
}
logFileWrite = new File(logDir, (&quot;log.&quot; +
Long.toHexString(hdr.getZxid())));
fos = new FileOutputStream(logFileWrite);
logStream=new BufferedOutputStream(fos);
oa = BinaryOutputArchive.getArchive(logStream);
FileHeader fhdr = new FileHeader(TXNLOG_MAGIC,VERSION, dbId);
fhdr.serialize(oa, &quot;fileheader&quot;);
// Make sure that the magic number is written before padding.
logStream.flush();
currentSize = fos.getChannel().position();
streamsToFlush.add(fos);
}
padFile(fos);
byte[] buf = Util.marshallTxnEntry(hdr, txn);
if (buf == null || buf.length == 0) {
throw new IOException(&quot;Faulty serialization for header &quot; +
&quot;and txn&quot;);
}
Checksum crc = makeChecksumAlgorithm();
crc.update(buf, 0, buf.length);
oa.writeLong(crc.getValue(), &quot;txnEntryCRC&quot;);
Util.writeTxnBytes(oa, buf);
return true;
</code></pre>
<p>从对事务日志的底底层代码分析中可以看出,在 datadir 配置参数路径下存放着 ZooKeeper 服务器所有的事务日志所有事务日志的命名方法都是“log.+ 该条事务会话的 zxid”。</p>
<h3>数据快照</h3>
<p>最后,我们来介绍 ZooKeeper 服务运行过程中产生的最后一个数据文件,即事务快照。</p>
<p>说到快照,可能很多技术人员都不陌生。一个快照可以看作是当前系统或软件服务运行状态和数据的副本。在 ZooKeeper 中,数据快照的作用是将内存数据结构存储到本地磁盘中。因此,从设计的角度说,数据快照与内存数据的逻辑结构一样,都使用 DataTree 结构。在 ZooKeeper 服务运行的过程中,数据快照每间隔一段时间,就会把 ZooKeeper 内存中的数据存储到磁盘中,快照文件是间隔一段时间后对内存数据的备份。</p>
<p>因此,与内存数据相比,快照文件的数据具有滞后性。而与上面介绍的事务日志文件一样,在创建数据快照文件时,也是使用 zxid 作为文件名称。</p>
<p>在代码层面ZooKeeper 通过 FileTxnSnapLog 类来实现数据快照的相关功能。如下图所示在FileTxnSnapLog 类的内部,最核心的方法是 save 方法,在 save 方法的内部,首先会创建数据快照文件,之后调用 FileSnap 类对内存数据进行序列化,并写入到快照文件中。</p>
<pre><code>public void save(DataTree dataTree,
ConcurrentHashMap&lt;Long, Integer&gt; sessionsWithTimeouts,
boolean syncSnap)
throws IOException {
long lastZxid = dataTree.lastProcessedZxid;
File snapshotFile = new File(snapDir, Util.makeSnapshotName(lastZxid));
LOG.info(&quot;Snapshotting: 0x{} to {}&quot;, Long.toHexString(lastZxid),
snapshotFile);
snapLog.serialize(dataTree, sessionsWithTimeouts, snapshotFile, syncSnap);
}
</code></pre>
<h3>总结</h3>
<p>通过本课时的学习,我们知道在 ZooKeeper 服务的运行过程中,<strong>会涉及内存数据</strong><strong>事务日志</strong><strong>数据快照这三种数据文件</strong>。从存储位置上来说,事务日志和数据快照一样,都存储在本地磁盘上;而从业务角度来讲,内存数据就是我们创建数据节点、添加监控等请求时直接操作的数据。事务日志数据主要用于记录本地事务性会话操作,用于 ZooKeeper 集群服务器之间的数据同步。事务快照则是将内存数据持久化到本地磁盘。</p>
<p>这里要注意的一点是,<strong>数据快照是每间隔一段时间才把内存数据存储到本地磁盘,因此数据并不会一直与内存数据保持一致</strong>。在单台 ZooKeeper 服务器运行过程中因为异常而关闭时,可能会出现数据丢失等情况。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/ZooKeeper源码分析与实战-完/19 Observer 的作用与 Follow 有哪些不同?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/ZooKeeper源码分析与实战-完/21 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":"709975f04daa3cfa","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>