package com.taobao.yugong.applier;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.MDC;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCallback;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.MigrateMap;
import com.taobao.yugong.common.YuGongConstants;
import com.taobao.yugong.common.db.IncrementRecordMerger;
import com.taobao.yugong.common.db.meta.ColumnValue;
import com.taobao.yugong.common.model.YuGongContext;
import com.taobao.yugong.common.model.record.IncrementOpType;
import com.taobao.yugong.common.model.record.IncrementRecord;
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.exception.YuGongException;
/**
* 支持multi-thread处理
*
* @author agapple 2013-9-23 下午5:30:50
*/
public class MultiThreadIncrementRecordApplier extends IncrementRecordApplier {
private int threadSize = 5;
private int splitSize = 50;
private ThreadPoolExecutor executor;
private String executorName;
public MultiThreadIncrementRecordApplier(YuGongContext context){
super(context);
}
public MultiThreadIncrementRecordApplier(YuGongContext context, int threadSize, int splitSize){
super(context);
this.threadSize = threadSize;
this.splitSize = splitSize;
}
public MultiThreadIncrementRecordApplier(YuGongContext context, int threadSize, int splitSize,
ThreadPoolExecutor executor){
super(context);
this.threadSize = threadSize;
this.splitSize = splitSize;
this.executor = executor;
}
public void start() {
super.start();
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());
}
}
public void stop() {
super.stop();
executor.shutdownNow();
}
public void apply(List records) throws YuGongException {
// no one,just return
if (YuGongUtils.isEmpty(records)) {
return;
}
// 进行数据合并
List<IncrementRecord> mergeRecords = IncrementRecordMerger.merge(records);
doApply(mergeRecords);
}
protected void doApply(List records) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getTargetDs());
Map<List<String>, Map<IncrementOpType, List<IncrementRecord>>> buckets = buildBucket(records);
for (Map<IncrementOpType, List<IncrementRecord>> bucket : buckets.values()) {
if (context.isBatchApply()) {
// 优先处理delete
applyBatch(bucket.get(IncrementOpType.D), jdbcTemplate, IncrementOpType.D);
// 处理insert/update
applyBatch(bucket.get(IncrementOpType.I), jdbcTemplate, IncrementOpType.I);
applyBatch(bucket.get(IncrementOpType.U), jdbcTemplate, IncrementOpType.U);
} else {
applyOneByOne(bucket.get(IncrementOpType.D), jdbcTemplate);
applyOneByOne(bucket.get(IncrementOpType.I), jdbcTemplate);
applyOneByOne(bucket.get(IncrementOpType.U), jdbcTemplate);
}
}
}
/**
* 划分为table + I/U/D类型
*/
protected Map<List<String>, Map<IncrementOpType, List<IncrementRecord>>> buildBucket(List records) {
Map<List<String>, Map<IncrementOpType, List<IncrementRecord>>> buckets = MigrateMap.makeComputingMap(new Function<List<String>, Map<IncrementOpType, List<IncrementRecord>>>() {
public Map<IncrementOpType, List<IncrementRecord>> apply(List<String> names) {
return MigrateMap.makeComputingMap(new Function<IncrementOpType, List<IncrementRecord>>() {
public List<IncrementRecord> apply(IncrementOpType opType) {
return Lists.newArrayList();
}
});
}
});
// 先按照I/U/D进行划分下
for (Object record : records) {
IncrementRecord incRecord = (IncrementRecord) record;
List<String> names = Arrays.asList(incRecord.getSchemaName(), incRecord.getTableName());
buckets.get(names).get(incRecord.getOpType()).add(incRecord);
}
return buckets;
}
/**
* 并发处理batch
*/
protected void applyBatch(List<IncrementRecord> incRecords, final JdbcTemplate jdbcTemplate,
final IncrementOpType opType) {
if (incRecords.size() > splitSize) {// 超过一定大小才进行多线程处理
ExecutorTemplate template = new ExecutorTemplate(executor);
try {
int index = 0;// 记录下处理成功的记录下标
int size = incRecords.size();
// 全量复制时,无顺序要求,数据可以随意切割,直接按照splitSize切分后提交到多线程中进行处理
for (; index < size;) {
int end = (index + splitSize > size) ? size : (index + splitSize);
final List<IncrementRecord> subList = incRecords.subList(index, end);
template.submit(new Runnable() {
public void run() {
String name = Thread.currentThread().getName();
try {
MDC.put(YuGongConstants.MDC_TABLE_SHIT_KEY, context.getTableMeta().getFullName());
Thread.currentThread().setName(executorName);
applyBatch0(subList, jdbcTemplate, opType);
} finally {
Thread.currentThread().setName(name);
}
}
});
index = end;// 移动到下一批次
}
// 等待所有结果返回
template.waitForResult();
} finally {
template.clear();
}
} else {
applyBatch0(incRecords, jdbcTemplate, opType);
}
}
/**
* batch处理支持
*/
protected void applyBatch0(final List<IncrementRecord> incRecords, JdbcTemplate jdbcTemplate, IncrementOpType opType) {
if (YuGongUtils.isEmpty(incRecords)) {
return;
}
boolean redoOneByOne = false;
try {
TableSqlUnit sqlUnit = getSqlUnit(incRecords.get(0));
String applierSql = sqlUnit.applierSql;
final Map<String, Integer> indexs = sqlUnit.applierIndexs;
jdbcTemplate.execute(applierSql, new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
for (IncrementRecord incRecord : incRecords) {
int count = 0;
// 需要先加字段
List<ColumnValue> cvs = incRecord.getColumns();
for (ColumnValue cv : cvs) {
Integer index = getIndex(indexs, cv, true); // 考虑delete的目标库主键,可能在源库的column中
if (index != null) {
ps.setObject(index, cv.getValue(), cv.getColumn().getType());
count++;
}
}
// 添加主键
List<ColumnValue> pks = incRecord.getPrimaryKeys();
for (ColumnValue pk : pks) {
Integer index = getIndex(indexs, pk, true);// 考虑delete的目标库主键,可能在源库的column中
if (index != null) {
ps.setObject(index, pk.getValue(), pk.getColumn().getType());
count++;
}
}
if (count != indexs.size()) {
processMissColumn(incRecord, indexs);
}
ps.addBatch();
}
return ps.executeBatch();
}
});
} catch (Exception e) {
// catch the biggest exception,no matter how, rollback it;
redoOneByOne = true;
// conn.rollback();
}
// batch cannot pass the duplicate entry exception,so
// if executeBatch throw exception,rollback it, and
// redo it one by one
if (redoOneByOne) {
// 如果出错,强制使用单线程处理
super.applyOneByOne(incRecords, jdbcTemplate);
}
}
protected void applyOneByOne(List<IncrementRecord> incRecords, final JdbcTemplate jdbcTemplate) {
if (incRecords.size() > 1) {
ExecutorTemplate template = new ExecutorTemplate(executor);
try {
int index = 0;// 记录下处理成功的记录下标
int size = incRecords.size();
// 全量复制时,无顺序要求,数据可以随意切割,直接按照splitSize切分后提交到多线程中进行处理
for (; index < size;) {
int end = (index + 1 > size) ? size : (index + 1);
final List<IncrementRecord> subList = incRecords.subList(index, end);
template.submit(new Runnable() {
public void run() {
String name = Thread.currentThread().getName();
try {
MDC.put(YuGongConstants.MDC_TABLE_SHIT_KEY, context.getTableMeta().getFullName());
Thread.currentThread().setName(executorName);
applyOneByOne(subList, jdbcTemplate);
} finally {
Thread.currentThread().setName(name);
}
}
});
index = end;// 移动到下一批次
}
// 等待所有结果返回
template.waitForResult();
} finally {
template.clear();
}
} else {
super.applyOneByOne(incRecords, jdbcTemplate);
}
}
}