/* * 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.AbstractClusterService; import com.navercorp.pinpoint.collector.cluster.ClusterPointRouter; import com.navercorp.pinpoint.common.server.util.concurrent.CommonState; import com.navercorp.pinpoint.common.server.util.concurrent.CommonStateContext; import com.navercorp.pinpoint.collector.cluster.connection.*; import com.navercorp.pinpoint.collector.config.CollectorConfiguration; import com.navercorp.pinpoint.collector.util.CollectorUtils; import com.navercorp.pinpoint.rpc.server.handler.ServerStateChangeEventHandler; import org.apache.commons.lang3.StringUtils; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.proto.WatcherEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.atomic.AtomicBoolean; /** * @author koo.taejin */ public class ZookeeperClusterService extends AbstractClusterService { static final long DEFAULT_RECONNECT_DELAY_WHEN_SESSION_EXPIRED = 30000; private static final String PINPOINT_CLUSTER_PATH = "/pinpoint-cluster"; private static final String PINPOINT_WEB_CLUSTER_PATH = PINPOINT_CLUSTER_PATH + "/web"; private static final String PINPOINT_PROFILER_CLUSTER_PATH = PINPOINT_CLUSTER_PATH + "/profiler"; private final Logger logger = LoggerFactory.getLogger(this.getClass()); // represented as pid@hostname (identifiers may overlap for services hosted on localhost if pids are identical) // shouldn't be too big of a problem, but will change to MAC or IP if it becomes problematic. private final String serverIdentifier = CollectorUtils.getServerIdentifier(); private final CommonStateContext serviceState; private CollectorClusterConnectionManager clusterConnectionManager; private ZookeeperClient client; // WebClusterManager checks Zookeeper for the Web data, and manages collector -> web connections. private ZookeeperClusterManager webClusterManager; // ProfilerClusterManager detects/manages profiler -> collector connections, and saves their information in Zookeeper. private ZookeeperProfilerClusterManager profilerClusterManager; public ZookeeperClusterService(CollectorConfiguration config, ClusterPointRouter clusterPointRouter) { super(config, clusterPointRouter); this.serviceState = new CommonStateContext(); if (config.isClusterEnable()) { CollectorClusterConnectionRepository clusterRepository = new CollectorClusterConnectionRepository(); CollectorClusterConnectionFactory clusterConnectionFactory = new CollectorClusterConnectionFactory(serverIdentifier, clusterPointRouter, clusterPointRouter); CollectorClusterConnector clusterConnector = clusterConnectionFactory.createConnector(); CollectorClusterAcceptor clusterAcceptor = null; if (StringUtils.isNotEmpty(config.getClusterListenIp()) && config.getClusterListenPort() > 0) { InetSocketAddress bindAddress = new InetSocketAddress(config.getClusterListenIp(), config.getClusterListenPort()); clusterAcceptor = clusterConnectionFactory.createAcceptor(bindAddress, clusterRepository); } this.clusterConnectionManager = new CollectorClusterConnectionManager(serverIdentifier, clusterRepository, clusterConnector, clusterAcceptor); } } @PostConstruct @Override public void setUp() throws KeeperException, IOException, InterruptedException { if (!config.isClusterEnable()) { logger.info("pinpoint-collector cluster disable."); return; } switch (this.serviceState.getCurrentState()) { case NEW: if (this.serviceState.changeStateInitializing()) { logger.info("{} initialization started.", this.getClass().getSimpleName()); ClusterManagerWatcher watcher = new ClusterManagerWatcher(); this.client = new DefaultZookeeperClient(config.getClusterAddress(), config.getClusterSessionTimeout(), watcher); this.client.connect(); this.profilerClusterManager = new ZookeeperProfilerClusterManager(client, serverIdentifier, clusterPointRouter.getTargetClusterPointRepository()); this.profilerClusterManager.start(); this.webClusterManager = new ZookeeperClusterManager(client, PINPOINT_WEB_CLUSTER_PATH, clusterConnectionManager); this.webClusterManager.start(); this.serviceState.changeStateStarted(); logger.info("{} initialization completed.", this.getClass().getSimpleName()); if (client.isConnected()) { WatcherEvent watcherEvent = new WatcherEvent(EventType.None.getIntValue(), KeeperState.SyncConnected.getIntValue(), ""); WatchedEvent event = new WatchedEvent(watcherEvent); watcher.process(event); } } break; case INITIALIZING: logger.info("{} already initializing.", this.getClass().getSimpleName()); break; case STARTED: logger.info("{} already started.", this.getClass().getSimpleName()); break; case DESTROYING: throw new IllegalStateException("Already destroying."); case STOPPED: throw new IllegalStateException("Already stopped."); case ILLEGAL_STATE: throw new IllegalStateException("Invalid State."); } } @PreDestroy @Override public void tearDown() { if (!config.isClusterEnable()) { logger.info("pinpoint-collector cluster disable."); return; } if (!(this.serviceState.changeStateDestroying())) { CommonState state = this.serviceState.getCurrentState(); logger.info("{} already {}.", this.getClass().getSimpleName(), state.toString()); return; } logger.info("{} destroying started.", this.getClass().getSimpleName()); if (this.profilerClusterManager != null) { profilerClusterManager.stop(); } if (this.webClusterManager != null) { webClusterManager.stop(); } if (client != null) { client.close(); } if (clusterConnectionManager != null) { clusterConnectionManager.stop(); } this.serviceState.changeStateStopped(); logger.info("{} destroying completed.", this.getClass().getSimpleName()); } @Override public boolean isEnable() { return config.isClusterEnable(); } public ServerStateChangeEventHandler getChannelStateChangeEventHandler() { return profilerClusterManager; } public ZookeeperProfilerClusterManager getProfilerClusterManager() { return profilerClusterManager; } public ZookeeperClusterManager getWebClusterManager() { return webClusterManager; } class ClusterManagerWatcher implements ZookeeperEventWatcher { private final AtomicBoolean connected = new AtomicBoolean(false); @Override public void process(WatchedEvent event) { logger.debug("Process Zookeeper Event({})", event); KeeperState state = event.getState(); EventType eventType = event.getType(); // ephemeral node is removed on disconnect event (leave node management exclusively to zookeeper) if (ZookeeperUtils.isDisconnectedEvent(state, eventType)) { connected.compareAndSet(true, false); if (state == KeeperState.Expired) { if (client != null) { client.reconnectWhenSessionExpired(); } } return; } // on connect/reconnect event if (ZookeeperUtils.isConnectedEvent(state, eventType)) { // could already be connected (failure to compareAndSet doesn't really matter) boolean changed = connected.compareAndSet(false, true); } if (serviceState.isStarted() && connected.get()) { // duplicate event possible - but the logic does not change if (ZookeeperUtils.isConnectedEvent(state, eventType)) { profilerClusterManager.initZookeeperClusterData(); webClusterManager.handleAndRegisterWatcher(PINPOINT_WEB_CLUSTER_PATH); } else if (eventType == EventType.NodeChildrenChanged) { String path = event.getPath(); if (PINPOINT_WEB_CLUSTER_PATH.equals(path)) { webClusterManager.handleAndRegisterWatcher(path); } else { logger.warn("Unknown Path ChildrenChanged {}.", path); } } } } @Override public boolean isConnected() { return connected.get(); } } }