com.palantir.atlasdb.keyvalue.cassandra.CassandraJMXCompaction.java Source code

Java tutorial

Introduction

Here is the source code for com.palantir.atlasdb.keyvalue.cassandra.CassandraJMXCompaction.java

Source

/**
 * Copyright 2015 Palantir Technologies
 *
 * Licensed under the BSD-3 License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://opensource.org/licenses/BSD-3-Clause
 *
 * 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.palantir.atlasdb.keyvalue.cassandra;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.apache.cassandra.db.HintedHandOffManagerMBean;
import org.apache.cassandra.db.compaction.CompactionManagerMBean;
import org.apache.cassandra.service.StorageServiceMBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.palantir.common.base.Throwables;

/**
 * Maintains a JMX connection for a C* node
 */
public class CassandraJMXCompaction {
    private static final Logger log = LoggerFactory.getLogger(CassandraJMXCompaction.class);
    private static final int RETRY_TIMES = 3;
    private static final long RETRY_INTERVAL_IN_SECONDS = 5;

    private final String host;
    private final int port;
    private final String username;
    private final String password;

    private JMXConnector jmxc;
    private CompactionManagerMBean compactionProxy;
    private StorageServiceMBean ssProxy;
    private HintedHandOffManagerMBean hintedHandoffProxy;

    private CassandraJMXCompaction(Builder builder) {
        this.host = builder.host;
        this.port = builder.port;
        this.username = builder.username;
        this.password = builder.password;
    }

    public static class Builder {
        private String host;
        private int port;
        private String username = "";
        private String password = "";

        public Builder(String host, int port) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(host));
            Preconditions.checkArgument(port > 0, "invalid port:[%s], must be greater than zero", port);
            this.host = host;
            this.port = port;
        }

        public Builder username(String val) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(val), "username:[%s] should not be empty or null",
                    val);
            this.username = val;
            return this;
        }

        public Builder password(String val) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(val), "password:[%s] should not be empty or null",
                    val);
            this.password = val;
            return this;
        }

        public CassandraJMXCompaction build() {
            CassandraJMXCompaction compaction = new CassandraJMXCompaction(this);
            boolean status = compaction.connect();
            int retries = 0;
            while (status == false && retries < RETRY_TIMES) {
                retries++;
                log.info("Failed to connect to JMX, retrying in {} seconds for {} time(s)",
                        RETRY_INTERVAL_IN_SECONDS, retries);
                try {
                    TimeUnit.SECONDS.sleep(RETRY_INTERVAL_IN_SECONDS);
                } catch (InterruptedException e) {
                    log.error("Sleep interupted while trying to connect to JMX server", e);
                }
                status = compaction.connect();
            }
            if (status == false) {
                log.error("Failed to connect to JMX after {} retries on node {}!", RETRY_TIMES, host);
                return null;
            }
            return compaction;
        }
    }

    /**
     * Create a connection to the JMX agent and setup the M[X]Bean proxies.
     */
    private boolean connect() {
        Map<String, Object> env = new HashMap<String, Object>();
        String[] creds = { username, password };
        env.put(JMXConnector.CREDENTIALS, creds);

        JMXServiceURL jmxUrl = null;
        MBeanServerConnection mbeanServerConn = null;
        try {
            jmxUrl = new JMXServiceURL(String.format(CassandraConstants.JMX_RMI, host, port));
            jmxc = JMXConnectorFactory.connect(jmxUrl, env);
            mbeanServerConn = jmxc.getMBeanServerConnection();

            ObjectName name = new ObjectName(CassandraConstants.STORAGE_SERVICE_OBJECT_NAME);
            ssProxy = JMX.newMBeanProxy(mbeanServerConn, name, StorageServiceMBean.class);

            name = new ObjectName(CassandraConstants.COMPACTION_MANAGER_OBJECT_NAME);
            compactionProxy = JMX.newMBeanProxy(mbeanServerConn, name, CompactionManagerMBean.class);

            name = new ObjectName(CassandraConstants.HINTED_HANDOFF_MANAGER_OBJECT_NAME);
            hintedHandoffProxy = JMX.newMBeanProxy(mbeanServerConn, name, HintedHandOffManagerMBean.class);
        } catch (MalformedURLException e) {
            log.error("Wrong JMX service URL", e);
            return false;
        } catch (IOException e) {
            log.error("JMXConnectorFactory cannot connect to the JMX service URL.", e);
            return false;
        } catch (MalformedObjectNameException e) {
            log.error("JMX ObjectName has wrong fomat.", e);
            return false;
        }
        return true;
    }

    public void close() {
        try {
            if (jmxc != null) {
                jmxc.close();
            }
        } catch (IOException e) {
            log.error("Error in closing Cassandra JMX compaction connection.", e);
        }
    }

    public void deleteLocalHints() {
        // hintedHandoff needs to be deleted to make sure data will not reappear later
        hintedHandoffProxy.deleteHintsForEndpoint(host);
        log.trace("hintedhandoff on host:{} was deleted for tombstone compaction!", host);
    }

    public void forceTableFlush(String keyspace, String tableName) {
        log.trace("flushing {}.{} on host:{} for tombstone compaction!", keyspace, tableName, host);
        try {
            ssProxy.forceKeyspaceFlush(keyspace, tableName);
        } catch (IOException e) {
            log.error("forceTableFlush IOException: {}", e.getMessage());
            Throwables.throwUncheckedException(e);
        } catch (ExecutionException e) {
            log.error("forceTableFlush ExecutionException: {}", e.getMessage());
            Throwables.throwUncheckedException(e);
        } catch (InterruptedException e) {
            log.error("forceTableFlush InterruptedException: {}", e.getMessage());
            Throwables.throwUncheckedException(e);
        }
    }

    /**
     * retry for a number of times. The reason why we need to retry compaction is:
     * during a node's coming back process, the keyspace is not ready to be compacted
     * even the JMX connection can be established.
     * @param keyspace
     * @param tableName
     */
    public boolean forceTableCompaction(String keyspace, String tableName) {
        boolean status = performTableCompaction(keyspace, tableName);
        int retries = 0;
        while (status == false && retries < RETRY_TIMES) {
            retries++;
            log.info("Failed to compact, retrying in {} seconds for {} time(s) on node {}",
                    RETRY_INTERVAL_IN_SECONDS, retries, host);
            try {
                TimeUnit.SECONDS.sleep(RETRY_INTERVAL_IN_SECONDS);
            } catch (InterruptedException e) {
                log.error("Sleep interupted while trying to compact", e);
            }
            status = performTableCompaction(keyspace, tableName);
        }
        if (status == false) {
            log.error("Failed to compact after {} retries on node {}!", RETRY_INTERVAL_IN_SECONDS, host);
        }
        return status;
    }

    private boolean performTableCompaction(String keyspace, String tableName) {
        try {
            Stopwatch timer = Stopwatch.createStarted();
            ssProxy.forceKeyspaceCompaction(keyspace, tableName);
            log.info("Compaction for {}:{} completed on node {} in {}", keyspace, Arrays.asList(tableName), host,
                    timer.stop());
        } catch (IOException e) {
            log.error("Invalid keyspace or tableNames specified for forceTableCompaction()", e);
            return false;
        } catch (ExecutionException e) {
            log.error("ExecutionException in forceTableCompaction()", e);
            return false;
        } catch (InterruptedException e) {
            log.error("InterruptedException in forceTableCompaction()", e);
            return false;
        }
        return true;
    }

    public int getPendingTasks() {
        return compactionProxy.getPendingTasks();
    }

    public List<String> getLiveNodes() {
        return ssProxy.getLiveNodes();
    }

    public String getHost() {
        return host;
    }

    /* auto generated by eclipse */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((host == null) ? 0 : host.hashCode());
        result = prime * result + port;
        return result;
    }

    /* auto generated by eclipse */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        CassandraJMXCompaction other = (CassandraJMXCompaction) obj;
        if (host == null) {
            if (other.host != null)
                return false;
        } else if (!host.equals(other.host))
            return false;
        if (port != other.port)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "CassandraJMXCompaction [host=" + host + ", port=" + port + "]";
    }
}