package com.taobao.yugong.controller; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import com.google.common.collect.Lists; import com.taobao.yugong.applier.RecordApplier; import com.taobao.yugong.common.YuGongConstants; import com.taobao.yugong.common.alarm.AlarmMessage; import com.taobao.yugong.common.alarm.AlarmService; import com.taobao.yugong.common.audit.RecordDumper; import com.taobao.yugong.common.lifecycle.AbstractYuGongLifeCycle; import com.taobao.yugong.common.model.DbType; import com.taobao.yugong.common.model.ExtractStatus; import com.taobao.yugong.common.model.ProgressStatus; import com.taobao.yugong.common.model.RunMode; import com.taobao.yugong.common.model.YuGongContext; import com.taobao.yugong.common.model.position.Position; import com.taobao.yugong.common.model.record.Record; import com.taobao.yugong.common.stats.ProgressTracer; import com.taobao.yugong.common.stats.StatAggregation; import com.taobao.yugong.common.stats.StatAggregation.AggregationItem; import com.taobao.yugong.common.utils.YuGongUtils; import com.taobao.yugong.common.utils.thread.ExecutorTemplate; import com.taobao.yugong.common.utils.thread.NamedThreadFactory; import com.taobao.yugong.common.utils.thread.YuGongUncaughtExceptionHandler; import com.taobao.yugong.exception.YuGongException; import com.taobao.yugong.extractor.RecordExtractor; import com.taobao.yugong.positioner.RecordPositioner; import com.taobao.yugong.translator.BackTableDataTranslator; import com.taobao.yugong.translator.DataTranslator; import com.taobao.yugong.translator.core.EncodeDataTranslator; import com.taobao.yugong.translator.core.OracleIncreamentDataTranslator; /** * 代表一个同步迁移任务 * * @author agapple 2013-9-17 下午3:21:01 */ public class YuGongInstance extends AbstractYuGongLifeCycle { private final Logger logger = LoggerFactory.getLogger(YuGongInstance.class); private YuGongContext context; private RecordExtractor extractor; private RecordApplier applier; private DataTranslator translator; private RecordPositioner positioner; private AlarmService alarmService; private String alarmReceiver; private TableController tableController; private ProgressTracer progressTracer; private StatAggregation statAggregation; private DbType targetDbType; private List<DataTranslator> coreTranslators = Lists.newArrayList(); private Thread worker = null; private volatile boolean extractorDump = true; private volatile boolean applierDump = true; private CountDownLatch mutex = new CountDownLatch(1); private YuGongException exception = null; private String tableShitKey; private int retryTimes = 1; private int retryInterval; private int noUpdateThresold; private int noUpdateTimes = 0; // translator private boolean concurrent = true; private int threadSize = 5; private ThreadPoolExecutor executor; private String executorName; public YuGongInstance(YuGongContext context){ this.context = context; this.tableShitKey = context.getTableMeta().getFullName(); } public void start() { MDC.put(YuGongConstants.MDC_TABLE_SHIT_KEY, tableShitKey); super.start(); try { tableController.acquire();// 尝试获取 executorName = this.getClass().getSimpleName() + "-" + context.getTableMeta().getFullName(); if (executor == null) { executor = new ThreadPoolExecutor(threadSize, threadSize, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(threadSize * 2), new NamedThreadFactory(executorName), new ThreadPoolExecutor.CallerRunsPolicy()); } // 后续可改进为按类型识别添加 coreTranslators.add(new OracleIncreamentDataTranslator()); if (targetDbType.isOracle()) { coreTranslators.add(new EncodeDataTranslator(context.getSourceEncoding(), context.getTargetEncoding())); // oracle源库已经正确将编码转为'UTF-8'了 } if (!positioner.isStart()) { positioner.start(); } Position lastPosition = positioner.getLatest(); context.setLastPosition(lastPosition); if (!extractor.isStart()) { extractor.start(); } if (!applier.isStart()) { applier.start(); } worker = new Thread(new Runnable() { public void run() { try { if (context.getRunMode() != RunMode.INC) { // 目前只针对inc可以做重试 retryTimes = 1; } for (int i = 0; i < retryTimes; i++) { MDC.remove(YuGongConstants.MDC_TABLE_SHIT_KEY); if (i > 0) { logger.info("table[{}] is start , retrying ", context.getTableMeta().getFullName()); } else { logger.info("table[{}] is start", context.getTableMeta().getFullName()); } try { // 处理几次重试,避免因短暂网络问题导致同步挂起 processTable(); exception = null; break; // 处理成功就退出 } catch (YuGongException e) { exception = e; if (processException(e, i)) { break; } } finally { MDC.remove(YuGongConstants.MDC_TABLE_SHIT_KEY); } } if (exception == null) { // 记录到总文件下 logger.info("table[{}] is end", context.getTableMeta().getFullName()); } else if (ExceptionUtils.getRootCause(exception) instanceof InterruptedException) { progressTracer.update(context.getTableMeta().getFullName(), ProgressStatus.FAILED); logger.info("table[{}] is interrpt ,current status:{} !", context.getTableMeta() .getFullName(), extractor.status()); } else { progressTracer.update(context.getTableMeta().getFullName(), ProgressStatus.FAILED); logger.info("table[{}] is error , current status:{} !", context.getTableMeta() .getFullName(), extractor.status()); } } finally { tableController.release(YuGongInstance.this); // 标记为成功 mutex.countDown(); } } private void processTable() { try { MDC.put(YuGongConstants.MDC_TABLE_SHIT_KEY, tableShitKey); ExtractStatus status = ExtractStatus.NORMAL; AtomicLong batchId = new AtomicLong(0); Position lastPosition = positioner.getLatest(); context.setLastPosition(lastPosition); long tpsLimit = context.getTpsLimit(); do { long start = System.currentTimeMillis(); // 提取数据 List<Record> records = extractor.extract(); List<Record> ackRecords = records;// 保留ack引用 if (YuGongUtils.isEmpty(records)) { status = extractor.status(); } // 判断是否记录日志 RecordDumper.dumpExtractorInfo(batchId.incrementAndGet(), ackRecords, lastPosition, extractorDump); // 是否有系统的translator处理 if (YuGongUtils.isNotEmpty(coreTranslators)) { for (DataTranslator translator : coreTranslators) { records = processTranslator(translator, records); } } // 转换数据 records = processTranslator(translator, records); // 载入数据 Throwable applierException = null; for (int i = 0; i < retryTimes; i++) { try { applier.apply(records); applierException = null; break; } catch (Throwable e) { applierException = e; if (processException(e, i)) { break; } } } if (applierException != null) { throw applierException; } // 提供ack,进行后续处理 Position position = extractor.ack(ackRecords); if (position != null) { // 持久化位点信息,如果持久化失败,这批数据会重复 positioner.persist(position); } context.setLastPosition(position); lastPosition = position; // 判断是否记录日志 RecordDumper.dumpApplierInfo(batchId.get(), ackRecords, records, position, applierDump); long end = System.currentTimeMillis(); if (tpsLimit > 0) { tpsControl(ackRecords, start, end, tpsLimit); end = System.currentTimeMillis(); } if (YuGongUtils.isNotEmpty(ackRecords)) { statAggregation.push(new AggregationItem(start, end, Long.valueOf(ackRecords.size()))); } // 控制一下增量的退出 if (status == ExtractStatus.NO_UPDATE) { noUpdateTimes++; if (noUpdateThresold > 0 && noUpdateTimes > noUpdateThresold) { break; } } } while (status != ExtractStatus.TABLE_END); logger.info("table[{}] is end by {}", context.getTableMeta().getFullName(), status); statAggregation.print(); } catch (InterruptedException e) { // 正常退出,不发送报警 throw new YuGongException(e); } catch (Throwable e) { throw new YuGongException(e); } } private List<Record> processTranslator(final DataTranslator translator, List<Record> records) { if (records.isEmpty()) { return records; } if (translator != null) { if (translator instanceof BackTableDataTranslator) { ExecutorTemplate template = null; if (concurrent) { template = new ExecutorTemplate(executor); } records = ((BackTableDataTranslator) translator).translator(context.getSourceDs(), context.getTargetDs(), records, template); } else { records = translator.translator(records); } } return records; } private boolean processException(Throwable e, int i) { if (!(ExceptionUtils.getRootCause(e) instanceof InterruptedException)) { logger.error("retry {} ,something error happened. caused by {}", (i + 1), ExceptionUtils.getFullStackTrace(e)); try { alarmService.sendAlarm(new AlarmMessage(ExceptionUtils.getFullStackTrace(e), alarmReceiver)); } catch (Throwable e1) { logger.error("send alarm failed. ", e1); } try { Thread.sleep(retryInterval); } catch (InterruptedException e1) { exception = new YuGongException(e1); Thread.currentThread().interrupt(); return true; } } else { // interrupt事件,响应退出 return true; } return false; } }); worker.setUncaughtExceptionHandler(new YuGongUncaughtExceptionHandler(logger)); worker.setName(this.getClass().getSimpleName() + "-" + context.getTableMeta().getFullName()); worker.start(); logger.info("table[{}] start successful. extractor:{} , applier:{}, translator:{}", new Object[] { context.getTableMeta().getFullName(), extractor.getClass().getName(), applier.getClass().getName(), translator != null ? translator.getClass().getName() : "NULL" }); } catch (InterruptedException e) { progressTracer.update(context.getTableMeta().getFullName(), ProgressStatus.FAILED); exception = new YuGongException(e); mutex.countDown(); tableController.release(this); // 释放下 Thread.currentThread().interrupt(); } catch (Throwable e) { progressTracer.update(context.getTableMeta().getFullName(), ProgressStatus.FAILED); exception = new YuGongException(e); mutex.countDown(); logger.error("table[{}] start failed caused by {}", context.getTableMeta().getFullName(), ExceptionUtils.getFullStackTrace(e)); tableController.release(this); // 释放下 } } /** * 等待instance处理完成 * * @throws InterruptedException */ public void waitForDone() throws InterruptedException, YuGongException { mutex.await(); if (exception != null) { throw exception; } } public void stop() { MDC.put(YuGongConstants.MDC_TABLE_SHIT_KEY, tableShitKey); super.stop(); // 尝试中断 if (worker != null) { worker.interrupt(); try { worker.join(2 * 1000); } catch (InterruptedException e) { // ignore } } if (extractor.isStart()) { extractor.stop(); } if (applier.isStart()) { applier.stop(); } if (positioner.isStart()) { positioner.stop(); } executor.shutdownNow(); exception = null; logger.info("table[{}] stop successful. ", context.getTableMeta().getFullName()); } private void tpsControl(List<Record> result, long start, long end, long tps) throws InterruptedException { long expectTime = (result.size() * 1000) / tps; long runTime = expectTime - (end - start); if (runTime > 0) { Thread.sleep(runTime); } } public RecordExtractor getExtractor() { return extractor; } public void setExtractor(RecordExtractor extractor) { this.extractor = extractor; } public RecordApplier getApplier() { return applier; } public void setApplier(RecordApplier applier) { this.applier = applier; } public DataTranslator getTranslator() { return translator; } public void setTranslator(DataTranslator translator) { this.translator = translator; } public RecordPositioner getPositioner() { return positioner; } public void setPositioner(RecordPositioner positioner) { this.positioner = positioner; } public void setAlarmService(AlarmService alarmService) { this.alarmService = alarmService; } public void setExtractorDump(boolean extractorDump) { this.extractorDump = extractorDump; } public void setApplierDump(boolean applierDump) { this.applierDump = applierDump; } public void setTableController(TableController tableController) { this.tableController = tableController; } public void setTableShitKey(String tableShitKey) { this.tableShitKey = tableShitKey; } public void setStatAggregation(StatAggregation statAggregation) { this.statAggregation = statAggregation; } public void setAlarmReceiver(String alarmReceiver) { this.alarmReceiver = alarmReceiver; } public void setRetryTimes(int retryTimes) { this.retryTimes = retryTimes; } public void setRetryInterval(int retryInterval) { this.retryInterval = retryInterval; } public void setTargetDbType(DbType targetDbType) { this.targetDbType = targetDbType; } public YuGongContext getContext() { return context; } public void setProgressTracer(ProgressTracer progressTracer) { this.progressTracer = progressTracer; } public void setNoUpdateThresold(int noUpdateThresold) { this.noUpdateThresold = noUpdateThresold; } public void setThreadSize(int threadSize) { this.threadSize = threadSize; } public void setConcurrent(boolean concurrent) { this.concurrent = concurrent; } public void setExecutor(ThreadPoolExecutor executor) { this.executor = executor; } }