作者:洞庭散人

出处:http://phinecos.cnblogs.com/    

本博客遵从Creative Commons
Attribution 3.0 License
,若用于非商业目的,您可以自由转载,但请保留原作者信息和文章链接URL。

上一篇中介绍了SolrCloud的第一个模块---构建管理solr集群状态信息的zookeeper集群。当我们在solr服务器启动时拥有了这样一个Zookeeper集群后,显然我们需要连接到Zookeeper集群的方便手段,在这一篇中我将对Zookeeper客户端相关的各个封装类进行分析。

SolrZkClient类是Solr服务器用来与Zookeeper集群进行通信的接口类,它包含的主要组件有:

  private ConnectionManager connManager;

  private volatile SolrZooKeeper keeper;

  private ZkCmdExecutor zkCmdExecutor = new ZkCmdExecutor();

其中ConnectionManager是Watcher的实现类,主要负责对客户端与Zookeeper集群之间连接的状态变化信息进行响应,关于Watcher的详细介绍,可以参考http://zookeeper.apache.org/doc/trunk/zookeeperProgrammers.html#ch_zkWatches

SolrZooKeeper类是一个包装类,没有实际意义,ZkCmdExecutor类是负责在连接失败的情况下,重试某种操作特定次数,具体的操作是ZkOperation这个抽象类的具体实现子类,其execute方法中包含了具体操作步骤,这些操作包括新建一个Znode节点,读取Znode节点数据,创建Znode路径,删除Znode节点等Zookeeper操作。

首先来看它的构造函数,先创建ConnectionManager对象来响应两端之间的状态变化信息,然后ZkClientConnectionStrategy类是一个连接策略抽象类,它包含连接和重连两种策略,并且采用模板方法模式,具体的实现是通过静态累不类ZkUpdate来实现的,DefaultConnectionStrategy是它的一个实现子类,它覆写了connect和reconnect两个连接策略方法。

  public SolrZkClient(String zkServerAddress, int zkClientTimeout,

      ZkClientConnectionStrategy strat, final OnReconnect onReconnect, int clientConnectTimeout) throws InterruptedException,

      TimeoutException, IOException {

    connManager = new ConnectionManager("ZooKeeperConnection Watcher:"

        + zkServerAddress, this, zkServerAddress, zkClientTimeout, strat, onReconnect);

    strat.connect(zkServerAddress, zkClientTimeout, connManager,

        new ZkUpdate() {

          @Override

          public void update(SolrZooKeeper zooKeeper) {

            SolrZooKeeper oldKeeper = keeper;

            keeper = zooKeeper;

            if (oldKeeper != null) {

              try {

                oldKeeper.close();

              } catch (InterruptedException e) {

                // Restore the interrupted status

                Thread.currentThread().interrupt();

                log.error("", e);

                throw new ZooKeeperException(SolrException.ErrorCode.SERVER_ERROR,

                    "", e);

              }

            }

          }

        });

    connManager.waitForConnected(clientConnectTimeout);

    numOpens.incrementAndGet();

  }

值得注意的是,构造函数中生成的ZkUpdate匿名类对象,它的update方法会被调用,

在这个方法里,会首先将已有的老的SolrZooKeeperg关闭掉,然后放置上一个新的SolrZooKeeper。做好这些准备工作以后,就会去连接Zookeeper服务器集群,

connManager.waitForConnected(clientConnectTimeout);//连接zk服务器集群,默认30秒超时时间

其实具体的连接动作是new SolrZooKeeper(serverAddress, timeout, watcher)引发的,上面那句代码只是在等待指定时间,看是否已经连接上。

如果连接Zookeeper服务器集群成功,那么就可以进行Zookeeper的常规操作了:

1) 是否已经连接

  public boolean isConnected() {

    return keeper != null && keeper.getState() == ZooKeeper.States.CONNECTED;

  }

2)  是否存在某个路径的Znode

  public Stat exists(final String path, final Watcher watcher, boolean retryOnConnLoss) throws KeeperException, InterruptedException {

    if (retryOnConnLoss) {

      return zkCmdExecutor.retryOperation(new ZkOperation() {

        @Override

        public Stat execute() throws KeeperException, InterruptedException {

          return keeper.exists(path, watcher);

        }

      });

    } else {

      return keeper.exists(path, watcher);

    }

  }

3) 创建一个Znode节点

  public String create(final String path, final byte data[], final List<ACL> acl, final CreateMode createMode, boolean retryOnConnLoss) throws KeeperException, InterruptedException {

    if (retryOnConnLoss) {

      return zkCmdExecutor.retryOperation(new ZkOperation() {

        @Override

        public String execute() throws KeeperException, InterruptedException {

          return keeper.create(path, data, acl, createMode);

        }

      });

    } else {

      return keeper.create(path, data, acl, createMode);

    }

  }

4)  获取指定路径下的孩子Znode节点

  public List<String> getChildren(final String path, final Watcher watcher, boolean retryOnConnLoss) throws KeeperException, InterruptedException {

    if (retryOnConnLoss) {

      return zkCmdExecutor.retryOperation(new ZkOperation() {

        @Override

        public List<String> execute() throws KeeperException, InterruptedException {

          return keeper.getChildren(path, watcher);

        }

      });

    } else {

      return keeper.getChildren(path, watcher);

    }

  }

5) 获取指定Znode上附加的数据

  public byte[] getData(final String path, final Watcher watcher, final Stat stat, boolean retryOnConnLoss) throws KeeperException, InterruptedException {

    if (retryOnConnLoss) {

      return zkCmdExecutor.retryOperation(new ZkOperation() {

        @Override

        public byte[] execute() throws KeeperException, InterruptedException {

          return keeper.getData(path, watcher, stat);

        }

      });

    } else {

      return keeper.getData(path, watcher, stat);

    }

  }

6)  在指定Znode上设置数据

  public Stat setData(final String path, final byte data[], final int version, boolean retryOnConnLoss) throws KeeperException, InterruptedException {

    if (retryOnConnLoss) {

      return zkCmdExecutor.retryOperation(new ZkOperation() {

        @Override

        public Stat execute() throws KeeperException, InterruptedException {

          return keeper.setData(path, data, version);

        }

      });

    } else {

      return keeper.setData(path, data, version);

    }

  }

7) 创建路径

  public void makePath(String path, byte[] data, CreateMode createMode, Watcher watcher, boolean failOnExists, boolean retryOnConnLoss) throws KeeperException, InterruptedException {

    if (log.isInfoEnabled()) {

      log.info("makePath: " + path);

    }

    boolean retry = true;

    

    if (path.startsWith("/")) {

      path = path.substring(1, path.length());

    }

    String[] paths = path.split("/");

    StringBuilder sbPath = new StringBuilder();

    for (int i = 0; i < paths.length; i++) {

      byte[] bytes = null;

      String pathPiece = paths[i];

      sbPath.append("/" + pathPiece);

      final String currentPath = sbPath.toString();

      Object exists = exists(currentPath, watcher, retryOnConnLoss);

      if (exists == null || ((i == paths.length -1) && failOnExists)) {

        CreateMode mode = CreateMode.PERSISTENT;

        if (i == paths.length - 1) {

          mode = createMode;

          bytes = data;

          if (!retryOnConnLoss) retry = false;

        }

        try {

          if (retry) {

            final CreateMode finalMode = mode;

            final byte[] finalBytes = bytes;

            zkCmdExecutor.retryOperation(new ZkOperation() {

              @Override

              public Object execute() throws KeeperException, InterruptedException {

                keeper.create(currentPath, finalBytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, finalMode);

                return null;

              }

            });

          } else {

            keeper.create(currentPath, bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, mode);

          }

        } catch (NodeExistsException e) {

          

          if (!failOnExists) {

            // TODO: version ? for now, don't worry about race

            setData(currentPath, data, -1, retryOnConnLoss);

            // set new watch

            exists(currentPath, watcher, retryOnConnLoss);

            return;

          }

          

          // ignore unless it's the last node in the path

          if (i == paths.length - 1) {

            throw e;

          }

        }

        if(i == paths.length -1) {

          // set new watch

          exists(currentPath, watcher, retryOnConnLoss);

        }

      } else if (i == paths.length - 1) {

        // TODO: version ? for now, don't worry about race

        setData(currentPath, data, -1, retryOnConnLoss);

        // set new watch

        exists(currentPath, watcher, retryOnConnLoss);

      }

    }

  }

8) 删除指定Znode

  public void delete(final String path, final int version, boolean retryOnConnLoss) throws InterruptedException, KeeperException {

    if (retryOnConnLoss) {

      zkCmdExecutor.retryOperation(new ZkOperation() {

        @Override

        public Stat execute() throws KeeperException, InterruptedException {

          keeper.delete(path, version);

          return null;

        }

      });

    } else {

      keeper.delete(path, version);

    }

  }

我们再回过头来看看ConnectionManager类是如何响应两端的连接状态信息的变化的,它最重要的方法是process方法,当它被触发回调时,会从WatchedEvent参数中得到事件的各种状态信息,比如连接成功,会话过期(此时需要进行重连),连接断开等。

  public synchronized void process(WatchedEvent event) {

    if (log.isInfoEnabled()) {

      log.info("Watcher " + this + " name:" + name + " got event " + event + " path:" + event.getPath() + " type:" + event.getType());

    }



    state = event.getState();

    if (state == KeeperState.SyncConnected) {

      connected = true;

      clientConnected.countDown();

    } else if (state == KeeperState.Expired) {

      connected = false;

      log.info("Attempting to reconnect to recover relationship with ZooKeeper...");

      //尝试重新连接zk服务器

      try {

        connectionStrategy.reconnect(zkServerAddress, zkClientTimeout, this,

            new ZkClientConnectionStrategy.ZkUpdate() {

              @Override

              public void update(SolrZooKeeper keeper) throws InterruptedException, TimeoutException, IOException {

                synchronized (connectionStrategy) {

                  waitForConnected(SolrZkClient.DEFAULT_CLIENT_CONNECT_TIMEOUT);

                  client.updateKeeper(keeper);

                  if (onReconnect != null) {

                    onReconnect.command();

                  }

                  synchronized (ConnectionManager.this) {

                    ConnectionManager.this.connected = true;

                  }

                }

                

              }

            });

      } catch (Exception e) {

        SolrException.log(log, "", e);

      }

      log.info("Connected:" + connected);

    } else if (state == KeeperState.Disconnected) {

      connected = false;

    } else {

      connected = false;

    }

    notifyAll();

  }

作者:洞庭散人

出处:http://phinecos.cnblogs.com/    

本博客遵从Creative Commons Attribution 3.0 License,若用于非商业目的,您可以自由转载,但请保留原作者信息和文章链接URL。

深入剖析SolrCloud(三)的更多相关文章

  1. Tomcat剖析(三):连接器(2)

    Tomcat剖析(三):连接器(2) 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器(1) 4 ...

  2. Tomcat剖析(三):连接器(1)

    Tomcat剖析(三):连接器(1) 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器(1) 4 ...

  3. x264代码剖析(三):主函数main()、解析函数parse()与编码函数encode()

    x264代码剖析(三):主函数main().解析函数parse()与编码函数encode() x264的入口函数为main().main()函数首先调用parse()解析输入的參数,然后调用encod ...

  4. 深入剖析SolrCloud(一)

    作者:洞庭散人 出处:http://phinecos.cnblogs.com/ 本博客遵从Creative Commons Attribution 3.0 License,若用于非商业目的,您可以自由 ...

  5. 深入剖析SolrCloud(二)

    作者:洞庭散人 出处:http://phinecos.cnblogs.com/ 本博客遵从Creative Commons Attribution 3.0 License,若用于非商业目的,您可以自由 ...

  6. SpringMVC源码剖析(三)- DispatcherServlet的初始化流程

    在我们第一次学Servlet编程,学Java Web的时候,还没有那么多框架.我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转 ...

  7. C++ 性能剖析 (三):Heap Object对比 Stack (auto) Object

    通常认为,性能的改进是90 ~ 10 规则, 即10%的代码要对90%的性能问题负责.做过大型软件工程的程序员一般都知道这个概念. 然而对于软件工程师来说,有些性能问题是不可原谅的,无论它们属于10% ...

  8. python进程池剖析(三)

    之前文章对python中进程池的原理.数据流以及应用从代码角度做了简单的剖析,现在让我们回头看看标准库中对进程池的实现都有哪些值得我们学习的地方.我们知道,进程池内部由多个线程互相协作,向客户端提供可 ...

  9. Java反射机制剖析(三)-简单谈谈动态代理

    通过Java反射机制剖析(一)和Java反射机制剖析(二)的学习,已经对反射有了一定的了解,这一篇通过动态代理的例子来进一步学习反射机制. 1.     代理模式 代理模式就是为其他对象提供一种代理来 ...

随机推荐

  1. Skynet服务器框架(八) 任务和消息调度机制

    引言: 在我看来,消息和任务调度应该是skynet的核心,整个skynet框架的核心其实就是一个消息管理系统.在skynet中可以把每个功能都当做一个服务,整个skynet工程在执行过程中会创建很多个 ...

  2. VSCode打开文件总是会覆盖上次打开的标签

    在使用VSCode的时候,打开一个文件之后,如果没有修改的话,那么再打开下一个文件的时候,他总会替换上次打开的标签,那么怎么样才能每次都在新的标签打开文件呢? 实际上,这种情况的出现是因为我们点击文件 ...

  3. AS3里面的错误代码

    ActionScript 3 出现2048安全策略服务,一种原因是因为843端口下发策略文件有问题,另一种原因是Socket端口有问题,可以用telnet来测试. 其它AS3错误代码的意义可以在官网文 ...

  4. (二)Nginx反向代理与负载均衡的实现

    引言:nginx正向代理与反向代理在上一篇文章中已经谈论过,这里狗尾草主要告诉大家Nginx对前端的小伙伴来说在工作中如何简单的使用. 1.0什么是反向代理 当我们有一个服务器集群,并且服务器集群中的 ...

  5. volatile 续

    上次的问题在看了一篇博客后有了点理解了 博文地址为 http://www.cnblogs.com/dolphin0520/p/3920373.html 按照文章中写的,在并发编程中,我们通常会遇到以下 ...

  6. js对象原型链

    JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象.这个对象的所有属性和方法,都会被构造函数的所拥有. 这也就意味着,我们可以把所有对象实例需要共享的属性和方 ...

  7. 洛谷P2661 信息传递

    传送门 题目大意:每个人每一轮可以把消息传给另一个人,问几轮后某个人可以从人 听到自己的消息. 题解:tarjian缩点,求缩点后缩的点包含的最少的点个数. 代码: 正解 #include<io ...

  8. cookie跨站脚本漏洞解决方案

    近日项目碰到一个跨脚本注入的问题: 这安全测评工具也是厉害了,直接将脚本注入到cookie里头,以前没有碰到这样的情况. 之前写过一篇文章过滤跨脚本注入的问题.<浅谈XSS攻击原理与解决方法&g ...

  9. Maven打包同一个jar有不同的:版本+时间戳(解决思路)

    在我们的开发过程中,目前流行的版本控制工具maven,在项目开发阶段,大家都是通过发布SNAPSHOT快照版进行相互模块之间的依赖开发, 这个时候就会有一个问题,要是一天构建多次的快照版,会发现在项目 ...

  10. (转)Android 使用com.j256.ormlite

    在web开发中经常采用的hibernate,在android也提供了一个ormlite 导入所需jar包后 摘自:http://blog.csdn.net/cuiran/article/details ...