cn.vko.cache.dao.ha.FailoverMonitorJob.java Source code

Java tutorial

Introduction

Here is the source code for cn.vko.cache.dao.ha.FailoverMonitorJob.java

Source

/**
 * Copyright 1999-2011 Alibaba Group
 *
 * 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 cn.vko.cache.dao.ha;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.sql.DataSource;

import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.target.HotSwappableTargetSource;

/**
 * The standard to switch in this job is vague, because usually we will figure
 * out different SQLException type with error code to decide whether current
 * SQLException indicates a connection failure or something else. but as per
 * Hexianmao's statement, it's not necessary to be so, because sometimes, DBA or
 * someone will cause SQLException with some operations on purpose so that the
 * data source can be switched out to be maintained.<br>
 * So we adapt the failover checking logic from current Cobar's HAPool with some
 * code structure adjustment.<br>
 * 
 * @author fujohnwang
 * @since 1.0
 */
public class FailoverMonitorJob implements Runnable {

    transient final Logger logger = LoggerFactory.getLogger(FailoverMonitorJob.class);

    private String detectingSQL;
    /**
     * time unit in milliseconds
     */
    private long detectingRequestTimeout;

    private long recheckInterval;
    private int recheckTimes;

    private HotSwappableTargetSource hotSwapTargetSource;
    private DataSource masterDataSource;
    private DataSource standbyDataSource;
    private DataSource masterDetectorDataSource;
    private DataSource standbyDetectorDataSource;

    /**
     * first time it should be referenced to masterDetectorDataSource.
     */
    private DataSource currentDetectorDataSource;

    /**
     * Since {@link FailoverMonitorJob} will be scheduled to run in sequence,
     * One executor as instance field is ok.<br>
     * This executor will be used to execute detecting logic asynchronously, if
     * the execution exceeds given timeout threshold, we will check again before
     * switching to standby data source.
     */
    private ExecutorService executor;

    public FailoverMonitorJob(ExecutorService es) {
        Validate.notNull(es);
        this.executor = es;
    }

    @Override
    public void run() {
        Future<Integer> future = executor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                Integer result = -1;

                for (int i = 0; i < getRecheckTimes(); i++) {
                    Connection conn = null;
                    try {
                        conn = getCurrentDetectorDataSource().getConnection();
                        PreparedStatement pstmt = conn.prepareStatement(getDetectingSQL());
                        pstmt.execute();
                        pstmt.close();
                        result = 0;
                        break;
                    } catch (Exception e) {
                        logger.warn("(" + (i + 1) + ") check with failure. sleep (" + getRecheckInterval()
                                + ") for next round check.");
                        try {
                            TimeUnit.MILLISECONDS.sleep(getRecheckInterval());
                        } catch (InterruptedException e1) {
                            logger.warn("interrupted when waiting for next round rechecking.");
                        }
                        continue;
                    } finally {
                        if (conn != null) {
                            try {
                                conn.close();
                            } catch (SQLException e) {
                                logger.warn("failed to close checking connection:\n", e);
                            }
                        }
                    }
                }
                return result;
            }
        });

        try {
            Integer result = future.get(getDetectingRequestTimeout(), TimeUnit.MILLISECONDS);
            if (result == -1) {
                doSwap();
            }
        } catch (InterruptedException e) {
            logger.warn("interrupted when getting query result in FailoverMonitorJob.");
        } catch (ExecutionException e) {
            logger.warn("exception occured when checking failover status in FailoverMonitorJob");
        } catch (TimeoutException e) {
            logger.warn("exceed DetectingRequestTimeout threshold. Switch to standby data source.");
            doSwap();
        }
    }

    private void doSwap() {
        synchronized (hotSwapTargetSource) {
            DataSource target = (DataSource) getHotSwapTargetSource().getTarget();
            if (target == masterDataSource) {
                getHotSwapTargetSource().swap(standbyDataSource);
                currentDetectorDataSource = standbyDetectorDataSource;
            } else {
                getHotSwapTargetSource().swap(masterDataSource);
                currentDetectorDataSource = masterDetectorDataSource;
            }
        }
    }

    public String getDetectingSQL() {
        return detectingSQL;
    }

    public void setDetectingSQL(String detectingSQL) {
        this.detectingSQL = detectingSQL;
    }

    public long getDetectingRequestTimeout() {
        return detectingRequestTimeout;
    }

    public void setDetectingRequestTimeout(long detectingRequestTimeout) {
        this.detectingRequestTimeout = detectingRequestTimeout;
    }

    public HotSwappableTargetSource getHotSwapTargetSource() {
        return hotSwapTargetSource;
    }

    public void setHotSwapTargetSource(HotSwappableTargetSource hotSwapTargetSource) {
        this.hotSwapTargetSource = hotSwapTargetSource;
    }

    public DataSource getMasterDataSource() {
        return masterDataSource;
    }

    public void setMasterDataSource(DataSource masterDataSource) {
        this.masterDataSource = masterDataSource;
    }

    public DataSource getStandbyDataSource() {
        return standbyDataSource;
    }

    public void setStandbyDataSource(DataSource standbyDataSource) {
        this.standbyDataSource = standbyDataSource;
    }

    public void setRecheckInterval(long recheckInterval) {
        this.recheckInterval = recheckInterval;
    }

    public long getRecheckInterval() {
        return recheckInterval;
    }

    public void setRecheckTimes(int recheckTimes) {
        this.recheckTimes = recheckTimes;
    }

    public int getRecheckTimes() {
        return recheckTimes;
    }

    public void setMasterDetectorDataSource(DataSource masterDetectorDataSource) {
        this.masterDetectorDataSource = masterDetectorDataSource;
    }

    public DataSource getMasterDetectorDataSource() {
        return masterDetectorDataSource;
    }

    public void setStandbyDetectorDataSource(DataSource standbyDetectorDataSource) {
        this.standbyDetectorDataSource = standbyDetectorDataSource;
    }

    public DataSource getStandbyDetectorDataSource() {
        return standbyDetectorDataSource;
    }

    public void setCurrentDetectorDataSource(DataSource currentDetectorDataSource) {
        this.currentDetectorDataSource = currentDetectorDataSource;
    }

    public DataSource getCurrentDetectorDataSource() {
        return currentDetectorDataSource;
    }

}