
大体流程是:启动时
1.对zk上某个目录节点A添加CHILD的watch事件,以监听A的子节点数量变更,及时更新cache;
2. 对A的所有子节点进行一次get,并添加每个子节点的变更watch事件,当节点内容变更或者节点删除时会收到回调。
这套系统运行很长时间,整体跑得很ok。但最近做了一个新功能,线下验证过程中出现了各种奇奇怪怪的问题,比如两个节点直接出现了请求的无限转发,导致cpu持续打满;或者某个任务被分配到了某个节点负责(映射关系在zk上记录),但是该节点始终不启动这个任务。
根据debug日志定位了2天,才发现是一个典型的ABA问题,导致cache不对了。
为啥之前系统跑得好好的呢?因为是去中心化的设计,某些实例节点对某些zk节点的cache不对也不影响整体运行,只有当所有实例节点的zcache都不对,才会work不了。(去中心化的设计有一定优势,但也有一些问题,不同场景需求不同,没有绝对的孰好孰坏)。
接下来具体说一下问题:
假设zk上有目录 A,A下面有节点b,即目录结构为 /A/b
某一时刻节点b被删除了,并且马上重建,则根据前文所述我们加上的watch,zk会给我们发送2个事件
1. 目录A的子节点发生变更(加在A上的watch)
2. 节点b发生删除(加在b上的watch)
此时根据我们实现的(有问题)逻辑,直接忽略事件2,然后对A进行一次get children请求,根据拿到的最新子节点列表,跟内存的节点做diff,来删除或者增加cache中的节点。 但是此时节点b又被创建了,所以本次 *** 作不会对cache中的b进行修改。 注意,由于b节点的watch已经触发了,所以b节点的变更将不会再次发送事件通知,即我们内存中所看到的b节点内容永远是错的,除非下一次节点b再被删除,并且我们正确地从cache中删掉他,然后b再创建,我们再次get并加上watch。
对于这种典型的ABA问题,我们直接想到的就是在get children拿到的最新子节点列表,与内存cache做diff时,带上版本信息,就能搞定了。但是zk的get children接口只能拿到父节点的stat(包含版本、修改时间等),没有返回所有子节点的stat,故此时我们拿不到版本信息。
我们的解决办法是,在收到事件2后,直接对节点b再发送一次get请求,若返回NO NODE,说明b确实被删除了,将内存cache中节点b删除;若返回ok,说明节点b又被创建了,则用最新的内容更新内存cache中的b信息,并且此时还是带着节点b的watch的。
掌握zookeeper事件监听机制,非常重要,可以说是跨入了进阶的门槛,只有掌握了如何监听某个节点或路径,我们才能在节点变化后,做一些我们想做的事,包括:1,配置文件同步
2,主从切换
3,分布式队列
4,分布式锁
5,其他
首先,在以前的文章里面有写过使用zookeeper原生的api,监听zk节点变化,那么本篇我们就来看下,如何使用curator来完成监听,代码如下:
<pre name="code" class="java">package com.qin.curator.zk
import javax.sound.midi.Patch
import org.apache.curator.RetryPolicy
import org.apache.curator.framework.CuratorFramework
import org.apache.curator.framework.CuratorFrameworkFactory
import org.apache.curator.framework.CuratorFrameworkFactory.Builder
import org.apache.curator.framework.api.CuratorWatcher
import org.apache.curator.framework.recipes.cache.NodeCache
import org.apache.curator.framework.recipes.cache.NodeCacheListener
import org.apache.curator.framework.recipes.cache.PathChildrenCache
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener
import org.apache.curator.retry.ExponentialBackoffRetry
import org.apache.curator.utils.ZKPaths
import org.apache.zookeeper.WatchedEvent
/**
*
* 使用curator监听zookeeper节点
* @author qindongliang
* **/
public class CuratorWatch {
static CuratorFramework zkclient=null
static String nameSpace="php"
static {
String zkhost="192.168.46.22:2181"//zk的host
RetryPolicy rp=new ExponentialBackoffRetry(1000, 3)//重试机制
Builder builder = CuratorFrameworkFactory.builder().connectString(zkhost)
.connectionTimeoutMs(5000)
.sessionTimeoutMs(5000)
.retryPolicy(rp)
builder.namespace(nameSpace)
CuratorFramework zclient = builder.build()
zkclient=zclient
zkclient.start()// 放在这前面执行
zkclient.newNamespaceAwareEnsurePath(nameSpace)
}
public static void main(String[] args) throws Exception{
watch()
Thread.sleep(Long.MAX_VALUE)
}
/**
*
* 监听节点变化
*
* */
public static void watch()throws Exception{
PathChildrenCache cache = new PathChildrenCache(zkclient, "/zk", false)
cache.start()
System.out.println("监听开始/zk........")
PathChildrenCacheListener plis=new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
throws Exception {
switch ( event.getType() )
{
case CHILD_ADDED:
{
System.out.println("Node added: " + ZKPaths.getNodeFromPath(event.getData().getPath()))
break
}
case CHILD_UPDATED:
{
System.out.println("Node changed: " + ZKPaths.getNodeFromPath(event.getData().getPath()))
break
}
case CHILD_REMOVED:
{
System.out.println("Node removed: " + ZKPaths.getNodeFromPath(event.getData().getPath()))
break
}
}
}
}
//注册监听
cache.getListenable().addListener(plis)
}
}
</pre>
运行后的控制台打印:
<pre name="code" class="java">18:33:07.722 [main] INFO o.a.c.f.imps.CuratorFrameworkImpl - Starting
18:33:07.727 [main] DEBUG o.a.curator.CuratorZookeeperClient - Starting
18:33:07.727 [main] DEBUG org.apache.curator.ConnectionState - Starting
18:33:07.727 [main] DEBUG org.apache.curator.ConnectionState - reset
18:33:07.734
[main] INFO org.apache.zookeeper.ZooKeeper - Client
environment:zookeeper.version=3.4.6-1569965, built on 02/20/2014 09:09
GMT
18:33:07.734 [main] INFO org.apache.zookeeper.ZooKeeper - Client environment:host.name=QINDONGLIANG.dhgatecn.msf
18:33:07.734 [main] INFO org.apache.zookeeper.ZooKeeper - Client environment:java.version=1.7.0_04
18:33:07.734 [main] INFO org.apache.zookeeper.ZooKeeper - Client environment:java.vendor=Oracle Corporation
18:33:07.734 [main] INFO org.apache.zookeeper.ZooKeeper - Client environment:java.home=D:\Java\jdk1.7.0_04\jre
18:33:07.734
[main] INFO org.apache.zookeeper.ZooKeeper - Client
environment:java.class.path=D:\eclipseworkspace2yw\opzk\binD:\eclipseworkspace2yw\opzk\lib\curator-client-2.6.0.jarD:\eclipseworkspace2yw\opzk\lib\curator-examples-2.6.0.jarD:\eclipseworkspace2yw\opzk\lib\curator-framework-2.6.0.jarD:\eclipseworkspace2yw\opzk\lib\curator-recipes-2.6.0.jarD:\eclipseworkspace2yw\opzk\lib\curator-test-2.6.0.jarD:\eclipseworkspace2yw\opzk\lib\curator-x-discovery-2.6.0.jarD:\eclipseworkspace2yw\opzk\lib\curator-x-discovery-server-2.6.0.jarD:\eclipseworkspace2yw\opzk\lib\curator-x-rpc-2.6.0.jarD:\eclipseworkspace2yw\opzk\lib\log4j-1.2.15.jarD:\eclipseworkspace2yw\opzk\lib\zookeeper-3.4.5.jarD:\eclipseworkspace2yw\opzk\lib\commons-io-2.1.jar
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)