/* * Copyright 2012-2015 org.opencloudb. * * 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 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.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) { switch (result) { case OK_STATUS: setOk(detector); break; case ERROR_STATUS: if (detector.isQuit()) { isChecking.set(false); } else { if (isTransferError) { detector.close("heartbeat transfererr"); } 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); if (isStop.get()) { detector.quit(); } } } private void setError(MySQLDetector detector) { if (++errorCount < maxRetryCount) { isChecking.set(false); if (detector != null && isStop.get()) { detector.quit(); } else { heartbeat(); // error count not enough, heart beat again } } else { this.status = ERROR_STATUS; this.errorCount = 0; this.isChecking.set(false); try { switchSource("ERROR"); } finally { if (detector != null && isStop.get()) { detector.quit(); } } } } private void setTimeout(MySQLDetector detector) { status = DBHeartbeat.TIMEOUT_STATUS; if (++errorCount >= MAX_RETRY_COUNT) { try { switchSource("TIMEOUT"); } finally { detector.quit(); isChecking.set(false); } } } /** * switch data source */ private void switchSource(String reason) { // read node can't switch ,only write node can switch if (!isStop.get() && !source.isReadNode()) { PhysicalDBPool pool = source.getDbPool(); if (pool.getSources().length > 1) { int i = pool.next(pool.getActivedIndex()); pool.switchSource(i, true, reason); } } } }