/* * Copyright 2014 NAVER Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.navercorp.pinpoint.collector.cluster.zookeeper; import com.navercorp.pinpoint.collector.cluster.zookeeper.exception.AuthException; import com.navercorp.pinpoint.collector.cluster.zookeeper.exception.BadOperationException; import com.navercorp.pinpoint.collector.cluster.zookeeper.exception.ConnectionException; import com.navercorp.pinpoint.collector.cluster.zookeeper.exception.NoNodeException; import com.navercorp.pinpoint.collector.cluster.zookeeper.exception.PinpointZookeeperException; import com.navercorp.pinpoint.collector.cluster.zookeeper.exception.TimeoutException; import com.navercorp.pinpoint.collector.cluster.zookeeper.exception.UnknownException; import com.navercorp.pinpoint.common.server.util.concurrent.CommonStateContext; import com.navercorp.pinpoint.rpc.util.TimerFactory; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; import org.jboss.netty.util.HashedWheelTimer; import org.jboss.netty.util.Timeout; import org.jboss.netty.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; /** * * <strong>Conditional thread safe</strong> <br> * If multiple threads invokes connect, reconnect, or close concurrently; then it is possible for the object's zookeeper field to be out of sync. * * @author koo.taejin */ public class DefaultZookeeperClient implements ZookeeperClient { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final CommonStateContext stateContext; private final HashedWheelTimer timer; private final String hostPort; private final int sessionTimeout; private final ZookeeperEventWatcher watcher; private final long reconnectDelayWhenSessionExpired; // Zookeeper clients are thread-safe private volatile ZooKeeper zookeeper; public DefaultZookeeperClient(String hostPort, int sessionTimeout, ZookeeperEventWatcher watcher) { this(hostPort, sessionTimeout, watcher, ZookeeperClusterService.DEFAULT_RECONNECT_DELAY_WHEN_SESSION_EXPIRED); } public DefaultZookeeperClient(String hostPort, int sessionTimeout, ZookeeperEventWatcher watcher, long reconnectDelayWhenSessionExpired) { this.hostPort = hostPort; this.sessionTimeout = sessionTimeout; this.watcher = watcher; this.reconnectDelayWhenSessionExpired = reconnectDelayWhenSessionExpired; this.timer = TimerFactory.createHashedWheelTimer(this.getClass().getSimpleName(), 100, TimeUnit.MILLISECONDS, 512); this.stateContext = new CommonStateContext(); } @Override public void connect() throws IOException { if (stateContext.changeStateInitializing()) { this.zookeeper = new ZooKeeper(hostPort, sessionTimeout, watcher); // server stateContext.changeStateStarted(); } else { logger.warn("connect() failed. error : Illegal State. State may be {}.", stateContext.getCurrentState()); } } @Override public void reconnectWhenSessionExpired() { if (!stateContext.isStarted()) { logger.warn("ZookeeperClient.reconnectWhenSessionExpired() failed. Error: Already closed."); return; } ZooKeeper zookeeper = this.zookeeper; if (zookeeper.getState().isConnected()) { logger.warn("ZookeeperClient.reconnect() failed. Error: session(0x{}) is connected.", Long.toHexString(zookeeper.getSessionId())); return; } logger.warn("Execute ZookeeperClient.reconnectWhenSessionExpired()(Expired session:0x{}).", Long.toHexString(zookeeper.getSessionId())); try { zookeeper.close(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } ZooKeeper newZookeeper = createNewZookeeper(); if (!stateContext.isStarted()) { logger.warn("ZookeeperClient.reconnectWhenSessionExpired() failed. Error: Already closed."); return; } if (newZookeeper == null) { logger.warn("Failed to create new Zookeeper instance. It will be retry AFTER {}ms.", reconnectDelayWhenSessionExpired); timer.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { if (timeout.isCancelled()) { return; } reconnectWhenSessionExpired(); } }, reconnectDelayWhenSessionExpired, TimeUnit.MILLISECONDS); } else { this.zookeeper = newZookeeper; } } private ZooKeeper createNewZookeeper() { try { return new ZooKeeper(hostPort, sessionTimeout, watcher); } catch (IOException ignore) { // ignore } return null; } /** * do not create the final node in the given path. * * @throws PinpointZookeeperException * @throws InterruptedException */ @Override public void createPath(String path) throws PinpointZookeeperException, InterruptedException { createPath(path, false); } @Override public void createPath(String path, boolean createEndNode) throws PinpointZookeeperException, InterruptedException { checkState(); int pos = 1; do { pos = path.indexOf('/', pos + 1); if (pos == -1) { pos = path.length(); } ZooKeeper zookeeper = this.zookeeper; try { if (pos == path.length()) { if (!createEndNode) { return; } } String subPath = path.substring(0, pos); if (zookeeper.exists(subPath, false) != null) { continue; } zookeeper.create(subPath, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } catch (KeeperException exception) { if (exception.code() != Code.NODEEXISTS) { handleException(exception); } } } while (pos < path.length()); } @Override public String createNode(String zNodePath, byte[] data) throws PinpointZookeeperException, InterruptedException { checkState(); ZooKeeper zookeeper = this.zookeeper; try { if (zookeeper.exists(zNodePath, false) != null) { return zNodePath; } String pathName = zookeeper.create(zNodePath, data, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); return pathName; } catch (KeeperException exception) { handleException(exception); } return zNodePath; } @Override public byte[] getData(String path) throws PinpointZookeeperException, InterruptedException { checkState(); ZooKeeper zookeeper = this.zookeeper; try { return zookeeper.getData(path, false, null); } catch (KeeperException exception) { handleException(exception); } throw new UnknownException("UnknownException."); } @Override public void setData(String path, byte[] data) throws PinpointZookeeperException, InterruptedException { checkState(); ZooKeeper zookeeper = this.zookeeper; try { if (zookeeper.exists(path, false) == null) { return; } zookeeper.setData(path, data, -1); } catch (KeeperException exception) { handleException(exception); } } @Override public void delete(String path) throws PinpointZookeeperException, InterruptedException { checkState(); ZooKeeper zookeeper = this.zookeeper; try { zookeeper.delete(path, -1); } catch (KeeperException exception) { if (exception.code() != Code.NONODE) { handleException(exception); } } } @Override public boolean exists(String path) throws PinpointZookeeperException, InterruptedException { checkState(); ZooKeeper zookeeper = this.zookeeper; try { Stat stat = zookeeper.exists(path, false); if (stat == null) { return false; } } catch (KeeperException exception) { if (exception.code() != Code.NODEEXISTS) { handleException(exception); } } return true; } private void checkState() throws PinpointZookeeperException { if (!isConnected()) { throw new ConnectionException("Instance must be connected."); } } @Override public boolean isConnected() { if (!watcher.isConnected() || !stateContext.isStarted()) { return false; } return true; } @Override public List<String> getChildrenNode(String path, boolean watch) throws PinpointZookeeperException, InterruptedException { checkState(); try { List<String> childNodeList = zookeeper.getChildren(path, watch, null); logger.info("ChildNode List = {}", childNodeList); return childNodeList; } catch (KeeperException exception) { if (exception.code() != Code.NONODE) { handleException(exception); } } return Collections.emptyList(); } private void handleException(KeeperException keeperException) throws PinpointZookeeperException { switch (keeperException.code()) { case CONNECTIONLOSS: case SESSIONEXPIRED: throw new ConnectionException(keeperException.getMessage(), keeperException); case AUTHFAILED: case INVALIDACL: case NOAUTH: throw new AuthException(keeperException.getMessage(), keeperException); case BADARGUMENTS: case BADVERSION: case NOCHILDRENFOREPHEMERALS: case NOTEMPTY: case NODEEXISTS: throw new BadOperationException(keeperException.getMessage(), keeperException); case NONODE: throw new NoNodeException(keeperException.getMessage(), keeperException); case OPERATIONTIMEOUT: throw new TimeoutException(keeperException.getMessage(), keeperException); default: throw new UnknownException(keeperException.getMessage(), keeperException); } } @Override public void close() { if (stateContext.changeStateDestroying()) { ZooKeeper zookeeper = this.zookeeper; if (timer != null) { timer.stop(); } if (zookeeper != null) { try { zookeeper.close(); } catch (InterruptedException ignore) { logger.info("Interrupted zookeeper.close(). Caused:" + ignore.getMessage(), ignore); Thread.currentThread().interrupt(); } } stateContext.changeStateStopped(); } } }