<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Hadoop</title>
	<atom:link href="https://blog.gmem.cc/tag/hadoop/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 13 Apr 2026 08:03:10 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>ZooKeeper学习笔记</title>
		<link>https://blog.gmem.cc/zookeeper-study-note</link>
		<comments>https://blog.gmem.cc/zookeeper-study-note#comments</comments>
		<pubDate>Mon, 08 Aug 2016 09:23:19 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[BigData]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Hadoop]]></category>
		<category><![CDATA[ZooKeeper]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=15297</guid>
		<description><![CDATA[<p>基础知识 ZooKeeper是Hadoop的子项目，实现高可靠的分布式协调服务。它可以提供分布式的配置、同步、命名、集群服务。ZooKeeper暴露了一系列简单的接口，具有Java、C语言绑定。 为了正确构建复杂的服务，ZooKeeper提供以下保证： 顺序一致性：来自客户端的更新，按照它们被发送的顺序应用 原子性：更新要么成功要么失败 单一系统镜像：不管客户端连接到哪个ZooKeeper实例，它都看到服务的一致性视图 可靠性：一旦更新被写入，即是永久的 Timeliness：客户端看到的系统视图确保在一定时间范围内更新到最新状态 ZooKeeper组件 ZooKeeper由以下组件构成： 服务器：运行在ZooKeeper ensemble节点上的Java服务器 客户端：一个Java类库，用于链接到ZooKeeper集群 Native客户端：基于C实现的客户端 其它可选组件 Native客户端和可选组件仅仅支持Linux。 服务器端模块 除了Request Processor之外，所有ZooKeeper实例持有一模一样的组件： replicated database，一个内存数据库，包含整个名字空间。对此数据库的更新被刷入磁盘，以便重启后恢复 每个ZooKeeper实例都可以接受客户端请求，读请求基于本地的数据库处理，更新服务状态的写请求基于 <a class="read-more" href="https://blog.gmem.cc/zookeeper-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/zookeeper-study-note">ZooKeeper学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基础知识</span></div>
<p>ZooKeeper是Hadoop的子项目，实现高可靠的分布式协调服务。它可以提供<span style="background-color: #c0c0c0;">分布式的配置、同步、命名、集群服务</span>。ZooKeeper暴露了一系列简单的接口，具有Java、C语言绑定。</p>
<p>为了正确构建复杂的服务，ZooKeeper提供以下保证：</p>
<ol>
<li>顺序一致性：来自客户端的更新，按照它们被发送的顺序应用</li>
<li>原子性：更新要么成功要么失败</li>
<li>单一系统镜像：不管客户端连接到哪个ZooKeeper实例，它都看到服务的一致性视图</li>
<li>可靠性：一旦更新被写入，即是永久的</li>
<li>Timeliness：客户端看到的系统视图确保在一定时间范围内更新到最新状态</li>
</ol>
<div class="blog_h2"><span class="graybg">ZooKeeper组件</span></div>
<p>ZooKeeper由以下组件构成：</p>
<ol>
<li>服务器：运行在ZooKeeper ensemble节点上的Java服务器</li>
<li>客户端：一个Java类库，用于链接到ZooKeeper集群</li>
<li>Native客户端：基于C实现的客户端</li>
<li>其它可选组件</li>
</ol>
<p>Native客户端和可选组件仅仅支持Linux。</p>
<div class="blog_h2"><span class="graybg">服务器端模块</span></div>
<p>除了Request Processor之外，所有ZooKeeper实例持有一模一样的组件：</p>
<p><img class="aligncenter size-full wp-image-15303" src="https://blog.gmem.cc/wp-content/uploads/2016/08/zkcomponents.png" alt="zkcomponents" width="611" height="248" /></p>
<ol>
<li>replicated database，一个内存数据库，包含整个名字空间。对此数据库的更新被刷入磁盘，以便重启后恢复</li>
<li>每个ZooKeeper实例都可以接受客户端请求，读请求基于本地的数据库处理，更新服务状态的写请求基于 agreement protocol处理。此协议：
<ol>
<li>将一个实例作为leader，其它实例作为followers</li>
<li>follower从leader接收消息提议，并同意消息递送</li>
<li>消息层负责leader的故障转移、follower与leader的同步</li>
</ol>
</li>
<li>ZooKeeper使用一个定制化的原子消息协议，保证消息层是原子性的。这种原子性保证了本地数据库不会出现数据不一致性 </li>
</ol>
<div class="blog_h2"><span class="graybg">基本特性</span></div>
<div class="blog_h3"><span class="graybg">简单性</span></div>
<p>ZooKeeper很简单，它允许分布式进程通过一个共享的、有层次的名字空间来相互交互。这个名字空间的组织就像文件系统一样，名字空间由数据寄存器（data registers） —— 所谓znodes组成，类似于文件系统的目录/文件，ZooKeeper的数据是驻留内存的，而不是像文件系统那样写在磁盘上。</p>
<div class="blog_h3"><span class="graybg">复制性</span></div>
<p>就像被它管理的分布式进程，ZooKeeper本身也倾向于跨越一组宿主机（所谓ensemble）复制，整体上形成一个ZooKeeper服务。只要大部分节点可用，则ZooKeeper服务可用。</p>
<p>客户端连接到单个ZooKeeper节点，通过一个TCP连接来：</p>
<ol>
<li>发送请求</li>
<li>获得响应</li>
<li>获得监听的事件</li>
<li>发送心跳</li>
</ol>
<p>如果此TCP连接丢失，客户端可能连接到另外一个节点。</p>
<div class="blog_h3"><span class="graybg">有序性</span></div>
<p>ZooKeeper使用数字标注每个update，以反应所有ZooKeeper事务的顺序。后续操作可以使用此数字序号实现高层次的抽象，例如同步原语。</p>
<div class="blog_h3"><span class="graybg">高性能</span></div>
<p>ZooKeeper非常高速，特别是在读为主的应用场景下。在上千台机器上运行的ZooKeeper应用，其性能在读写比10:1左右最好。</p>
<div class="blog_h2"><span class="graybg">名字空间</span></div>
<p>ZooKeeper提供的名字空间非常类似于Linux目录树，由一系列以 / 分隔的路径元素组成，名字空间中的每个节点以完整路径唯一识别。</p>
<div class="blog_h2"><span class="graybg">时间</span></div>
<p>ZooKeeper使用多种方式来追踪时间：</p>
<ol>
<li>Zxid：即ZooKeeper事务ID。每次对ZooKeeper的状态做出修改，都对应一个这样的ID。这个ID是单调递增的</li>
<li>版本号：对节点的修改会导致其某个版本号字段变更</li>
<li>时间单元（Ticks）：在集群中，ZooKeeper使用时间单元来界定状态上传、会话过期、Peer连接过期的最小时间间隔</li>
<li>真实时间：节点的创建、修改时间使用真实时间记录</li>
</ol>
<div class="blog_h2"><span class="graybg">会话</span></div>
<p>客户端利用C/Java语言绑定库建立会话：</p>
<ol>
<li>客户端发起调用时，可以指定连接字符串、会话超时、默认Watcher等参数，连接字符串包括逗号分隔的多个ZooKeeper实例的host:port列表</li>
<li>客户端创建一个到ZooKeeper服务集群的句柄（Handle），句柄被创建后，进入CONNECTING状态。这时，ZooKeeper会：
<ol>
<li>创建一个代表会话ID的64位整数并分配给客户端</li>
<li>同时，发送一个会话密码给客户端，客户端重连时需要用到此密码</li>
<li>根据客户端请求的会话超时ms，确定一个允许的（受限于时间单元，必须在2-20倍之间）会话超时</li>
</ol>
</li>
<li>绑定库尝试建立到某个ZooKeeper实例的TCP连接。连接建立后，句柄进入CONNECTED状态</li>
<li>如果出现不可恢复的错误，包括会话过期、身份验证失败、客户端显式的关闭句柄，则句柄进入CLOSED状态</li>
<li>如果客户端发现连接意外断开，则尝试连接列表中下一个实例，直到重新连接上。客户端重新连接时，会在握手时携带自己的会话ID、密码</li>
<li>客户端重新连接上之后，可能发现会话已经过期</li>
<li>默认Watcher可以在会话状态发生变化（例如连接断开、会话过期）后，通知客户端</li>
</ol>
<p>从3.2开始，连接字符串可以有一个chroot后缀，例如<pre class="crayon-plain-tag">127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/pems/webmgr</pre> ，其意义是，将客户端的根znode设置为/pems/webmgr，所有绝对、相对路径均假设此znode为根。</p>
<p>客户端通过发送PING心跳来保活，这样双方都可以知道连接是否断开。</p>
<p>客户端可以变更连接字符串，也就是变更服务器列表。此变更可能触发一个负载均衡算法，并导致客户端重连到其它服务器。</p>
<div class="blog_h2"><span class="graybg">节点</span></div>
<p>名字空间的每个节点被称为znode，类似于文件系统的目录。znode有以下特点：</p>
<ol>
<li>路径是znode的唯一标识</li>
<li>znode可以具有关联的数据，还可以具有子节点</li>
<li>znode关联的数据通常很小，一般最大KB级别</li>
<li>znode具有一个状态结构（Stat Structure），存放版本号、数据长度、修改时间戳等信息</li>
<li>znode的数据发生变化，版本号就递增</li>
<li>znode同时保存数据的多个版本</li>
<li>读写znode数据的操作都是原子性的，写操作替换掉数据</li>
<li>每个节点包含一个访问控制列表（ACL）用于限制谁能做什么</li>
<li>znode可以被监控（watch），一旦数据修改、子节点变化，即可通常关注此znode的客户端。这是ZooKeeper的核心特性</li>
</ol>
<div class="blog_h3"><span class="graybg">临时节点</span></div>
<p>所谓临时（EPHEMERAL）节点，仅仅在创建它的Session存在期间存在。ZooKeeper的客户端Session基于TCP长连接，通过心跳来保活。</p>
<div class="blog_h3"><span class="graybg">有序节点</span></div>
<p>当创建一个znode时，你可以要求ZooKeeper<span style="background-color: #c0c0c0;">在路径尾部自动附加单调递增</span>的计数器。对于父节点来说，此计数器具有唯一性。计数器十位左侧补零，示例：<pre class="crayon-plain-tag">&lt;path&gt;0000000001</pre></p>
<p>计数器的下一个值，存储在父节点中。如果计数超过2147483647 会出现溢出。</p>
<div class="blog_h3"><span class="graybg">容器节点</span></div>
<p>3.6版本新增。这类节点的所有子节点都消失后，可能在未来的某个时间点被自动删除。</p>
<div class="blog_h3"><span class="graybg">状态结构</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">字段</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>czxid</td>
<td>创建此节点的zxid </td>
</tr>
<tr>
<td>mzxid</td>
<td>最后一次修改此节点的zxid </td>
</tr>
<tr>
<td>pzxid</td>
<td>最后一次修改子节点的zxid </td>
</tr>
<tr>
<td>ctime</td>
<td>此节点创建的时间</td>
</tr>
<tr>
<td>mtime</td>
<td>此节点被修改的时间</td>
</tr>
<tr>
<td>version</td>
<td>关联数据变更版本号</td>
</tr>
<tr>
<td>cversion</td>
<td>子节点变更版本号</td>
</tr>
<tr>
<td>aversion</td>
<td>ACL版本号</td>
</tr>
<tr>
<td>ephemeralOwner</td>
<td>
<p>如果此节点是临时节点，则此字段存放节点创建者的会话ID</p>
<p>如果不是临时节点，值为0</p>
</td>
</tr>
<tr>
<td>dataLength</td>
<td>关联数据的长度</td>
</tr>
<tr>
<td>numChildren</td>
<td>子节点的数量</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">监控</span></div>
<p>ZooKeeper支持监控（Watch）的概念——<span style="background-color: #c0c0c0;">一次性的事件监听器</span>。它有三个要点：</p>
<ol>
<li>一次性的：当被监控数据发生变化后，事件发送且仅一次。例如<pre class="crayon-plain-tag">getData("/znode1", true)</pre>导致当znode1的关联数据发生变化后客户端得到通知，但是后续znode1的数据再发生变化则不会自动得到通知</li>
<li>发送给客户端：事件被异步的发送给客户端，并且ZooKeeper提供顺序性保证，客户端不可能在收到通知之前就看到目标数据的变化</li>
<li>监控什么数据：getData/exists针对关联数据进行监控，getChildren()则针对子节点进行监控</li>
</ol>
<p>ZooKeeper中所有的读操作，包括<pre class="crayon-plain-tag">getData()</pre>、<pre class="crayon-plain-tag">getChildren()</pre>、<pre class="crayon-plain-tag">exists()</pre>，都可选设置Watch。</p>
<p>Watch仅仅在客户端所连接到的服务器上维护，但如果客户端发生重连，则新服务器负责维护客户端之前注册的Watch。有一种情况下，Watch会遗漏监控：</p>
<ol>
<li>客户端设置了exists监控，然后客户端断开</li>
<li>被监控节点创建，然后又被删除</li>
<li>客户端重连，此时它不会收到Watch</li>
</ol>
<p>客户端可以调用removeWatches来移除监控。</p>
<div class="blog_h3"><span class="graybg">关于监控的注意事项</span></div>
<p>ZooKeeper对Watch提供以下保证：</p>
<ol>
<li>先发生的事件，其Watcher先被通知</li>
<li>针对同一事件先注册的Watcher先被通知</li>
<li>先得到Watch通知，然后才能看到目标znode数据的变化</li>
</ol>
<p>使用Watch时需要注意：</p>
<ol>
<li>一次性，如果需要获得后续变更通知，需要再次注册Watcher</li>
<li>由于再次注册会有网络延迟，这期间目标znode可能已经发生了多次变化，这些变化是捕获不到的</li>
<li>在连接断开期间，接收不到通知</li>
</ol>
<div class="blog_h3"><span class="graybg">监控与API对照表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">事件类型</td>
<td style="text-align: center;">可监控该事件的API</td>
</tr>
</thead>
<tbody>
<tr>
<td>节点创建</td>
<td>exists</td>
</tr>
<tr>
<td>节点删除</td>
<td>exists / getData / getChildren</td>
</tr>
<tr>
<td>节点数据改变</td>
<td>getData</td>
</tr>
<tr>
<td>子节点的创建、删除、改变</td>
<td>getChildren</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">访问控制</span></div>
<p>ZooKeeper支持基于ACL对znode进行访问控制。其实现有些类似UNIX文件模式，不同的是，ZooKeeper没有user/group/world这三种角色，它允许定义无限数量的角色（ID），并为这些角色授权。此外<span style="background-color: #c0c0c0;">ACL不是递归的</span>，也就是子节点不会继承父节点的授权设置。</p>
<p>ZooKeeper支持可拔插的身份验证方案（scheme）。ID的形式则为<pre class="crayon-plain-tag">scheme:id</pre>，例如ip:172.16.16.1表示IP验证方案下的实体172.16.16.1。</p>
<p>当客户端登录ZooKeeper并对自己进行身份验证时，ZooKeeper将所有对应此客户端的ID都分配给它。当客户端访问znode时，这些ID用来进行ACL验证。</p>
<p>ACL中每个条目的格式为<pre class="crayon-plain-tag">(scheme:expression, perms)</pre>，其中expression取决于scheme，覆盖1-N个ID。例如<pre class="crayon-plain-tag">(ip:172.21.0.0/16, READ)</pre>表示授予172.21网段所有客户端读权限。</p>
<div class="blog_h3"><span class="graybg">内置验证模式</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">Scheme</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>world</td>
<td>此模式仅包含单个id —— anyone，表示任何人</td>
</tr>
<tr>
<td>auth</td>
<td>此模式没有id，表示任何通过身份验证的人</td>
</tr>
<tr>
<td>digest</td>
<td>基于username:password字符串生产id，用在ACL条目中密码显示为Base64编码的SH1摘要</td>
</tr>
<tr>
<td>ip</td>
<td>使用客户端IP地址作为id</td>
</tr>
<tr>
<td>x509</td>
<td>使用数字整数进行验证，在ACL条目中使用X500主体名称作为id</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">ACL权限列表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">Perm</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>CREATE</td>
<td>允许创建子节点</td>
</tr>
<tr>
<td>READ</td>
<td>允许读取数据、列出子节点</td>
</tr>
<tr>
<td>WRITE</td>
<td>允许修改节点的关联数据</td>
</tr>
<tr>
<td>DELETE</td>
<td>允许删除子节点</td>
</tr>
<tr>
<td>ADMIN</td>
<td>允许设置权限</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">安装配置</span></div>
<div class="blog_h2"><span class="graybg">安装</span></div>
<p>首先保证1.7或者更高版本的JDK已经安装，然后到<a href="https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/stable/">稳定频道</a>下载二进制压缩包，解压到合适的目录中。</p>
<div class="blog_h2"><span class="graybg">配置</span></div>
<div class="blog_h3"><span class="graybg">Standalone </span></div>
<p>要启动ZooKeeper服务，需要创建一个配置文件conf/zoo.cfg：</p>
<pre class="crayon-plain-tag"># 基本的时间单元对应的毫秒数，心跳每个时间单元发送一次，会话过期最小时间2时间单元
tickTime=2000
# 内存数据库的快照存放目录
dataDir=/data
# 监听客户端连接的端口
clientPort=2181</pre>
<div class="blog_h3"><span class="graybg">quorum</span></div>
<p>独立运行的ZooKeeper可以用户开发、测试目的。在产品环境下，你需要在复制（Replication）模式下运行ZooKeeper。一组复制的ZookKeeper称为Quorum，它们必须共享一致的配置文件：</p>
<pre class="crayon-plain-tag">tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
# 连接到Leader超时时间单元
initLimit=5
# Follower最多落后Leader多少时间单元
syncLimit=2
quorumListenOnAllIPs=true
# 服务器列表，server.X中的X是实例ID，在运行时，每个实例检查自己数据目录下的myid文件，其中以ASCII写着自己的ID
# 前一个端口供Follower连接到Leader使用；后一个端口用于选举Leader
server.1=172.21.0.1:2888:3888
server.2=172.21.0.2:2888:3888
server.3=172.21.0.3:2888:3888</pre>
<p>每个Quorum最少应该包含3个ZooKeeper实例，并且应当配置奇数个数的实例。</p>
<div class="blog_h3"><span class="graybg">docker</span></div>
<p>官方镜像的配置文件路径为/conf/zoo.cfg，下面的命令创建容器：</p>
<pre class="crayon-plain-tag"># ZOO_MY_ID 用于设置myid文件，此镜像假设数据目录是/data
docker run -e "ZOO_MY_ID=2" --name zookeeper-2 --network local --ip 172.21.0.2 -d docker.gmem.cc/zookeeper
docker run -e "ZOO_MY_ID=3" --name zookeeper-3 --network local --ip 172.21.0.3 -d docker.gmem.cc/zookeeper</pre>
<div class="blog_h3"><span class="graybg">日志配置</span></div>
<p>环境变量<pre class="crayon-plain-tag">ZOO_LOG_DIR</pre>用于指定ZooKeeper运行期间产生的日志的存放目录。包括log4j日志和守护程序的stdout都存放在此目录中。</p>
<p>环境变量<pre class="crayon-plain-tag">ZOO_LOG4J_PROP</pre>用于设置log4j的日志级别、启用的Appender。此变量的默认值是INFO, CONSOLE，也即是将最低INFO级别的日志输出到标准输出。可以设置为<pre class="crayon-plain-tag">ERROR,ROLLINGFILE</pre>，以使用滚动日志。</p>
<div class="blog_h2"><span class="graybg">运行</span></div>
<pre class="crayon-plain-tag"># 在后台启动ZooKeeper服务
zkServer.sh start
# 在前台启动ZooKeeper服务
zkServer.sh start-foreground
# 停止ZooKeeper服务
zkServer.sh stop

# 启动CLI客户端
zkCli -server 127.0.0.1:2181</pre>
<div class="blog_h1"><span class="graybg">开发</span></div>
<div class="blog_h2"><span class="graybg">简单的API</span></div>
<p>ZooKeeper具有非常简单的编程接口，它仅仅提供以下操作：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">操作</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>create</td>
<td>在名字空间树中创建一个节点</td>
</tr>
<tr>
<td>delete</td>
<td>删除一个节点</td>
</tr>
<tr>
<td>exists</td>
<td>测试某个路径上的节点是否存在</td>
</tr>
<tr>
<td>get data</td>
<td>读取节点数据</td>
</tr>
<tr>
<td>set data</td>
<td>写入节点数据</td>
</tr>
<tr>
<td>get children</td>
<td>获取子节点列表</td>
</tr>
<tr>
<td>sync</td>
<td>等待数据传播到整个集群</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Java绑定</span></div>
<div class="blog_h3"><span class="graybg">Maven依赖</span></div>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.zookeeper&lt;/groupId&gt;
    &lt;artifactId&gt;zookeeper&lt;/artifactId&gt;
    &lt;version&gt;3.4.10&lt;/version&gt;
&lt;/dependency&gt;</pre>
<div class="blog_h3"><span class="graybg">有序节点</span></div>
<p>使用模式EPHEMERAL可以创建有序节点，反复针对同一路径创建有序节点，会自动后缀单调递增的编号。</p>
<pre class="crayon-plain-tag">ArrayList&lt;ACL&gt; acl = ZooDefs.Ids.OPEN_ACL_UNSAFE;
byte[] data = { 0 };
// 临时节点不支持子节点
try {
    // 同步创建
    zk.create( "/tmp", data, acl, CreateMode.PERSISTENT );
} catch ( KeeperException e ) {
    if ( e.code() != KeeperException.Code.NODEEXISTS ) throw e;
}
// 异步创建
zk.create( "/tmp/no", data, acl, CreateMode.EPHEMERAL_SEQUENTIAL, ( rc, path, ctx, name ) -&gt; {
    // rc: OK path: /tmp/no, name: /tmp/no0000000000
    // rc: OK path: /tmp/no, name: /tmp/no0000000001
    // rc: OK path: /tmp/no, name: /tmp/no0000000002
    LOGGER.debug( "rc: {} path: {}, name: {}", new Object[]{ KeeperException.Code.get( rc ), path, name } );
}, this );
TimeUnit.SECONDS.sleep( 1 );</pre>
<div class="blog_h3"><span class="graybg">简单Watch客户端</span></div>
<p>这个例子中，我们使用一个Agent来：</p>
<ol>
<li>管理到ZooKeeper的连接，一旦会话过期即重连</li>
<li>转发ZooKeeper发来的事件通知给客户端</li>
<li>对事件机制进行简单封装，实现了：
<ol>
<li>隔离客户端和ZooKeeper</li>
<li>会话过期后，自动重新注册Watch</li>
</ol>
</li>
</ol>
<p>Agent代码：</p>
<pre class="crayon-plain-tag">package cc.gmem.study;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;

public class ZooKeeperAgent implements Watcher {

    private static final Logger LOGGER = LoggerFactory.getLogger( ZooKeeperAgent.class );


    private ZooKeeper zk;

    private String connectString;

    private int timeout;

    private Map&lt;Event.EventType, Map&lt;String, Queue&lt;Consumer&lt;EventResolver&gt;&gt;&gt;&gt; allListeners;

    // 对WatchedEvent进行简单包装，支持获取znode数据，对客户端屏蔽ZooKeeper
    public class EventResolver {

        private WatchedEvent event;

        public EventResolver( WatchedEvent event ) {
            this.event = event;
        }

        public WatchedEvent getEvent() {
            return event;
        }

        public byte[] getData() {

            return getData( null );
        }

        public byte[] getData( Stat stat ) {
            if ( stat == null ) {
                stat = new Stat();
            }
            try {
                return zk.getData( event.getPath(), false, stat );
            } catch ( Exception e ) {
                throw new RuntimeException( e.getMessage(), e );
            }
        }
    }

    public ZooKeeperAgent( String connectString, int timeout ) {
        this.connectString = connectString;
        this.timeout = timeout;
        allListeners = new ConcurrentHashMap&lt;&gt;();
        allListeners.put( Event.EventType.NodeCreated, new ConcurrentHashMap&lt;&gt;() );
        allListeners.put( Event.EventType.NodeDataChanged, new ConcurrentHashMap&lt;&gt;() );
        allListeners.put( Event.EventType.NodeDeleted, new ConcurrentHashMap&lt;&gt;() );
        allListeners.put( Event.EventType.NodeChildrenChanged, new ConcurrentHashMap&lt;&gt;() );
        allListeners = Collections.unmodifiableMap( allListeners );
        createZooKeeper();
    }

    public void on( Event.EventType eventType, String path, Consumer&lt;EventResolver&gt; listener ) {
        Map&lt;String, Queue&lt;Consumer&lt;EventResolver&gt;&gt;&gt; listenersOfType = this.allListeners.get( eventType );
        Queue&lt;Consumer&lt;EventResolver&gt;&gt; listeners;
        synchronized ( this ) {
            listeners = listenersOfType.get( path );
            if ( listeners == null ) {
                listeners = new ConcurrentLinkedQueue&lt;&gt;();
                listenersOfType.put( path, listeners );
            }
        }
        listeners.add( listener );
        watch( eventType, path );
    }

    private void watch( Event.EventType eventType, String path ) {
        try {
            switch ( eventType ) {
                case NodeCreated:
                    // 这里可以传递一个Watcher类型，也可以传入boolean
                    // 如果为true，使用构造ZooKeeper时提供的Watcher ———— 所谓defaultWatcher
                    zk.exists( path, this );
                    break;
                case NodeDataChanged:
                    Stat stat = new Stat();
                    zk.getData( path, this, stat );
                    break;
                case NodeDeleted:
                    zk.exists( path, this );
                    break;
                case NodeChildrenChanged:
                    zk.getChildren( path, this );
                    break;
            }
        } catch ( Exception e ) {
            LOGGER.error( e.getMessage(), e );
        }
    }

    private void createZooKeeper() {
        try {
            zk = new ZooKeeper( connectString, timeout, this );
            // 新会话，Watch需要重新注册
            allListeners.entrySet().stream().flatMap( entry -&gt; {
                Event.EventType eventType = entry.getKey();
                return entry.getValue().keySet().stream().map( path -&gt; {
                    return new Object[]{ eventType, path };
                } );
            } ).forEach( args -&gt; watch( (Event.EventType) args[0], (String) args[1] ) );
        } catch ( IOException e ) {
            throw new RuntimeException( e.getMessage(), e );
        }
    }

    public void process( WatchedEvent event ) {
        switch ( event.getType() ) {
            case None:
                onKeeperStateEvent( event );
                break;
            default:
                onZnodeEvent( event );
        }
    }

    private void onKeeperStateEvent( WatchedEvent event ) {
        switch ( event.getState() ) {
            case SyncConnected:
                LOGGER.debug( "Connected to server with session id {}", zk.getSessionId() );
                break;
            case Expired:
                LOGGER.debug( "Session expired, recreating" );
                // 一旦会话过期，ZooKeeper对象就废了
                createZooKeeper();
                break;
        }
    }

    private void onZnodeEvent( WatchedEvent event ) {
        Queue&lt;Consumer&lt;EventResolver&gt;&gt; consumers = allListeners.get( event.getType() ).get( event.getPath() );
        if ( consumers != null ) {
            consumers.forEach( consumer -&gt; consumer.accept( new EventResolver( event ) ) );
            // 继续监控
            watch( event.getType(), event.getPath() );
        }
    }

}</pre>
<p>客户端代码：</p>
<pre class="crayon-plain-tag">package cc.gmem.study;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

import static org.apache.zookeeper.Watcher.Event.EventType.*;

/**
 * Created by alex on 8/10/16.
 */
public class WatchClient {

    private static final Logger LOGGER = LoggerFactory.getLogger( WatchClient.class );

    public static void main( String[] args ) throws InterruptedException {
        String connectString = "172.21.0.1:2181,172.21.0.2:2181,172.21.0.3:2181";
        ZooKeeperAgent agent = new ZooKeeperAgent( connectString, 2000 * 20 );
        agent.on( NodeCreated, "/user", ( resolver ) -&gt; {
            Stat stat = new Stat();
            LOGGER.debug( "znode craeted with data: {}", new String( resolver.getData( stat ) ) );
        } );
        agent.on( NodeDataChanged, "/user", ( resolver ) -&gt; {
            Stat stat = new Stat();
            LOGGER.debug( "znode data: {}, version: {}", new String( resolver.getData( stat ) ), stat.getVersion() );
        } );
        agent.on( NodeDeleted, "/user", ( resolver ) -&gt; {
            LOGGER.debug( "znode deleted" );
        } );
        TimeUnit.DAYS.sleep( 1 );
    }
}</pre>
<div class="blog_h1"><span class="graybg">应用场景</span></div>
<div class="blog_h2"><span class="graybg">命名服务</span></div>
<p>ZooKeeper名字空间可以直接当做类似于JNDI的命名服务使用，ZooKeeper和JNDI都可以把目录节点关联到特定的资源（但是JNDI的资源是Java对象），ZooKeeper还具有与生俱来的高可用性。</p>
<div class="blog_h2"><span class="graybg">配置管理</span></div>
<p>ZooKeeper可以用来管理分布式系统中的配置项，如果多个应用服务实例需要共享很多系统参数，可以交由ZooKeeper来管理，通过Watch的方式，应用服务可以在配置变更后获得通知。</p>
<div class="blog_h2"><span class="graybg">集群管理</span></div>
<p>ZooKeeper可以管理服务的集群：</p>
<ol>
<li>当新增服务器、删除服务器（服务器宕机）时，客户端、ZooKeeper、集群成员可以得到通知。实现原理是：
<ol>
<li>每个集群成员启动后，都在某个znode下创建临时子节点，这个子节点依赖于TCP长连接保活</li>
<li>一旦集群成员宕机，临时子节点由于TCP连接的断开而自动删除</li>
<li>关注者可以在父节点上调用getChildren( parentPath, true)进行watch，一旦子节点发生增减，此调用即返回</li>
</ol>
</li>
<li>在集群成员中选取Leader（Master）。实现原理是：
<ol>
<li>每个成员创建不但是临时，还是有序（SEQUENTIAL）的子节点</li>
<li>序号最小的成员，总是作为Leader</li>
<li>如果当前Leader宕机，则次小的成员被选作Leader</li>
</ol>
</li>
</ol>
<div class="blog_h2"><span class="graybg">分布式锁</span></div>
<p>要实现独占锁，可以把多个协作者共享的资源抽象为一个znode，然后：</p>
<ol>
<li>需要占用此资源的协作者，在znode 下创建一个临时、有序子节点</li>
<li>协作者判断自己这个节点序号是否最小：
<ol>
<li>如果是，意味着获得锁</li>
<li>如果否，则在父节点上watch。收到通知后，继续执行步骤2</li>
</ol>
</li>
<li> 当需要是否资源时，删除自己创建的子节点即可</li>
</ol>
<div class="blog_h2"><span class="graybg">屏障</span></div>
<p>所谓屏障（Barrier），即协作者到达某个状态后，等待其它协作者都到达此状态，然后一起继续。基于ZooKeeper可以这样实现屏障：</p>
<ol>
<li>每个协作者到达状态后，均创建一个子节点</li>
<li>判断子节点总数是否足够：
<ol>
<li>如果是，跨越屏障继续执行业务逻辑</li>
<li>如果否，则在父节点上watch。收到通知后，继续执行步骤2</li>
</ol>
</li>
</ol>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">在Docker桥接接口上监听失败</span></div>
<p>报错信息：</p>
<p>2017-08-09 17:53:03,750 [myid:1] - ERROR [/172.21.1.1:3888:QuorumCnxManager$Listener@763] - Exception while listening<br />java.net.BindException: Cannot assign requested address (Bind failed)</p>
<p>解决办法：配置文件添加<pre class="crayon-plain-tag">quorumListenOnAllIPs=true</pre></p>
<div class="blog_h3"><span class="graybg">如何查看集群状态</span></div>
<p>可以通过JMX查看，通过Oracle Mission Controll连接到名为 org.apache.zookeeper.server.quorum.QuorumPeerMain  的JVM，在MBean Browser选项卡中可以看到相关信息。</p>
<div class="blog_h3"><span class="graybg">zookeeper.out</span></div>
<p>此文件是ZooKeeper运行期间的标准输出，要指定它的存放位置，可以设置<pre class="crayon-plain-tag">ZOO_LOG_DIR</pre>这个环境变量。</p>
<p>注意，上述环境变量同时也作为log4j的日志输出目录。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/zookeeper-study-note">ZooKeeper学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/zookeeper-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
