Java tutorial
/** * 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.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.sql.DataSource; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.target.HotSwappableTargetSource; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import cn.vko.cache.dao.rw.DataSourceDescriptor; /** * FailoverHotSwapDataSourceCreator will create a Data Source Proxy that will * handle Failover on 2 DataSouces which are in same HA group.<br> * The {@link FailoverHotSwapDataSourceCreator} will enable 2 type of failover * strategies: * <ol> * <li>Passive Monitoring Failover Strategy : intercept method invocation status * on DataSource to decide whether to perform failover action.</li> * <li>Active Monitoring Failover Strategy : send detecting SQL to target data * source in a fixed time period to check the status of target data source. If * the monitoring action failed, the failover action will be taken.</li> * </ol> {@link FailoverHotSwapDataSourceCreator#passiveFailoverEnable} and * {@link FailoverHotSwapDataSourceCreator#positiveFailoverEnable} are * indicators that will be used to control which failover strategy will be used * or both.<br> * if positive failover strategy is enabled, there are 2 things need paying * attention to. firstly, a {@link #detectingSql} must be provided which will be * used to detect data source status. secondly, the value of * {@link #detectingTimeoutThreshold} should be less (even equals) than * {@link #monitorPeriod}. * * @author fujohnwang * @see DefaultCobarDataSourceService */ public class FailoverHotSwapDataSourceCreator implements IHADataSourceCreator, InitializingBean, DisposableBean { private transient final Logger logger = LoggerFactory.getLogger(FailoverHotSwapDataSourceCreator.class); /** * indicator that's used to indicate whether to enable passive failover * support. */ private boolean passiveFailoverEnable = false; /** * indicator that's used to indicate whether to enable positive failover * support. */ private boolean positiveFailoverEnable = true; /** * register scheduling job in synchronization to check DB status. */ private ConcurrentMap<ScheduledFuture<?>, ScheduledExecutorService> schedulerFutures = new ConcurrentHashMap<ScheduledFuture<?>, ScheduledExecutorService>(); /** * hold executor reference for later disposal. */ private List<ExecutorService> jobExecutorRegistry = new ArrayList<ExecutorService>(); /** * time unit in milliseconds */ private long monitorPeriod = 15 * 1000; /** * initial time delay before starting the positive HA monitoring job */ private int initialDelay = 0; /** * the detecting SQL that will be used to detect data source status. */ private String detectingSql = "select 1 "; /** * detecting timeout threshold with time unit in milliseconds.<br> * the value of this usually should be less than {@link #monitorPeriod}. */ private long detectingTimeoutThreshold = 15 * 1000; /** * time unit in milliseconds */ private long recheckInterval = 5 * 1000; private int recheckTimes = 3; @Override public DataSource createHADataSource(DataSourceDescriptor descriptor) throws Exception { DataSource activeDataSource = descriptor.getTarget(); DataSource standbyDataSource = descriptor.getStandby(); if (activeDataSource == null && standbyDataSource == null) { throw new IllegalArgumentException("must have at least one data source active."); } if (activeDataSource == null || standbyDataSource == null) { logger.warn("only one data source is available for use, so no HA support."); if (activeDataSource == null) { return standbyDataSource; } return activeDataSource; } HotSwappableTargetSource targetSource = new HotSwappableTargetSource(activeDataSource); ProxyFactory pf = new ProxyFactory(); pf.setInterfaces(new Class[] { DataSource.class }); pf.setTargetSource(targetSource); if (isPositiveFailoverEnable()) { DataSource targetDetectorDataSource = descriptor.getTarget(); DataSource standbyDetectorDataSource = descriptor.getStandby(); if (targetDetectorDataSource == null || standbyDetectorDataSource == null) { throw new IllegalArgumentException( "targetDetectorDataSource or standbyDetectorDataSource can't be null if positive failover is enabled."); } // 1. create active monitoring job for failover event ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); ExecutorService jobExecutor = Executors.newFixedThreadPool(1); jobExecutorRegistry.add(jobExecutor); FailoverMonitorJob job = new FailoverMonitorJob(jobExecutor); // 1.1 inject dependencies job.setHotSwapTargetSource(targetSource); job.setMasterDataSource(activeDataSource); job.setStandbyDataSource(standbyDataSource); job.setMasterDetectorDataSource(targetDetectorDataSource); job.setStandbyDetectorDataSource(standbyDetectorDataSource); job.setCurrentDetectorDataSource(targetDetectorDataSource); job.setDetectingRequestTimeout(getDetectingTimeoutThreshold()); job.setDetectingSQL(getDetectingSql()); job.setRecheckInterval(recheckInterval); job.setRecheckTimes(recheckTimes); // 1.2 start scheduling and keep reference for canceling and shutdown ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(job, initialDelay, monitorPeriod, TimeUnit.MILLISECONDS); schedulerFutures.put(future, scheduler); } if (isPassiveFailoverEnable()) { // 2. create data source proxy with passive event advice PassiveEventHotSwappableAdvice advice = new PassiveEventHotSwappableAdvice(); advice.setRetryInterval(recheckInterval); advice.setRetryTimes(recheckTimes); advice.setDetectingSql(detectingSql); advice.setTargetSource(targetSource); advice.setMainDataSource(activeDataSource); advice.setStandbyDataSource(standbyDataSource); pf.addAdvice(advice); } return (DataSource) pf.getProxy(); } @Override public void afterPropertiesSet() throws Exception { if (!isPassiveFailoverEnable() && !isPositiveFailoverEnable()) { return; } if (StringUtils.isEmpty(detectingSql)) { throw new IllegalArgumentException( "A 'detectingSql' should be provided if positive failover function is enabled."); } if (monitorPeriod <= 0 || detectingTimeoutThreshold <= 0 || recheckInterval <= 0 || recheckTimes <= 0) { throw new IllegalArgumentException( "'monitorPeriod' OR 'detectingTimeoutThreshold' OR 'recheckInterval' OR 'recheckTimes' must be positive."); } if (isPositiveFailoverEnable()) { if ((detectingTimeoutThreshold > monitorPeriod)) { throw new IllegalArgumentException( "the 'detectingTimeoutThreshold' should be less(or equals) than 'monitorPeriod'."); } if ((recheckInterval * recheckTimes) > detectingTimeoutThreshold) { throw new IllegalArgumentException( " 'recheckInterval * recheckTimes' can not be longer than 'detectingTimeoutThreshold'"); } } } @Override public void destroy() throws Exception { for (Map.Entry<ScheduledFuture<?>, ScheduledExecutorService> e : schedulerFutures.entrySet()) { ScheduledFuture<?> future = e.getKey(); ScheduledExecutorService scheduler = e.getValue(); future.cancel(true); shutdownExecutor(scheduler); } for (ExecutorService executor : jobExecutorRegistry) { shutdownExecutor(executor); } } private void shutdownExecutor(ExecutorService executor) { try { executor.shutdown(); executor.awaitTermination(5, TimeUnit.SECONDS); } catch (Exception ex) { logger.warn("interrupted when shutting down executor service."); } } /** * set the time period of positive database status detection, monitor will * send detecting request in a interval of such time period.<br> * * @param monitorPeriod */ public void setMonitorPeriod(long monitorPeriod) { this.monitorPeriod = monitorPeriod; } public long getMonitorPeriod() { return monitorPeriod; } /** * set the initial time delay before launching the monitoring job. default * value is 0, that's, start at once. * * @param initialDelay */ public void setInitialDelay(int initialDelay) { this.initialDelay = initialDelay; } public int getInitialDelay() { return initialDelay; } /** * set true to enable passive fail over support.<br> * default is false. * * @param passiveFailoverEnable */ public void setPassiveFailoverEnable(boolean passiveFailoverEnable) { this.passiveFailoverEnable = passiveFailoverEnable; } public boolean isPassiveFailoverEnable() { return passiveFailoverEnable; } /** * set false to disable positive fail over support, default is true too. * * @param positiveFailoverEnable */ public void setPositiveFailoverEnable(boolean positiveFailoverEnable) { this.positiveFailoverEnable = positiveFailoverEnable; } public boolean isPositiveFailoverEnable() { return positiveFailoverEnable; } /** * set the detecting sql that will be used to detect whether the status of * target database is OK.<br> * usually, it's better to assign an update SQL instead of a select one.<br> * * @param detectingSql */ public void setDetectingSql(String detectingSql) { this.detectingSql = detectingSql; } public String getDetectingSql() { return detectingSql; } /** * set the timeout that the detecting request doesn't return in such a time * period, it's should be less than {@link #monitorPeriod}. * * @param detectingTimeoutThreshold */ public void setDetectingTimeoutThreshold(long detectingTimeoutThreshold) { this.detectingTimeoutThreshold = detectingTimeoutThreshold; } public long getDetectingTimeoutThreshold() { return detectingTimeoutThreshold; } /** * when a detecting request fails, to make sure it's not a problem * occasionally, we will send another or more detecting request to detect * the status of database, the recheckInterval is the time interval that we * use to decide in which period that we should send next detecting request. * * @param recheckInterval */ public void setRecheckInterval(long recheckInterval) { this.recheckInterval = recheckInterval; } public long getRecheckInterval() { return recheckInterval; } /** * if a detecting request fails, we will send another or more to make sure, * this property will tell how many more requests should be send. * * @param recheckTimes */ public void setRecheckTimes(int recheckTimes) { this.recheckTimes = recheckTimes; } public int getRecheckTimes() { return recheckTimes; } }