net.spy.memcached.CacheManager.java Source code

Java tutorial

Introduction

Here is the source code for net.spy.memcached.CacheManager.java

Source

/*
 * arcus-java-client : Arcus Java client
 * Copyright 2010-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 net.spy.memcached;

/**
 * A program to use CacheMonitor to start and
 * stop memcached node based on a znode. The program watches the
 * specified znode and saves the znode that corresponds to the
 * memcached server in the remote machine. It also changes the 
 * previous ketama node
 */
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import net.spy.memcached.ArcusClientException.InitializeClientException;
import net.spy.memcached.compat.SpyThread;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;

public class CacheManager extends SpyThread implements Watcher, CacheMonitor.CacheMonitorListener {
    private static final String ARCUS_BASE_CACHE_LIST_ZPATH = "/arcus/cache_list/";

    private static final String ARCUS_BASE_CLIENT_INFO_ZPATH = "/arcus/client_list/";

    private static final int ZK_SESSION_TIMEOUT = 15000;

    private static final long ZK_CONNECT_TIMEOUT = ZK_SESSION_TIMEOUT;

    private final String hostPort;

    private final String serviceCode;

    private CacheMonitor cacheMonitor;

    private ZooKeeper zk;

    private ArcusClient[] client;

    private final CountDownLatch clientInitLatch;

    private final ConnectionFactoryBuilder cfb;

    private final int waitTimeForConnect;

    private final int poolSize;

    private volatile boolean shutdownRequested = false;

    private CountDownLatch zkInitLatch;

    private List<String> prevChildren;

    /**
     * The locator class of the spymemcached has an assumption
     * that it should have one cache node at least. 
     * Thus, we add a fake server node in it
     * if there's no cache servers for the given service code.
     * This is just a work-around, but it works really. 
     */
    public static final String FAKE_SERVER_NODE = "0.0.0.0:23456";

    public CacheManager(String hostPort, String serviceCode, ConnectionFactoryBuilder cfb,
            CountDownLatch clientInitLatch, int poolSize, int waitTimeForConnect) {

        this.hostPort = hostPort;
        this.serviceCode = serviceCode;
        this.cfb = cfb;
        this.clientInitLatch = clientInitLatch;
        this.poolSize = poolSize;
        this.waitTimeForConnect = waitTimeForConnect;

        initZooKeeperClient();

        setName("Cache Manager IO for " + serviceCode + "@" + hostPort);
        setDaemon(true);
        start();

        getLogger().info("CacheManager started. (" + serviceCode + "@" + hostPort + ")");

    }

    private void initZooKeeperClient() {
        try {
            getLogger().info("Trying to connect to Arcus admin(%s@%s)", serviceCode, hostPort);

            zkInitLatch = new CountDownLatch(1);
            zk = new ZooKeeper(hostPort, ZK_SESSION_TIMEOUT, this);

            try {
                /* In the above ZooKeeper() internals, reverse DNS lookup occurs
                 * when the getHostName() of InetSocketAddress class is called.
                 * In Windows, the reverse DNS lookup includes NetBIOS lookup
                 * that bring delay of 5 seconds (as well as dns and host file lookup).
                 * So, ZK_CONNECT_TIMEOUT is set as much like ZK session timeout.
                 */
                if (zkInitLatch.await(ZK_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) == false) {
                    getLogger().fatal("Connecting to Arcus admin(%s) timed out : %d miliseconds", hostPort,
                            ZK_CONNECT_TIMEOUT);
                    throw new AdminConnectTimeoutException(hostPort);
                }

                if (zk.exists(ARCUS_BASE_CACHE_LIST_ZPATH + serviceCode, false) == null) {
                    getLogger().fatal("Service code not found. (" + serviceCode + ")");
                    throw new NotExistsServiceCodeException(serviceCode);
                }

                String path = getClientInfo();
                if (path.isEmpty()) {
                    getLogger().fatal("Can't create the znode of client info (" + path + ")");
                } else {
                    if (zk.exists(path, false) == null) {
                        zk.create(path, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                    }
                }
            } catch (AdminConnectTimeoutException e) {
                shutdownZooKeeperClient();
                throw e;
            } catch (NotExistsServiceCodeException e) {
                shutdownZooKeeperClient();
                throw e;
            } catch (InterruptedException ie) {
                getLogger().fatal("Can't connect to Arcus admin(%s@%s) %s", serviceCode, hostPort, ie.getMessage());
                shutdownZooKeeperClient();
                return;
            } catch (Exception e) {
                getLogger().fatal("Unexpected exception. contact to Arcus administrator", e);

                shutdownZooKeeperClient();
                throw new InitializeClientException("Can't initialize Arcus client.", e);
            }

            cacheMonitor = new CacheMonitor(zk, ARCUS_BASE_CACHE_LIST_ZPATH, serviceCode, this);
        } catch (IOException e) {
            throw new InitializeClientException("Can't initialize Arcus client.", e);
        }
    }

    private String getClientInfo() {
        String path = "";

        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
            Date currentTime = new Date();

            // create the ephemeral znode 
            // "/arcus/client_list/{service_code}/{client hostname}_{ip address}_{pool size}_java_{client version}_{YYYYMMDDHHIISS}_{zk session id}"
            path = ARCUS_BASE_CLIENT_INFO_ZPATH + serviceCode + "/";
            path = path + InetAddress.getLocalHost().getHostName() + "_"
                    + InetAddress.getLocalHost().getHostAddress() + "_" + this.poolSize + "_java_"
                    + ArcusClient.VERSION + "_" + simpleDateFormat.format(currentTime) + "_" + zk.getSessionId();

        } catch (Exception e) {
            getLogger().fatal("Can't get client info.", e);
            return "";
        }
        return path;
    }

    /***************************************************************************
     * We do process only child node change event ourselves, we just need to
     * forward them on.
     * 
     */
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.None) {
            switch (event.getState()) {
            case SyncConnected:
                getLogger().info("Connected to Arcus admin. (%s@%s)", serviceCode, hostPort);
                zkInitLatch.countDown();
            }
        }

        if (cacheMonitor != null) {
            cacheMonitor.process(event);
        } else {
            getLogger().debug("cm is null, servicecode : %s, state:%s, type:%s", serviceCode, event.getState(),
                    event.getType());
        }
    }

    public void run() {
        try {
            synchronized (this) {
                while (!shutdownRequested) {
                    if (zk == null) {
                        getLogger().info("Arcus admin connection is not established. (%s@%s)", serviceCode,
                                hostPort);
                        initZooKeeperClient();
                    }

                    if (!cacheMonitor.dead) {
                        wait();
                    } else {
                        getLogger().warn(
                                "Unexpected disconnection from Arcus admin. Trying to reconnect to Arcus admin.");
                        try {
                            shutdownZooKeeperClient();
                            initZooKeeperClient();
                        } catch (AdminConnectTimeoutException e) {
                            Thread.sleep(5000L);
                        } catch (NotExistsServiceCodeException e) {
                            Thread.sleep(5000L);
                        } catch (InitializeClientException e) {
                            Thread.sleep(5000L);
                        }
                    }
                }
            }
        } catch (InterruptedException e) {
            getLogger().warn("current arcus admin is interrupted : %s", e.getMessage());
        } finally {
            shutdownZooKeeperClient();
        }
    }

    public void closing() {
        synchronized (this) {
            notifyAll();
        }
    }

    /**
     * If there's no children in the znode, make a fake server node.
     * 
     * Change current MemcachedNodes to new MemcachedNodes but intersection of
     * current and new will be ruled out.
     * 
     * @param children
     *            new children node list
     */
    public void commandNodeChange(List<String> children) {
        // If there's no children, add a fake server node to the list.
        if (children.size() == 0) {
            getLogger().error("Cannot find any cache nodes for your service code. "
                    + "Please contact Arcus support to solve this problem. " + "[serviceCode=" + serviceCode
                    + ", addminSessionId=0x" + Long.toHexString(zk.getSessionId()));
            children.add(CacheManager.FAKE_SERVER_NODE);
        }

        if (!children.equals(prevChildren)) {
            getLogger().warn("Cache list has been changed : From=" + prevChildren + ", To=" + children + ", "
                    + "[serviceCode=" + serviceCode + ", addminSessionId=0x" + Long.toHexString(zk.getSessionId()));
        }

        // Store the current children.
        prevChildren = children;

        String addrs = "";
        for (int i = 0; i < children.size(); i++) {
            String[] temp = children.get(i).split("-");
            if (i != 0) {
                addrs = addrs + "," + temp[0];
            } else {
                addrs = temp[0];
            }
        }

        if (client == null) {
            createArcusClient(addrs);
            return;
        }

        for (ArcusClient ac : client) {
            MemcachedConnection conn = ac.getMemcachedConnection();
            conn.putMemcachedQueue(addrs);
            conn.getSelector().wakeup();
        }
    }

    public List<String> getPrevChildren() {
        return this.prevChildren;
    }

    /**
     * Create a ArcusClient
     * 
     * @param addrs
     *            current available Memcached Addresses
     */
    private void createArcusClient(String addrs) {

        List<InetSocketAddress> socketList = AddrUtil.getAddresses(addrs);
        int addrCount = socketList.size();

        final CountDownLatch latch = new CountDownLatch(addrCount);
        final ConnectionObserver observer = new ConnectionObserver() {

            @Override
            public void connectionLost(SocketAddress sa) {

            }

            @Override
            public void connectionEstablished(SocketAddress sa, int reconnectCount) {
                latch.countDown();
            }
        };

        cfb.setInitialObservers(Collections.singleton(observer));

        int _awaitTime = 0;
        if (waitTimeForConnect == 0)
            _awaitTime = 50 * addrCount;
        else
            _awaitTime = waitTimeForConnect;

        client = new ArcusClient[poolSize];
        for (int i = 0; i < poolSize; i++) {
            try {
                client[i] = ArcusClient.getInstance(cfb.build(), socketList);
                client[i].setName("Memcached IO for " + serviceCode);
                client[i].setCacheManager(this);
            } catch (IOException e) {
                getLogger().fatal("Arcus Connection has critical problems. contact arcus manager.");
            }
        }
        try {
            if (latch.await(_awaitTime, TimeUnit.MILLISECONDS)) {
                getLogger().warn("All arcus connections are established.");
            } else {
                getLogger().error("Some arcus connections are not established.");
            }
            // Success signal for initial connections to Zookeeper and
            // Memcached.
        } catch (InterruptedException e) {
            getLogger().fatal("Arcus Connection has critical problems. contact arcus manager.");
        }
        this.clientInitLatch.countDown();

    }

    /**
     * Returns current ArcusClient
     * 
     * @return current ArcusClient
     */
    public ArcusClient[] getAC() {
        return client;
    }

    private void shutdownZooKeeperClient() {
        if (zk == null) {
            return;
        }

        try {
            getLogger().info("Close the ZooKeeper client. serviceCode=" + serviceCode + ", adminSessionId=0x"
                    + Long.toHexString(zk.getSessionId()));
            zk.close();
            zk = null;
        } catch (InterruptedException e) {
            getLogger().warn("An exception occured while closing ZooKeeper client.", e);
        }
    }

    public void shutdown() {
        if (!shutdownRequested) {
            getLogger().info("Shut down cache manager.");
            shutdownRequested = true;
            closing();
        }
    }
}