/*
* 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.heartbeat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import org.opencloudb.backend.PhysicalDBPool;
import org.opencloudb.backend.PhysicalDatasource;
import org.opencloudb.mysql.nio.MySQLDataSource;
/**
* @author mycat
*/
public class MySQLHeartbeat extends DBHeartbeat {
private static final int MAX_RETRY_COUNT = 5;
private static final Logger LOGGER = Logger.getLogger(MySQLHeartbeat.class);
private final MySQLDataSource source;
private final MySQLDetectorFactory factory;
private final ReentrantLock lock;
private final int maxRetryCount;
private MySQLDetector detector;
public MySQLHeartbeat(MySQLDataSource source) {
this.source = source;
this.factory = new MySQLDetectorFactory();
this.lock = new ReentrantLock(false);
this.maxRetryCount = MAX_RETRY_COUNT;
this.status = INIT_STATUS;
this.heartbeatSQL = source.getHostConfig().getHearbeatSQL();
}
public MySQLDataSource getSource() {
return source;
}
public MySQLDetector getDetector() {
return detector;
}
public long getTimeout() {
MySQLDetector detector = this.detector;
if (detector == null) {
return -1L;
}
return detector.getHeartbeatTimeout();
}
public String getLastActiveTime() {
MySQLDetector detector = this.detector;
if (detector == null) {
return null;
}
long t = Math.max(detector.lastReadTime(), detector.lastWriteTime());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(new Date(t));
}
public void start() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
isStop.compareAndSet(true, false);
} finally {
lock.unlock();
}
}
public void stop() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (isStop.compareAndSet(false, true)) {
if (isChecking.get()) {
// nothing
} else {
MySQLDetector detector = this.detector;
if (detector != null) {
detector.quit();
isChecking.set(false);
}
}
}
} finally {
lock.unlock();
}
}
/**
* execute heart beat
*/
public void heartbeat() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (isChecking.compareAndSet(false, true)) {
MySQLDetector detector = this.detector;
if (detector == null || detector.isQuit()
|| detector.isClosed()) {
try {
detector = factory.make(this);
} catch (Throwable e) {
LOGGER.warn(source.getConfig().toString(), e);
setError(null);
return;
}
this.detector = detector;
} else {
detector.heartbeat();
}
} else {
MySQLDetector detector = this.detector;
if (detector != null) {
if (detector.isQuit() || detector.isClosed()) {
isChecking.compareAndSet(true, false);
} else if (detector.isHeartbeatTimeout()) {
setTimeout(detector);
}
}
}
} finally {
lock.unlock();
}
}
public void setResult(int result, MySQLDetector detector,
boolean isTransferError,String msg) {
switch (result) {
case OK_STATUS:
setOk(detector);
break;
case ERROR_STATUS:
if (detector.isQuit()) {
isChecking.set(false);
} else {
if (isTransferError) {
detector.close(msg);
}
setError(detector);
}
break;
}
}
private void setOk(MySQLDetector detector) {
recorder.set(detector.lastReadTime() - detector.lastWriteTime());
switch (status) {
case DBHeartbeat.TIMEOUT_STATUS:
this.status = DBHeartbeat.INIT_STATUS;
this.errorCount = 0;
this.isChecking.set(false);
if (isStop.get()) {
detector.quit();
} else {
heartbeat();// timeout, heart beat again
}
break;
default:
this.status = OK_STATUS;
this.errorCount = 0;
this.isChecking.set(false);
this.switchSourceIfNeed("heart beate ok");
if (isStop.get()) {
detector.quit();
}
}
}
private void setError(MySQLDetector detector) {
// should continues check error status
if (++errorCount < maxRetryCount) {
isChecking.set(false);
if (detector != null && isStop.get()) {
detector.quit();
} else {
heartbeat(); // error count not enough, heart beat again
}
return;
}
this.status = ERROR_STATUS;
this.errorCount = 0;
this.isChecking.set(false);
}
private void setTimeout(MySQLDetector detector) {
status = DBHeartbeat.TIMEOUT_STATUS;
isChecking.set(false);
}
/**
* switch data source
*/
private void switchSourceIfNeed(String reason) {
PhysicalDBPool pool = source.getDbPool();
// read node can't switch ,only write node can switch
if (pool.getWriteType() == PhysicalDBPool.WRITE_ONLYONE_NODE
&& !source.isReadNode() && this.status == DBHeartbeat.OK_STATUS) {
// try to see if need switch datasource
int curDatasourceHB = pool.getSource().getHeartbeat().getStatus();
if (pool.getSources().length > 1
&& curDatasourceHB != DBHeartbeat.INIT_STATUS
&& curDatasourceHB != DBHeartbeat.OK_STATUS) {
int myIndex = -1;
PhysicalDatasource[] allWriteNodes = pool.getSources();
for (int i = 0; i < allWriteNodes.length; i++) {
if (this.source == allWriteNodes[i]) {
myIndex = i;
break;
}
}
pool.switchSource(myIndex, true, reason);
}
}
}
}