作者:洞庭散人

出处: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. Android 自定义组件之如何实现自定义组件

    参考链接:http://blog.csdn.net/jjwwmlp456/article/details/41076699 简介 Android提供了用于构建UI的强大的组件模型.两个基类:View和 ...

  2. [置顶] Android逆向从未如此简单

    哈,又标题党了..不过我一定竭尽所能,写一篇最亲民的入门文章. 本文仅供学习交流之用,切勿用于非法用途,读者若运用所学知识,进行非法任何商业目的或者非法牟利,一切责任由操作者自行承担,与本人无关.希望 ...

  3. Leetcode 1013. Partition Array Into Three Parts With Equal Sum

    简单题,暴力找出来就行. class Solution: def canThreePartsEqualSum(self, A: List[int]) -> bool: s = sum(A) if ...

  4. Supervisor进程监控

    安装 yum install -y python-setuptools easy_install supervisor echo_supervisord_conf > /etc/supervis ...

  5. [转载] FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...

  6. 关于public static void main(String[] args)相关知识

    main()方法是Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方法有很大的不同.比如方法的名字必须是main,方法必须是public ...

  7. HDU - 5829:Rikka with Subset (NTT)

    As we know, Rikka is poor at math. Yuta is worrying about this situation, so he gives Rikka some mat ...

  8. linux之epoll

    1. epoll简介 epoll 是Linux内核中的一种可扩展IO事件处理机制,最早在 Linux 2.5.44内核中引入,可被用于代替POSIX select 和 poll 系统调用,并且在具有大 ...

  9. 重温CLR(七 ) 属性和事件

    无参属性 许多类型都定义了能被获取或更高的状态信息.这种状态信息一般作为类型的字段成员实现.例如一下类型包含两个字段: public sealed class Employee{ public str ...

  10. 异常:org.springframework.http.converter.HttpMessageNotReadableException

    spring(springboot.springmvc)出现标题的异常一般是由于controller的入参失败引起的. 介绍下常规入参的两种格式: ,这种方式的入参主要是接受key-value的参数, ...