com.sequoiadb.base.SequoiadbDatasource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sequoiadb-driver Show documentation
Show all versions of sequoiadb-driver Show documentation
Java client driver for SequoiaDB
/**
* Copyright (C) 2012 SequoiaDB Inc.
*
* 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.sequoiadb.base;
* @brief SequoiaDB DataSource
* @author tanzhaobo
*/
package com.sequoiadb.base;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import com.sequoiadb.exception.BaseException;
import com.sequoiadb.net.ConfigOptions;
/**
* @class SequoiadbDatasource
* @brief SequoiaDB DataSource
*/
public class SequoiadbDatasource
{
// the idle queue
private volatile LinkedList idle_sequoiadbs = new LinkedList();
// the busy queue
private volatile LinkedList used_sequoiadbs = new LinkedList();
// the normal coord addresses
private volatile ArrayList normal_urls = new ArrayList();
// the abnormal coord addresses
private volatile ArrayList abnormal_urls = new ArrayList();
private String username = null;
private String password = null;
// the configuration for network
private ConfigOptions nwOpt = null;
// the configuration for datasource
private SequoiadbOption dsOpt = null;
// a timer for clean idle connection
private Timer timer = new Timer(true);
// a timer for get back the useful coord address
private Timer timer2 = new Timer(true);
private final double MULTIPLE = 1.2;
private final double MULTIPLE2 = 0.8;
private Random rand = new Random(47);
private boolean isClosed = false;
/**
* @fn int getIdleConnNum()
* @brief Get the current idle connection amount.
*/
public synchronized int getIdleConnNum()
{
return idle_sequoiadbs.size();
}
/**
* @fn int getUsedConnNum()
* @brief Get the current used connection amount.
*/
public synchronized int getUsedConnNum()
{
return used_sequoiadbs.size();
}
/**
* @fn int getNormalAddrNum()
* @brief Get the current normal address amount.
*/
public synchronized int getNormalAddrNum()
{
return normal_urls.size();
}
/**
* @fn int getAbnormalAddrNum()
* @brief Get the current abnormal address amount.
*/
public synchronized int getAbnormalAddrNum()
{
return abnormal_urls.size();
}
public synchronized void addCoord(String url)
{
normal_urls.add(url);
}
/**
* @fn SequoiadbDatasource(ArrayList urls, String username, String password,
* ConfigOptions nwOpt, SequoiadbOption dsOpt)
* @brief constructor.
* @param urls the addresses of coords, can't be null or empty,
* e.g."ubuntu1:11810","ubuntu2:11810",...
* @param username the username for logging sequoiadb
* @param password the password for logging sequoiadb
* @param nwOpt the options for connection
* @param dsOpt the options for datasource
* @note When offer several addresses for datasource to use, if
* some of them are not available(invalid address, network error, coord shutdown,
* catalog replica group is not available), we will put these addresses
* into a queue, and check them periodically. If some of them is valid again,
* get them back for use. When datasource get a unavailable address to connect,
* the default timeout time is 100ms, and default retry time is 0, use nwOpt can change them.
* @see ConfigOptions
* @see SequoiadbOption
* @exception com.sequoiadb.exception.BaseException
*/
public SequoiadbDatasource(List urls, String username, String password,
ConfigOptions nwOpt, SequoiadbOption dsOpt) throws BaseException
{
if (null == urls || 0 == urls.size())
throw new BaseException("SDB_INVALIDARG", urls);
ConfigOptions temp = new ConfigOptions();
temp.setConnectTimeout(100);
temp.setMaxAutoConnectRetryTime(0);
this.username = (null == username) ? "" : username;
this.password = (null == password) ? "" : password;
this.nwOpt = (null == nwOpt) ? temp : nwOpt;
this.dsOpt = (null == dsOpt) ? new SequoiadbOption() : dsOpt;
this.normal_urls.addAll(urls);
try
{
init(this.dsOpt);
}
catch(BaseException e)
{
throw e;
}
// after the timer start for a while, it goes to clean periodically
timer.schedule(new CleanConnectionTask(this), this.dsOpt.getRecheckCyclePeriod(),
this.dsOpt.getRecheckCyclePeriod());
// after the timer start 10 minutes, it goes to get back the useful coord address periodically
timer2.schedule(new RecaptureCoordAddrTask(this), this.dsOpt.getRecaptureConnPeriod(),
this.dsOpt.getRecaptureConnPeriod());
Runtime.getRuntime().addShutdownHook(new SDExitThread(this));
}
/**
* @fn SequoiadbDatasource(String url, String username, String password,
* SequoiadbOption dsOpt)
* @brief Constructor.
* @param url the address of coord, can't be null, e.g."ubuntu1:11810"
* @param username the username of sequoiadb
* @param password the password of sequoiadb
* @param dsOpt the option of datasource
* @exception com.sequoiadb.exception.BaseException
*/
public SequoiadbDatasource(String url, String username, String password,
SequoiadbOption dsOpt) throws BaseException
{
if (null == url)
throw new BaseException("SDB_INVALIDARG", url);
this.normal_urls.add(url);
ConfigOptions temp = new ConfigOptions();
temp.setConnectTimeout(100);
temp.setMaxAutoConnectRetryTime(0);
this.username = (null == username) ? "" : username;
this.password = (null == password) ? "" : password;
this.nwOpt = (null == nwOpt) ? temp : nwOpt;
this.dsOpt = (null == dsOpt) ? new SequoiadbOption() : dsOpt;
try
{
init(this.dsOpt);
}
catch(BaseException e)
{
throw e;
}
// after the timer start 500ms, it goes to clean periodically
timer.schedule(new CleanConnectionTask(this), dsOpt.getRecheckCyclePeriod(), dsOpt.getRecheckCyclePeriod());
Runtime.getRuntime().addShutdownHook(new SDExitThread(this));
}
/**
* @fn void init(SequoiadbOption dsOpt)
* @brief Initialize datasource.
* @param dsOpt the configuration for datasource
* @exception com.sequoiadb.Exception.BaseException
*/
private void init(SequoiadbOption dsOpt) throws BaseException
{
// check option
if (dsOpt.getMaxConnectionNum() < 0)
throw new BaseException("SDB_INVALIDARG",
"maxConnectionNum is negative: " + dsOpt.getMaxConnectionNum());
// if no need to init, return directly
if (dsOpt.getMaxConnectionNum() == 0)
return ;
// check option
if (dsOpt.getMaxConnectionNum() < dsOpt.getInitConnectionNum())
throw new BaseException("SDB_INVALIDARG", "maxConnectionNum is less than initConnectionNum, maxConnectionNum is " +
dsOpt.getMaxConnectionNum() + ", initConnectionNum is " + dsOpt.getInitConnectionNum());
if (dsOpt.getInitConnectionNum() < 0)
throw new BaseException("SDB_INVALIDARG",
"initConnectionNum is negative: " + dsOpt.getInitConnectionNum());
// increase some connection
increaseConn(dsOpt.getInitConnectionNum());
}
/**
* @fn String getCoordAddr()
* @brief Get coord url.
* @return one of the coord addresses
* @exception com.sequoiadb.Exception.BaseException
*/
private synchronized String getCoordAddr() throws BaseException
{
if (1 < normal_urls.size())
return normal_urls.get(rand.nextInt(normal_urls.size()));
else if (1 == normal_urls.size())
return normal_urls.get(0);
else
throw new BaseException("SDB_INVALIDARG", "no available address");
}
/**
* @fn String getBackCoordAddr()
* @brief Get back all the available coord addresses periodically.
* @exception com.sequoiadb.Exception.BaseException
*/
synchronized void getBackCoordAddr()
{
Sequoiadb sdb = null;
String addr = null;
if (0 == abnormal_urls.size())
return;
for (int i = 0; i < abnormal_urls.size(); i++ )
{
addr = abnormal_urls.get(i);
try
{
sdb = new Sequoiadb(addr, this.username, this.password, this.nwOpt);
if (sdb.isValid()) // it seem no need to check here?
{
normal_urls.add(addr);
abnormal_urls.remove(addr);
i--;
}
}
catch(BaseException e)
{
}
}
}
/**
* @fn Sequoiadb getConnDrt()
* @brief Get a connection directly.
* @exception com.sequoiadb.Exception.BaseException
*/
private synchronized Sequoiadb getConnDrt() throws BaseException
{
Sequoiadb sdb = null;
String addr = getCoordAddr();
while (normal_urls.size() > 0)
{
try
{
sdb = new Sequoiadb(addr, this.username, this.password, this.nwOpt);
break;
}
catch (BaseException e)
{
String errType = e.getErrorType();
if (errType.equals("SDB_NETWORK") || errType.equals("SDB_INVALIDARG") ||
errType.equals("SDB_NET_CANNOT_CONNECT"))
{
abnormal_urls.add(addr);
normal_urls.remove(addr);
addr = getCoordAddr();
continue;
}
else
throw e;
}
}
// check whether we succeed to get connection or not
if (null == sdb)
throw new BaseException("SDB_INVALIDARG");
return sdb;
}
/**
* @fn Sequoiadb getConnection()
* @brief Get the connection from current datasource.
* @return Sequoiadb the connection for use
* @exception com.sequoiadb.Exception.BaseException
* when datasource run out, throws BaseException with the type of "SDB_DRIVER_DS_RUNOUT"
* @exception InterruptedException
*/
public synchronized Sequoiadb getConnection() throws BaseException, InterruptedException
{
// when we don't want to use datasource, return sequiadb instance directly
if (dsOpt.getMaxConnectionNum() == 0)
return getConnDrt();
// otherwise
Sequoiadb sdb = null;
if ((idle_sequoiadbs.size() > 0) && (used_sequoiadbs.size() < dsOpt.getMaxConnectionNum()))
{
// get connection from idle queue if no connection, it return null
sdb = idle_sequoiadbs.poll();
// get a valid instance for return
while((null != sdb) && (!sdb.isValid()))
{
sdb = idle_sequoiadbs.poll();
}
// if no valid instance in idle queue, let't create one for return
if (null == sdb)
sdb = getConnDrt();
// add to busy queue
used_sequoiadbs.add(sdb);
}
else
{
// if we run out all the connection, we need to wait for a moment
if (used_sequoiadbs.size() >= dsOpt.getMaxConnectionNum())
{
wait(dsOpt.getTimeout());
// when wake up, check again
if (used_sequoiadbs.size() >= dsOpt.getMaxConnectionNum())
throw new BaseException("SDB_DRIVER_DS_RUNOUT");
}
sdb = getConnDrt();
used_sequoiadbs.add(sdb);
// create a thread to increase connection
Thread t = new Thread(new CreateConnectionTask(this));
t.start();
}
return sdb;
}
/**
* @fn void close(Sequoiadb sdb)
* @brief Put the connection back to the datasource.
* In the case of the connecton is not belong to current datasource or
* the connecton is outdate(by default, if a connection has not be used for 10 minutes),
* this API will disconnect directly.
* @param sdb the connection get from current datasource, can't be null
* @exception com.sequoiadb.Exception.BaseException
* if param sdb is null, throws BaseException with the type "SDB_INVALIDARG"
*/
public synchronized void close(Sequoiadb sdb) throws BaseException
{
if (null == sdb)
throw new BaseException("SDB_INVALIDARG", sdb);
// check option
if (dsOpt.getAbandonTime() <= 0)
throw new BaseException("SDB_INVALIDARG", "abandonTime is negative: " + dsOpt.getAbandonTime());
if (dsOpt.getRecheckCyclePeriod() <= 0)
throw new BaseException("SDB_INVALIDARG", "recheckCyclePeriod is negative: " + dsOpt.getRecheckCyclePeriod());
if (dsOpt.getRecheckCyclePeriod() >= dsOpt.getAbandonTime())
throw new BaseException("SDB_INVALIDARG", "recheckCyclePeriod is not less than abandonTime, recheckCyclePeriod is " +
dsOpt.getRecheckCyclePeriod() + ", abandonTime is " + dsOpt.getAbandonTime());
// if the busy queue contain this instance
if (used_sequoiadbs.contains(sdb))
{
// remove it from busy queue
used_sequoiadbs.remove(sdb);
// when datasource not be used, abandon the connection directly
if (dsOpt.getMaxConnectionNum() == 0)
{
sdb.disconnect();
return;
}
// judge put it in idle queue or not
long lastTime = sdb.getConnection().getLastUseTime();
long currentTime = System.currentTimeMillis();
// check the time keep in instance, don't let it go back to pool
// when it timeout
if (currentTime - lastTime + MULTIPLE * dsOpt.getRecheckCyclePeriod() >= dsOpt.getAbandonTime())
{
sdb.disconnect();
return;
}
else
{
// close all cursor
sdb.closeAllCursors();
// release the heap memory or other resource holds in the instance
sdb.releaseResource();
// put it back to idle queue
idle_sequoiadbs.add(sdb);
notify();
}
}
// else, abandon it directly
else
{
sdb.disconnect();
return;
}
}
/**
* @fn void increaseConnetions()
* @bref Add another 20 Sequoiadb objects to the datasource.
*/
synchronized void increaseConnetions() throws BaseException
{
if (isClosed) {
return;
}
// when datasource is not used, return directly
if (dsOpt.getMaxConnectionNum() == 0)
return;
// if the number of connection in the pool is less than SequoiadbOption::maxIdeNum
// we are going to increase
if (idle_sequoiadbs.size() >= dsOpt.getMaxIdeNum())
return;
if (dsOpt.getDeltaIncCount() < 0)
throw new BaseException("SDB_INVALIDARG",
"deltaIncCount is negative: " + dsOpt.getDeltaIncCount());
if (dsOpt.getMaxConnectionNum() < dsOpt.getDeltaIncCount())
throw new BaseException("SDB_INVALIDARG",
"deltaIncCount is greater than maxConnectionNum, deltaIncCount is " +
dsOpt.getDeltaIncCount() + ", maxConnectionNum is " + dsOpt.getMaxConnectionNum());
if (used_sequoiadbs.size() < dsOpt.getMaxConnectionNum() - dsOpt.getDeltaIncCount())
increaseConn(dsOpt.getDeltaIncCount());
else
increaseConn(dsOpt.getMaxConnectionNum() - used_sequoiadbs.size());
}
/**
* @fn void cleanAbandonConnection()
* @brief clean the tiemout connection periodically
*/
synchronized void cleanAbandonConnection() throws BaseException
{
if (isClosed) {
return;
}
// when no need to clean
if ((0 == idle_sequoiadbs.size()) && (0 == used_sequoiadbs.size()))
return ;
// check option
if (dsOpt.getAbandonTime() <= 0)
throw new BaseException("SDB_INVALIDARG", "abandonTime is negative: " + dsOpt.getAbandonTime());
if (dsOpt.getRecheckCyclePeriod() <= 0)
throw new BaseException("SDB_INVALIDARG", "recheckCyclePeriod is negative: " + dsOpt.getRecheckCyclePeriod());
if (dsOpt.getRecheckCyclePeriod() >= dsOpt.getAbandonTime())
throw new BaseException("SDB_INVALIDARG", "recheckCyclePeriod is not less than abandonTime, recheckCyclePeriod is " +
dsOpt.getRecheckCyclePeriod() + ", abandonTime is " + dsOpt.getAbandonTime());
long lastTime = 0;
long currentTime = System.currentTimeMillis();
// remove the timeout connection in idle connection queue
ListIterator list1 = idle_sequoiadbs.listIterator(0);
while(list1.hasNext())
{
Sequoiadb db = list1.next();
lastTime = db.getConnection().getLastUseTime();
if (currentTime - lastTime + MULTIPLE * dsOpt.getRecheckCyclePeriod() >= dsOpt.getAbandonTime())
{
db.disconnect () ;
list1.remove();
}
}
// remove the needless connection in idle connection queue
for (int i = 0; i < idle_sequoiadbs.size() - dsOpt.getMaxIdeNum(); i++)
{
Sequoiadb db = idle_sequoiadbs.poll();
db.disconnect();
i--;
}
}
/**
* @fn void increaseConn(int num)
* @brief Increase connections.
*/
private synchronized void increaseConn(int num)
{
if (num < 0)
throw new BaseException("SDB_INVALIDARG");
for (int i = 0; i < num; i++)
{
String coordAddr = getCoordAddr();
Sequoiadb sdb = null;
try
{
sdb = new Sequoiadb(coordAddr, username, password, this.nwOpt);
}
catch(BaseException e)
{
String errType = e.getErrorType();
if (errType.equals("SDB_NETWORK") || errType.equals("SDB_INVALIDARG") ||
errType.equals("SDB_NET_CANNOT_CONNECT"))
{
abnormal_urls.add(coordAddr);
normal_urls.remove(coordAddr);
i--;
continue;
}
else
throw e;
}
idle_sequoiadbs.add(sdb);
}
}
/**
* @fn void close()
* @brief clean all resources of this object
*/
public synchronized void close() {
timer.cancel();
timer2.cancel();
Iterator iterUsed = used_sequoiadbs.iterator();
while(iterUsed.hasNext()) {
Sequoiadb db = iterUsed.next();
db.disconnect();
iterUsed.remove();
}
Iterator iterIdle = idle_sequoiadbs.iterator();
while(iterIdle.hasNext()) {
Sequoiadb db = iterIdle.next();
db.disconnect();
iterIdle.remove();
}
isClosed=true;
}
protected void finalize() throws Throwable {
try {
close();
}
catch(Exception e) {
}
super.finalize();
}
}
class CleanConnectionTask extends TimerTask
{
private SequoiadbDatasource datasource;
public CleanConnectionTask(SequoiadbDatasource ds)
{
datasource = ds;
}
@Override
public void run()
{
datasource.cleanAbandonConnection();
}
}
class CreateConnectionTask implements Runnable
{
private SequoiadbDatasource datasource;
public CreateConnectionTask(SequoiadbDatasource ds)
{
datasource = ds;
}
//@Override
public void run()
{
datasource.increaseConnetions();
}
}
class RecaptureCoordAddrTask extends TimerTask
{
private SequoiadbDatasource datasource;
public RecaptureCoordAddrTask(SequoiadbDatasource ds){
datasource = ds;
}
@Override
public void run()
{
datasource.getBackCoordAddr();
}
}
class SDExitThread extends Thread {
private SequoiadbDatasource _sd = null ;
public SDExitThread( SequoiadbDatasource sd ) {
_sd = sd;
}
public void run() {
if (null != _sd) {
_sd.close();
}
}
}