package io.mycat.backend.postgresql.heartbeat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.mycat.backend.PhysicalDBPool;
import io.mycat.backend.PhysicalDatasource;
import io.mycat.backend.heartbeat.DBHeartbeat;
import io.mycat.backend.postgresql.PostgreSQLDataSource;
import io.mycat.server.config.node.DataHostConfig;
public class PostgreSQLHeartbeat extends DBHeartbeat {
private static final int MAX_RETRY_COUNT = 5;
public static final Logger LOGGER = LoggerFactory.getLogger(PostgreSQLHeartbeat.class);
private PostgreSQLDataSource source;
private ReentrantLock lock;
private int maxRetryCount;
private PostgreSQLDetector detector;
public PostgreSQLHeartbeat(PostgreSQLDataSource source) {
this.source = source;
this.lock = new ReentrantLock(false);
this.maxRetryCount = MAX_RETRY_COUNT;
this.status = INIT_STATUS;
this.heartbeatSQL = source.getHostConfig().getHeartbeatSQL();
}
@Override
public void start() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
isStop.compareAndSet(true, false);
super.status = DBHeartbeat.OK_STATUS;
} finally {
lock.unlock();
}
}
@Override
public void stop() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (isStop.compareAndSet(false, true)) {
if (isChecking.get()) {
// nothing
} else {
PostgreSQLDetector detector = this.detector;
if (detector != null) {
detector.quit();
isChecking.set(false);
}
}
}
} finally {
lock.unlock();
}
}
@Override
public String getLastActiveTime() {
PostgreSQLDetector detector = this.detector;
if (detector == null) {
return null;
}
long t = detector.getLasstReveivedQryTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(new Date(t));
}
@Override
public long getTimeout() {
PostgreSQLDetector detector = this.detector;
if (detector == null) {
return -1L;
}
return detector.getHeartbeatTimeout();
}
@Override
public void heartbeat() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (isChecking.compareAndSet(false, true)) {
PostgreSQLDetector detector = this.detector;
if (detector == null || detector.isQuit()) {
try {
detector = new PostgreSQLDetector(this);
detector.heartbeat();
} catch (Exception e) {
LOGGER.warn(source.getConfig().toString(), e);
setResult(ERROR_STATUS, detector, null);
return;
}
this.detector = detector;
} else {
detector.heartbeat();
}
} else {
PostgreSQLDetector detector = this.detector;
if (detector != null) {
if (detector.isQuit()) {
isChecking.compareAndSet(true, false);
} else if (detector.isHeartbeatTimeout()) {
setResult(TIMEOUT_STATUS, detector, null);
}
}
}
} finally {
lock.unlock();
}
}
public PostgreSQLDataSource getSource() {
return source;
}
public void setResult(int result, PostgreSQLDetector detector, Object attr) {
this.isChecking.set(false);
switch (result) {
case OK_STATUS:
setOk(detector);
break;
case ERROR_STATUS:
setError(detector);
break;
case TIMEOUT_STATUS:
setTimeout(detector);
break;
}
if (this.status != OK_STATUS) {
switchSourceIfNeed("heartbeat error");
}
}
private void switchSourceIfNeed(String reason) {
int switchType = source.getHostConfig().getSwitchType();
if (switchType == DataHostConfig.NOT_SWITCH_DS) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("not switch datasource ,for switchType is "
+ DataHostConfig.NOT_SWITCH_DS);
return;
}
return;
}
PhysicalDBPool pool = this.source.getDbPool();
int curDatasourceHB = pool.getSource().getHeartbeat().getStatus();
// read node can't switch ,only write node can switch
if (pool.getWriteType() == PhysicalDBPool.WRITE_ONLYONE_NODE
&& !source.isReadNode()
&& curDatasourceHB != DBHeartbeat.OK_STATUS
&& pool.getSources().length > 1) {
synchronized (pool) {
// try to see if need switch datasource
curDatasourceHB = pool.getSource().getHeartbeat().getStatus();
if (curDatasourceHB != DBHeartbeat.INIT_STATUS
&& curDatasourceHB != DBHeartbeat.OK_STATUS) {
int curIndex = pool.getActivedIndex();
int nextId = pool.next(curIndex);
PhysicalDatasource[] allWriteNodes = pool.getSources();
while (true) {
if (nextId == curIndex) {
break;
}
PhysicalDatasource theSource = allWriteNodes[nextId];
DBHeartbeat theSourceHB = theSource.getHeartbeat();
int theSourceHBStatus = theSourceHB.getStatus();
if (theSourceHBStatus == DBHeartbeat.OK_STATUS) {
if (switchType == DataHostConfig.SYN_STATUS_SWITCH_DS) {
if (Integer.valueOf(0).equals(
theSourceHB.getSlaveBehindMaster())) {
LOGGER.info("try to switch datasource ,slave is synchronized to master "
+ theSource.getConfig());
pool.switchSource(nextId, true, reason);
break;
} else {
LOGGER.warn("ignored datasource ,slave is not synchronized to master , slave behind master :"
+ theSourceHB
.getSlaveBehindMaster()
+ " " + theSource.getConfig());
}
} else {
// normal switch
LOGGER.info("try to switch datasource ,not checked slave synchronize status "
+ theSource.getConfig());
pool.switchSource(nextId, true, reason);
break;
}
}
nextId = pool.next(nextId);
}
}
}
}
}
private void setTimeout(PostgreSQLDetector detector) {
this.isChecking.set(false);
status = DBHeartbeat.TIMEOUT_STATUS;
}
private void setError(PostgreSQLDetector detector) {
// should continues check error status
if (++errorCount < maxRetryCount) {
if (detector != null && !detector.isQuit()) {
heartbeat(); // error count not enough, heart beat again
}
//return;
} else
{
if (detector != null ) {
detector.quit();
}
this.status = ERROR_STATUS;
this.errorCount = 0;
}
}
private void setOk(PostgreSQLDetector detector) {
recorder.set(detector.getLasstReveivedQryTime()
- detector.getLastSendQryTime());
switch (status) {
case DBHeartbeat.TIMEOUT_STATUS:
this.status = DBHeartbeat.INIT_STATUS;
this.errorCount = 0;
if (isStop.get()) {
detector.quit();
} else {
heartbeat();// timeout, heart beat again
}
break;
case DBHeartbeat.OK_STATUS:
break;
default:
this.status = OK_STATUS;
this.errorCount = 0;
}
if (isStop.get()) {
detector.quit();
}
}
}