All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fnklabs.draenei.CassandraClient Maven / Gradle / Ivy

package com.fnklabs.draenei;

import com.codahale.metrics.Timer;
import com.datastax.driver.core.*;
import com.datastax.driver.core.policies.*;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;

public class CassandraClient {
    private static final int RECONNECTION_DELAY_TIME = 5000;
    /**
     * Read timeout in ms
     */
    private static final int READ_TIMEOUT = 15000;
    /**
     * Connection timeout in ms
     */
    private static final int CONNECT_TIMEOUT_MILLIS = 30000;

    private static final Logger LOGGER = LoggerFactory.getLogger(CassandraClient.class);

    /**
     * Prepared statements map that allow solve problem with several prepared statements execution is same query
     */
    private final ConcurrentHashMap preparedStatementsMap = new ConcurrentHashMap();

    /**
     * Cassandra session instance
     */
    private final Session session;

    private final MetricsFactory metricsFactory;

    /**
     * Construct cassandra client
     *
     * @param username       Username
     * @param password       Password
     * @param keyspace       Default keyspace
     * @param hosts          Cassandra nodes
     * @param metricsFactory Metrics Factory that will be used for metrics
     * @param hostDistance   Cassandra host distance
     *
     * @throws IllegalArgumentException if can't connect to cluster
     */
    public CassandraClient(@NotNull String username,
                           @NotNull String password,
                           @NotNull String keyspace,
                           @NotNull String hosts,
                           @NotNull MetricsFactory metricsFactory,
                           @NotNull HostDistance hostDistance) {

        this.metricsFactory = metricsFactory;

        Cluster.Builder builder = Cluster.builder()
                                         .withPort(9042)
                                         .withProtocolVersion(ProtocolVersion.NEWEST_SUPPORTED)
                                         .withQueryOptions(getQueryOptions())
                                         .withRetryPolicy(new LoggingRetryPolicy(FallthroughRetryPolicy.INSTANCE))
                                         .withLoadBalancingPolicy(getLoadBalancingPolicy(hostDistance))
                                         .withReconnectionPolicy(new ConstantReconnectionPolicy(RECONNECTION_DELAY_TIME))
                                         .withSocketOptions(getSocketOptions());

        if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
            builder = builder.withCredentials(username, password);
        }

        String[] hostList = StringUtils.split(hosts, ",");

        LOGGER.info("Cassandra nodes: {}", hosts);

        for (String host : hostList) {
            builder.addContactPoint(host);
        }

        try {
            Cluster cluster = builder.build();

            Metadata metadata = cluster.getMetadata();

            LOGGER.info(String.format("Connecting to cluster: %s", metadata.getClusterName()));

            for (Host host : metadata.getAllHosts()) {
                LOGGER.info(String.format("DataCenter: %s; Host: %s; Rack: %s", host.getDatacenter(), host.getAddress(), host.getRack()));
            }

            cluster.init();

            session = createSession(cluster, keyspace);
        } catch (IllegalArgumentException e) {
            LOGGER.warn("Cant build cluster", e);
            throw e;
        }
    }

    public CassandraClient(@NotNull Session session, @NotNull MetricsFactory metricsFactory) {
        this.session = session;
        this.metricsFactory = metricsFactory;
    }

    @NotNull
    public KeyspaceMetadata getKeyspaceMetadata(@NotNull String keyspace) {
        return getSession().getCluster().getMetadata().getKeyspace(keyspace);
    }

    @NotNull
    public TableMetadata getTableMetadata(@NotNull String tablename) {
        return getKeyspaceMetadata(getSession().getLoggedKeyspace()).getTable(tablename);
    }

    @NotNull
    public TableMetadata getTableMetadata(@NotNull String keyspace, @NotNull String tablename) {
        return getKeyspaceMetadata(keyspace).getTable(tablename);
    }

    @NotNull
    public PreparedStatement prepare(@NotNull String query) {
        return preparedStatementsMap.compute(query, new ComputePreparedStatement());
    }

    /**
     * Execute CQL query
     *
     * @param query CQL query
     *
     * @return Result set
     */
    public ResultSet execute(@NotNull String query) {
        getMetricsFactory().getCounter(MetricsType.CASSANDRA_QUERIES_COUNT).inc();
        Timer.Context time = getMetricsFactory().getTimer(MetricsType.CASSANDRA_EXECUTE).time();

        ResultSet resultSet = getSession().execute(query);
        time.stop();

        return resultSet;
    }

    /**
     * Execute statement
     *
     * @param statement Statement
     *
     * @return Execution result set
     */
    public ResultSet execute(@NotNull Statement statement) {
        Timer.Context time = getMetricsFactory().getTimer(MetricsType.CASSANDRA_EXECUTE).time();

        getMetricsFactory().getCounter(MetricsType.CASSANDRA_QUERIES_COUNT).inc();

        ResultSet resultSetFuture = getSession().execute(statement);

        time.stop();

        return resultSetFuture;
    }

    /**
     * Execute cql query asynchronously
     *
     * @param query CQL query
     *
     * @return ResultSetFuture
     */
    public ResultSetFuture executeAsync(@NotNull String query) {
        Timer.Context time = getMetricsFactory().getTimer(MetricsType.CASSANDRA_EXECUTE_ASYNC).time();

        getMetricsFactory().getCounter(MetricsType.CASSANDRA_PROCESSING_QUERIES).inc();

        ResultSetFuture resultSetFuture = getSession().executeAsync(query);

        Futures.addCallback(resultSetFuture, new StatementExecutionCallback(query));

        monitorFuture(time, resultSetFuture);

        return resultSetFuture;
    }


    /**
     * Execute statement asynchronously
     *
     * @param statement Statement that must be executed
     *
     * @return ResultSetFuture
     */
    @NotNull
    public ResultSetFuture executeAsync(@NotNull Statement statement) {
        Timer.Context time = getMetricsFactory().getTimer(MetricsType.CASSANDRA_EXECUTE_ASYNC).time();

        getMetricsFactory().getCounter(MetricsType.CASSANDRA_PROCESSING_QUERIES).inc();

        ResultSetFuture resultSetFuture = getSession().executeAsync(statement);

        String query = (statement instanceof BoundStatement) ? ((BoundStatement) statement).preparedStatement().getQueryString() : statement.toString();

        Futures.addCallback(resultSetFuture, new StatementExecutionCallback(query));
        monitorFuture(time, resultSetFuture);

        return resultSetFuture;
    }

    /**
     * Initiate close cluster and session operations
     */
    public void close() {
        session.close();
        session.getCluster().close();
    }

    /**
     * Get Cluster session
     *
     * @return Session instance
     */
    @NotNull
    public Session getSession() {
        return session;
    }

    public Set getMembers() {
        return getSession().getCluster().getMetadata().getAllHosts();
    }

    protected  void monitorFuture(@NotNull Timer.Context timer, @NotNull ListenableFuture future) {
        Futures.addCallback(future, new FutureCallback() {
            @Override
            public void onSuccess(T result) {
                timer.stop();
            }

            @Override
            public void onFailure(Throwable t) {
                timer.stop();
                LOGGER.warn("Cant complete operation", t);
            }
        });
    }

    @NotNull
    protected SocketOptions getSocketOptions() {
        SocketOptions socketOptions = new SocketOptions();
        socketOptions.setConnectTimeoutMillis(CONNECT_TIMEOUT_MILLIS);
        socketOptions.setReadTimeoutMillis(READ_TIMEOUT);
        socketOptions.setKeepAlive(true);
        socketOptions.setTcpNoDelay(true);
        return socketOptions;
    }

    @NotNull
    protected QueryOptions getQueryOptions() {
        QueryOptions queryOptions = new QueryOptions();
        queryOptions.setConsistencyLevel(ConsistencyLevel.QUORUM);
        return queryOptions;
    }

    private MetricsFactory getMetricsFactory() {
        return metricsFactory;
    }

    private enum MetricsType implements MetricsFactory.Type {
        CASSANDRA_EXECUTOR_QUEUE_SIZE,
        CASSANDRA_EXECUTE,
        CASSANDRA_QUERIES_COUNT,
        CASSANDRA_QUERIES_ERRORS,
        CASSANDRA_PROCESSING_QUERIES,
        CASSANDRA_EXECUTE_ASYNC, CASSANDRA_PREPARE_STMT,
    }

    @NotNull
    protected static LoadBalancingPolicy getLoadBalancingPolicy(@NotNull final HostDistance hostDistance) {
        LatencyAwarePolicy.Builder latencyPolicyBuilder = LatencyAwarePolicy.builder(new RoundRobinPolicy() {
            @Override
            public HostDistance distance(Host host) {
                return hostDistance;
            }
        });

        LatencyAwarePolicy latencyAwarePolicy = latencyPolicyBuilder.build();

        return new TokenAwarePolicy(latencyAwarePolicy);
    }

    /**
     * Create session
     *
     * @param cluster  Cluster instance
     * @param keyspace Default keyspace
     *
     * @return Session instance
     */
    private static Session createSession(@NotNull Cluster cluster, @NotNull String keyspace) {
        Session session = cluster.connect(keyspace);
        session.init();

        return session;
    }

    private class ComputePreparedStatement implements BiFunction {

        @Override
        public PreparedStatement apply(String s, PreparedStatement preparedStatement) {
            if (preparedStatement == null) {
                Timer.Context timer = getMetricsFactory().getTimer(MetricsType.CASSANDRA_PREPARE_STMT).time();
                preparedStatement = getSession().prepare(s);
                timer.stop();
            }

            return preparedStatement;
        }
    }

    private class StatementExecutionCallback implements FutureCallback {
        private final String query;

        public StatementExecutionCallback(String query) {
            this.query = query;
        }

        @Override
        public void onSuccess(ResultSet result) {
            getMetricsFactory().getCounter(MetricsType.CASSANDRA_PROCESSING_QUERIES).dec();
            getMetricsFactory().getCounter(MetricsType.CASSANDRA_QUERIES_COUNT).inc();
        }

        @Override
        public void onFailure(Throwable t) {
            LOGGER.warn("Cant execute bound statement: " + query, t);

            getMetricsFactory().getCounter(MetricsType.CASSANDRA_QUERIES_COUNT).inc();
            getMetricsFactory().getCounter(MetricsType.CASSANDRA_QUERIES_ERRORS).inc();
            getMetricsFactory().getCounter(MetricsType.CASSANDRA_PROCESSING_QUERIES).dec();
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy