package org.opencloudb.backend;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import org.opencloudb.config.Alarms;
import org.opencloudb.heartbeat.DBHeartbeat;
import org.opencloudb.mysql.nio.handler.GetConnectionHandler;
import org.opencloudb.mysql.nio.handler.ResponseHandler;
public class PhysicalDBPool {
private static final int BALANCE_NONE = 0;
private static final int BALANCE_ALL_BACK = 1;
private static final int BALANCE_ALL = 2;
protected static final Logger LOGGER = Logger
.getLogger(PhysicalDBPool.class);
private final String hostName;
protected PhysicalDatasource[] sources;
protected Map<Integer, PhysicalDatasource[]> readSources;
protected volatile int activedIndex;
protected volatile boolean initSuccess;
protected final ReentrantLock switchLock = new ReentrantLock();
private final Collection<PhysicalDatasource> allDs;
private final int banlance;
private final Random random = new Random();
public PhysicalDBPool(String name, PhysicalDatasource[] writeSources,
Map<Integer, PhysicalDatasource[]> readSources, int balance) {
this.hostName = name;
this.sources = writeSources;
this.banlance = balance;
Iterator<Map.Entry<Integer, PhysicalDatasource[]>> entryItor = readSources
.entrySet().iterator();
while (entryItor.hasNext()) {
PhysicalDatasource[] values = entryItor.next().getValue();
if (values.length == 0) {
entryItor.remove();
}
}
this.readSources = readSources;
this.allDs = this.genAllDataSources();
LOGGER.info("total resouces of dataHost " + this.hostName + " is :"
+ allDs.size());
setDataSourceProps();
}
private void setDataSourceProps() {
for (PhysicalDatasource ds : this.allDs) {
ds.setDbPool(this);
}
}
public PhysicalDatasource findDatasouce(PhysicalConnection exitsCon) {
for (PhysicalDatasource ds : this.allDs) {
if (ds.isReadNode() == exitsCon.isFromSlaveDB()) {
if (ds.isMyConnection(exitsCon)) {
return ds;
}
}
}
LOGGER.warn("can't find connection in pool " + this.hostName + " con:"
+ exitsCon);
return null;
}
public String getHostName() {
return hostName;
}
public PhysicalDatasource[] getSources() {
return sources;
}
public PhysicalDatasource getSource() {
return sources[activedIndex];
}
public int getActivedIndex() {
return activedIndex;
}
public boolean isInitSuccess() {
return initSuccess;
}
public int next(int i) {
if (checkIndex(i)) {
return (++i == sources.length) ? 0 : i;
} else {
return 0;
}
}
/**
* 鍒囨崲鏁版嵁婧�
*/
public boolean switchSource(int newIndex, boolean isAlarm, String reason) {
if (!checkIndex(newIndex)) {
return false;
}
final ReentrantLock lock = this.switchLock;
lock.lock();
try {
int current = activedIndex;
if (current != newIndex) {
// write log
LOGGER.warn(switchMessage(current, newIndex, false, reason));
return true;
}
} finally {
lock.unlock();
}
return false;
}
private String switchMessage(int current, int newIndex, boolean alarm,
String reason) {
StringBuilder s = new StringBuilder();
if (alarm) {
s.append(Alarms.DATANODE_SWITCH);
}
s.append("[Host=").append(hostName).append(",result=[").append(current)
.append("->");
s.append(newIndex).append("],reason=").append(reason).append(']');
return s.toString();
}
private int loop(int i) {
return i < sources.length ? i : (i - sources.length);
}
public void init(int index) {
if (!checkIndex(index)) {
index = 0;
}
int active = -1;
for (int i = 0; i < sources.length; i++) {
int j = loop(i + index);
if (initSource(j, sources[j])) {
active = j;
break;
}
}
if (checkIndex(active)) {
activedIndex = active;
initSuccess = true;
LOGGER.info(getMessage(active, " init success"));
} else {
initSuccess = false;
StringBuilder s = new StringBuilder();
s.append(Alarms.DEFAULT).append(hostName).append(" init failure");
LOGGER.error(s.toString());
}
}
private boolean checkIndex(int i) {
return i >= 0 && i < sources.length;
}
private String getMessage(int index, String info) {
return new StringBuilder().append(hostName).append(':').append(index)
.append(info).toString();
}
private boolean initSource(int index, PhysicalDatasource ds) {
int initSize = ds.getConfig().getMinCon();
LOGGER.info("init backend myqsl source ,create connections total "
+ initSize + " for " + ds.getName());
CopyOnWriteArrayList<PhysicalConnection> list = new CopyOnWriteArrayList<PhysicalConnection>();
GetConnectionHandler getConHandler = new GetConnectionHandler(list,
initSize);
// long start=System.currentTimeMillis();
// long timeOut=start+5000*1000L;
for (int i = 0; i < initSize; i++) {
try {
ds.getConnection(getConHandler, null, null);
} catch (Exception e) {
LOGGER.warn(getMessage(index, " init connection error."), e);
}
}
// waiting for finish
while (!getConHandler.finished()) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (PhysicalConnection c : list) {
c.release();
}
return !list.isEmpty();
}
public void doHeartbeat() {
// 妫�煡鍐呴儴鏄惁鏈夎繛鎺ユ睜閰嶇疆淇℃伅
if (sources == null || sources.length == 0) {
return;
}
for (PhysicalDatasource source : this.allDs) {
// 鍑嗗鎵ц蹇冭烦妫�祴
if (source != null) {
source.doHeartbeat();
} else {
StringBuilder s = new StringBuilder();
s.append(Alarms.DEFAULT).append(hostName)
.append(" current dataSource is null!");
LOGGER.error(s.toString());
}
}
// 璇诲簱鐨勫績璺虫娴�
// todo
}
/**
* 绌洪棽妫�煡
*/
public void idleCheck() {
for (PhysicalDatasource ds : sources) {
if (ds != null) {
ds.idleCheck(ds.getConfig().getIdleTimeout());
}
}
}
public void startHeartbeat() {
for (PhysicalDatasource source : this.allDs) {
source.startHeartbeat();
}
}
public void stopHeartbeat() {
for (PhysicalDatasource source : this.allDs) {
source.stopHeartbeat();
}
}
public void clearDataSources(String reason) {
LOGGER.info("clear datasours of pool " + this.hostName);
for (PhysicalDatasource source : this.allDs) {
LOGGER.info("clear datasoure of pool " + this.hostName + " ds:"
+ source.getConfig());
source.clearCons(reason);
source.stopHeartbeat();
}
}
public Collection<PhysicalDatasource> genAllDataSources() {
LinkedList<PhysicalDatasource> allSources = new LinkedList<PhysicalDatasource>();
for (PhysicalDatasource ds : sources) {
if (ds != null) {
allSources.add(ds);
}
}
for (PhysicalDatasource[] dataSources : this.readSources.values()) {
for (PhysicalDatasource ds : dataSources) {
if (ds != null) {
allSources.add(ds);
}
}
}
return allSources;
}
public Collection<PhysicalDatasource> getAllDataSources() {
return this.allDs;
}
private ArrayList<PhysicalDatasource> getAllActiveSlaveSources() {
ArrayList<PhysicalDatasource> okSources = new ArrayList<PhysicalDatasource>(
this.allDs.size());
for (PhysicalDatasource[] readsources : this.readSources.values()) {
for (PhysicalDatasource read : readsources) {
if (isAlive(read)) {
okSources.add(read);
}
}
}
return okSources;
}
/**
* return connection for read balance
*
* @param handler
* @param attachment
* @param database
* @throws Exception
*/
public void getRWBanlanceCon(ResponseHandler handler, Object attachment,
String database) throws Exception {
PhysicalDatasource theNode = null;
ArrayList<PhysicalDatasource> okSources = null;
switch (banlance) {
case BALANCE_ALL_BACK: {// all read nodes and the standard by masters
if (sources[this.activedIndex].getHeartbeat().getStatus() == DBHeartbeat.OK_STATUS) {// cur
okSources = getAllActiveSlaveSources();
} else {// at least one master alive
okSources = getAllActiveRWSources(false);
}
theNode = randomSelect(okSources);
break;
}
case BALANCE_ALL: {
okSources = getAllActiveRWSources(true);
theNode = randomSelect(okSources);
break;
}
case BALANCE_NONE:
default:
// return default write data source
theNode = this.getSource();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("select read source " + theNode.getName()
+ " for dataHost:" + this.getHostName());
}
theNode.getConnection(handler, attachment, database);
}
private PhysicalDatasource randomSelect(
ArrayList<PhysicalDatasource> okSources) {
if (okSources.isEmpty()) {
return this.getSource();
} else {
int index = Math.abs(random.nextInt()) % okSources.size();
return okSources.get(index);
}
}
private boolean isAlive(PhysicalDatasource theSource) {
return (theSource.getHeartbeat().getStatus() == DBHeartbeat.OK_STATUS);
}
/**
* return all backup write sources
*
* @return
*/
private ArrayList<PhysicalDatasource> getAllActiveRWSources(
boolean includeCurWriteNode) {
int curActive = activedIndex;
ArrayList<PhysicalDatasource> okSources = new ArrayList<PhysicalDatasource>(
this.readSources.size() - 1);
for (int i = 0; i < this.sources.length; i++) {
if (i == curActive && includeCurWriteNode == false) {
// not include cur active source
} else {
okSources.add(sources[i]);
}
if (isAlive(sources[i])) {// write node is active
// check all slave nodes
PhysicalDatasource[] allSlaves = this.readSources.get(i);
if (allSlaves != null) {
for (PhysicalDatasource slave : allSlaves) {
if (isAlive(slave)) {
okSources.add(slave);
}
}
}
}
}
return okSources;
}
}