/*
* Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software;Designed and Developed mainly by many Chinese
* opensource volunteers. you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 2 only, as published by the
* Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Any questions about this component can be directed to it's project Web address
* https://code.google.com/p/opencloudb/.
*
*/
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.MycatServer;
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 {
public static final int BALANCE_NONE = 0;
public static final int BALANCE_ALL_BACK = 1;
public static final int BALANCE_ALL = 2;
public static final int WRITE_ONLYONE_NODE = 0;
public static final int WRITE_RANDOM_NODE = 1;
public static final int WRITE_ALL_NODE = 2;
protected static final Logger LOGGER = Logger
.getLogger(PhysicalDBPool.class);
private final String hostName;
protected PhysicalDatasource[] writeSources;
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 int writeType;
private final Random random = new Random();
private final Random wnrandom = new Random();
public PhysicalDBPool(String name, PhysicalDatasource[] writeSources,
Map<Integer, PhysicalDatasource[]> readSources, int balance,
int writeType) {
this.hostName = name;
this.writeSources = writeSources;
this.banlance = balance;
this.writeType = writeType;
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();
}
public int getWriteType() {
return writeType;
}
private void setDataSourceProps() {
for (PhysicalDatasource ds : this.allDs) {
ds.setDbPool(this);
}
}
public PhysicalDatasource findDatasouce(BackendConnection 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;
}
/**
* all write datanodes
*
* @return
*/
public PhysicalDatasource[] getSources() {
return writeSources;
}
public PhysicalDatasource getSource() {
switch (writeType) {
case WRITE_ONLYONE_NODE: {
return writeSources[activedIndex];
}
case WRITE_RANDOM_NODE: {
int index = Math.abs(wnrandom.nextInt()) % writeSources.length;
PhysicalDatasource result = writeSources[index];
if (!this.isAlive(result)) {
// find all live nodes
ArrayList<Integer> alives = new ArrayList<Integer>(
writeSources.length - 1);
for (int i = 0; i < writeSources.length; i++) {
if (i != index) {
if (this.isAlive(writeSources[i])) {
alives.add(i);
}
}
}
if (alives.isEmpty()) {
result = writeSources[0];
} else {
// random select one
index = Math.abs(wnrandom.nextInt()) % alives.size();
result = writeSources[alives.get(index)];
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("select write source " + result.getName()
+ " for dataHost:" + this.getHostName());
}
return result;
}
default: {
throw new java.lang.IllegalArgumentException("writeType is "
+ writeType + " ,so can't return one write datasource ");
}
}
}
public int getActivedIndex() {
return activedIndex;
}
public boolean isInitSuccess() {
return initSuccess;
}
public int next(int i) {
if (checkIndex(i)) {
return (++i == writeSources.length) ? 0 : i;
} else {
return 0;
}
}
/**
* 鍒囨崲鏁版嵁婧�
*/
public boolean switchSource(int newIndex, boolean isAlarm, String reason) {
if (this.writeType != PhysicalDBPool.WRITE_ONLYONE_NODE
|| !checkIndex(newIndex)) {
return false;
}
final ReentrantLock lock = this.switchLock;
lock.lock();
try {
int current = activedIndex;
if (current != newIndex) {
// switch index
activedIndex = newIndex;
// init again
this.init(activedIndex);
// clear all connections
this.getSources()[current].clearCons("switch datasource");
// 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 < writeSources.length ? i : (i - writeSources.length);
}
public void init(int index) {
if (!checkIndex(index)) {
index = 0;
}
int active = -1;
for (int i = 0; i < writeSources.length; i++) {
int j = loop(i + index);
if (initSource(j, writeSources[j])) {
active = j;
activedIndex = active;
initSuccess = true;
LOGGER.info(getMessage(active, " init success"));
if (this.writeType == WRITE_ONLYONE_NODE) {
// only init one write datasource
MycatServer.getInstance().saveDataHostIndex(hostName,
activedIndex);
break;
}
}
}
if (!checkIndex(active)) {
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 < writeSources.length;
}
private String getMessage(int index, String info) {
return new StringBuilder().append(hostName).append(" index:")
.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() + " index :" + index);
CopyOnWriteArrayList<BackendConnection> list = new CopyOnWriteArrayList<BackendConnection>();
GetConnectionHandler getConHandler = new GetConnectionHandler(list,
initSize);
// long start=System.currentTimeMillis();
// long timeOut=start+5000*1000L;
ConnectionMeta conMeta = new ConnectionMeta(null, "utf8", -1, true);
for (int i = 0; i < initSize; i++) {
try {
ds.getConnection(conMeta, getConHandler, null);
} catch (Exception e) {
LOGGER.warn(getMessage(index, " init connection error."), e);
}
}
long timeOut = System.currentTimeMillis() + 60 * 1000;
// waiting for finish
while (!getConHandler.finished()
&& (System.currentTimeMillis() < timeOut)) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
LOGGER.info("init result :" + getConHandler.getStatusInfo());
for (BackendConnection c : list) {
c.release();
}
return !list.isEmpty();
}
public void doHeartbeat() {
// 妫�煡鍐呴儴鏄惁鏈夎繛鎺ユ睜閰嶇疆淇℃伅
if (writeSources == null || writeSources.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
}
/**
* back physical connection heartbeat check
*/
public void heartbeatCheck(long ildCheckPeriod) {
for (PhysicalDatasource ds : allDs) {
// only readnode or all write node or writetype=WRITE_ONLYONE_NODE
// and current write node will check
if (ds != null
&& (ds.getHeartbeat().getStatus() == DBHeartbeat.OK_STATUS)
&& (ds.isReadNode()
|| (this.writeType != WRITE_ONLYONE_NODE) || (this.writeType == WRITE_ONLYONE_NODE && ds == this
.getSource()))) {
ds.heatBeatCheck(ds.getConfig().getIdleTimeout(),
ildCheckPeriod);
}
}
}
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 : writeSources) {
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;
}
/**
* return connection for read balance
*
* @param handler
* @param attachment
* @param database
* @throws Exception
*/
public void getRWBanlanceCon(ConnectionMeta conMeta,
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
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(conMeta, handler, attachment);
}
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.allDs.size());
for (int i = 0; i < this.writeSources.length; i++) {
if (isAlive(writeSources[i])) {// write node is active
if (i == curActive && includeCurWriteNode == false) {
// not include cur active source
} else {
okSources.add(writeSources[i]);
}
if (!readSources.isEmpty()) {
// 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;
}
}