org.yosemite.jcsadra.impl.SimpleCassandraClientPool.java Source code

Java tutorial

Introduction

Here is the source code for org.yosemite.jcsadra.impl.SimpleCassandraClientPool.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.yosemite.jcsadra.impl;

import java.util.NoSuchElementException;

import org.apache.cassandra.service.Cassandra;
import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.pool.impl.GenericObjectPoolFactory;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;

import org.apache.log4j.Logger;

import org.yosemite.jcsadra.CassandraClient;
import org.yosemite.jcsadra.CassandraClientPool;

/**
 * A simple client pool, have some configuration item to make it can work under
 * different request. it based on org.apache.commons.pool.GenericObjectPool.
 * 
 * configuration item include:
 *    maxActive controls the maximum number of client that can be created 
 *    by the pool at a given time.  When non-positive, there is no
 *    limit to the number of client that can be managed by the pool at one time.
 *    When maxActive is reached, the pool is said to be exhausted. 
 *    The default setting for this parameter is 50, this is large number, but should 
 *    work for us, because cassandra was a server cluster, if we using round-robine DNS
 *    LB, and have 10 server in cluster, there was avg 5 connect to each server, that
 *    would be not a problem
 *    
 *    maxIdle controls the maximum number of objects that can sit idle in the pool 
 *    at any time.  When negative, there is no limit to the number of objects that may 
 *    be idle at one time. The default setting for this parameter is 5( 1/10 of maxActive, for same the 
 *    client resource).<br>
 *  
 *    exhaustedAction specifies the behavior of the getClinet() method when 
 *    the pool is exhausted:
 *      When exhaustedAction is WHEN_EXHAUSTED_FAIL getClient() will throw a 
 *      NoSuchElementException
 *      
 *      When exhaustedAction WHEN_EXHAUSTED_GROW getClinet will create a new
 *      object and return it (essentially making maxActive meaningless.)
 *    
 *      When exhaustedAction is WHEN_EXHAUSTED_BLOCK, getClient() will block
 *      until a new or idle object is available.
 *      If a positive maxWait value is supplied, then getClient will block for at
 *      most that many milliseconds, after which a NoSuchElementException
 *      will be thrown.  If maxWait is non-positive, the getClient() method will 
 *      block indefinitely.
 *    
 *    The default exhaustedAction setting is WHEN_EXHAUSTED_BLOCK} and the default 
 *    maxWaitWhenBlock setting is 60000. By default, therefore, getClinet() will
 *    block 1 minutes, then throw out a exception, if we can not get a connect in 1 
 *    minutes, there must be something wrong.
 *  
 *  
 *  state table:
 *  =========                    ===========                        =========                         ============
 *  |init(0)|  ->  getClient  -> |maxActive| ->  ReleaseClient   -> |maxIdle| ->  Wait Idle time   -> |minIdle(0)|
 *  =========                    ===========                        =========                         ============
 *                                   ^                                   |                                 | 
 *                                   |-------------------when new getClient request arrive-----------------|
 *  
 * @author dayong
 */
public class SimpleCassandraClientPool implements CassandraClientPool {

    Logger logger = Logger.getLogger(this.getClass());

    /**
     * default max active number is 50, because Cassandra have multiple servers
     * so 50 should be a good value.
     */
    public static final int DEFAULT_MAX_ACTIVE = 50;

    /**
     * default max idle number is 5, so when the client keep idle, the total connection 
     * number will release to 5 
     */
    public static final int DEFAULT_MAX_IDLE = 5;

    /**
     * default exhausted action is block for 1 minute, because in many time, cassandra was being 
     * used in a web backed, so too long time block will cause very bad user experience.
     * for some other non-synchronize application, this value can configure to a more long time.  
     */
    public static final ExhaustedAction DEFAULT_EXHAUSTED_ACTION = ExhaustedAction.WHEN_EXHAUSTED_BLOCK;

    /**
     * the default max wait time when exhausted happened, default value is 1 minute
     */
    public static final long DEFAULT_MAX_WAITTIME_WHEN_EXHAUSTED = 1 * 60 * 1000;

    /**
     * the object pool, all client instance managed by this pool
     */
    GenericObjectPoolFactory _poolfactory = null;
    GenericObjectPool _pool = null;
    PoolableClientFactory _clientfactory = null;

    public SimpleCassandraClientPool(String serviceURL, int port) {
        this(serviceURL, port, null, DEFAULT_MAX_ACTIVE, DEFAULT_EXHAUSTED_ACTION,
                DEFAULT_MAX_WAITTIME_WHEN_EXHAUSTED, DEFAULT_MAX_IDLE);
    }

    public void close() {
        try {
            _pool.close();
        } catch (Exception e) {
            logger.error("close client pool error", e);
        }
    }

    public int getAvailableNum() {
        return _pool.getNumIdle();
    }

    public CassandraClient getClient() throws Exception, NoSuchElementException, IllegalStateException {
        CassandraClient cc = (CassandraClient) _pool.borrowObject();
        return cc;
    }

    public int getActiveNum() {
        return _pool.getNumActive();
    }

    public void releaseClient(CassandraClient client) throws Exception {
        _pool.returnObject(client);
    }

    /**
     * inner class for create and destory Cassandra.Client 
     * @author dayong
     */
    public class PoolableClientFactory extends BasePoolableObjectFactory implements PoolableObjectFactory {

        @Override
        public void destroyObject(Object obj) throws Exception {
            CassandraClient client = (CassandraClient) obj;
            if (logger.isDebugEnabled())
                logger.debug("close client " + client.toString());

            closeClient(client);
        }

        @Override
        public Object makeObject() throws Exception {
            if (logger.isDebugEnabled())
                logger.debug("create cassandra client [" + serviceURL + ":" + port + "]");

            try {
                return createClient(serviceURL, port);
            } catch (TTransportException e) {
                logger.error("create client error:", e);
                throw e;
            } catch (TException e) {
                logger.error("create client error:", e);
                throw e;
            }
        }

        /**
         * do a simple cassandra request
         */
        @Override
        public boolean validateObject(Object obj) {
            return validateClient((CassandraClient) obj);
        }

    }

    public int getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
        _pool.setMaxActive(maxActive);
    }

    public int getMaxIdle() {
        return maxIdle;
    }

    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
        _pool.setMaxIdle(maxIdle);
    }

    public long getMaxWaitWhenBlockByExhausted() {
        return maxWaitWhenBlockByExhausted;
    }

    public void setMaxWaitWhenBlockByExhausted(long maxWaitWhenBlockByExhausted) {
        this.maxWaitWhenBlockByExhausted = maxWaitWhenBlockByExhausted;
        _pool.setMaxWait(maxWaitWhenBlockByExhausted);
    }

    /**
     * serviceURL is read only, can not change after pool created
     * @return
     */
    public String getServiceURL() {
        return serviceURL;
    }

    /**
     * port number was read only, can not change after pool created
     * @return
     */
    public int getPort() {
        return port;
    }

    // ************************************ protected method ***********************************

    /**
     * this construct method wamaxWaitWhenBlockExhausteds for unit test, for regulary usage, should not given the client factory 
     */
    protected SimpleCassandraClientPool(String serviceURL, int port, PoolableObjectFactory clientfactory,
            int maxActive, ExhaustedAction exhaustedAction, long maxWait, int maxIdle) {
        this.serviceURL = serviceURL;
        this.port = port;
        this.maxActive = maxActive;
        this.exhaustedAction = exhaustedAction;
        this.maxWaitWhenBlockByExhausted = maxWait;
        this.maxIdle = maxIdle;

        // if not give  a client factory, will create the default PoolableClientFactory
        if (clientfactory == null) {
            this._clientfactory = new PoolableClientFactory();
            _poolfactory = new GenericObjectPoolFactory(_clientfactory, maxActive,
                    getObjectPoolExhaustedAction(exhaustedAction), maxWait, maxIdle);
        } else {
            _poolfactory = new GenericObjectPoolFactory(clientfactory, maxActive,
                    getObjectPoolExhaustedAction(exhaustedAction), maxWait, maxIdle);
        }

        _pool = (GenericObjectPool) _poolfactory.createPool();
    }

    public static CassandraClient createClient(String serviceURL, int port) throws TTransportException, TException {

        TTransport tr = new TSocket(serviceURL, port);
        TProtocol proto = new TBinaryProtocol(tr);
        Cassandra.Client client = new Cassandra.Client(proto);
        tr.open();

        CassandraClientImpl cclient = new CassandraClientImpl(client);
        cclient.init();
        return cclient;

    }

    public static void closeClient(CassandraClient cclient) {
        Cassandra.Client client = cclient.getCassandra();

        client.getInputProtocol().getTransport().close();
        client.getOutputProtocol().getTransport().close();
    }

    public static boolean validateClient(CassandraClient client) {
        //TODO send simple reqesut to cassandra, this request 
        // should very quickly and light
        return true;
    }

    public static byte getObjectPoolExhaustedAction(ExhaustedAction exhaustedAction) {
        switch (exhaustedAction) {
        case WHEN_EXHAUSTED_FAIL:
            return GenericObjectPool.WHEN_EXHAUSTED_FAIL;
        case WHEN_EXHAUSTED_BLOCK:
            return GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
        case WHEN_EXHAUSTED_GROW:
            return GenericObjectPool.WHEN_EXHAUSTED_GROW;
        default:
            return GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
        }
    }

    // **********************************  private method *********************************

    private int maxActive;

    private int maxIdle;

    private ExhaustedAction exhaustedAction;

    private long maxWaitWhenBlockByExhausted;

    // the Cassandra service url, 
    private String serviceURL = "localhost";

    // port
    private int port = 9096;

}