package com.yiqiniu.easytrans;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.yiqiniu.easytrans.config.EasyTransConifg;
import com.yiqiniu.easytrans.log.TransactionLogReader;
import com.yiqiniu.easytrans.log.TransactionLogWritter;
import com.yiqiniu.easytrans.log.vo.LogCollection;
import com.yiqiniu.easytrans.master.EasyTransMasterSelector;
import com.yiqiniu.easytrans.protocol.IdempotentTypeDeclare.TransactionId;
import com.yiqiniu.easytrans.util.NamedThreadFactory;
public class ConsistentGuardianDaemon {
@Resource
private EasyTransConifg config;
@Resource
private TransactionLogReader logReader;
@Resource
private TransactionLogWritter logWritter;
@Resource
private ConsistentGuardian guardian;
@Resource
private EasyTransMasterSelector master;
private Logger LOG = LoggerFactory.getLogger(this.getClass());
private HashMap<TransactionId, Object[]/*lastRetryTime,retried count*/> mapRetryInfo = new HashMap<TransactionId, Object[]>();
@PostConstruct
private void init(){
final Integer executeInterval = Integer.valueOf(config.getExtendConfig("consistent.daemon.interval"));
final Integer pageSize = Integer.valueOf(config.getExtendConfig("consistent.daemon.pagesize"));
final Integer delay = Integer.valueOf(config.getExtendConfig("consistent.daemon.involve.delay"));
ThreadFactory threadFactory = new NamedThreadFactory("ConsistentGuardianJob", false);
Runnable run = new Runnable() {
@Override
public void run() {
try {
master.await();
if (!master.hasLeaderShip()) {
mapRetryInfo.clear();
return;
}
LogCollection locationId = null;
List<LogCollection> collections = null;
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, -delay);
do {
collections = logReader.getUnfinishedLogs(locationId, pageSize, calendar.getTime());
for (LogCollection logCollection : collections) {
if (checkRetry(logCollection)) {
try {
updateRetryInfo(logCollection, guardian.process(logCollection));
} catch (Exception e) {
updateRetryInfo(logCollection, false);
LOG.error("Consistent handle failed!", e);
}
LOG.info(
"End finish handling transaction {} {} {}",
logCollection.getAppId(),
logCollection.getBusCode(),
logCollection.getTrxId());
} else {
LOG.info(
"Skip transaction {} {} {},for retry time limie",
logCollection.getAppId(),
logCollection.getBusCode(),
logCollection.getTrxId());
}
}
if(collections != null && collections.size() != 0){
locationId = collections.get(collections.size() - 1);
}else{
locationId = null;
}
} while (collections != null && collections.size() != 0 && master.hasLeaderShip());
} catch(InterruptedException e){
LOG.warn("Interrupte recived,end consisten guadian");
} catch (Exception e) {
LOG.error("Consistent Guardian Deamon Error",e);
}
}
};
Executors.newSingleThreadScheduledExecutor(threadFactory).scheduleWithFixedDelay(
run,
10,
executeInterval,
TimeUnit.SECONDS);
}
private void updateRetryInfo(LogCollection logCollection,boolean success) {
TransactionId id = new TransactionId(logCollection.getAppId(), logCollection.getBusCode(), logCollection.getTrxId());
if(success){
mapRetryInfo.remove(id);
}else{
Object[] objects = mapRetryInfo.get(id);
if(objects == null){
objects = new Object[]{new Date(),1};
mapRetryInfo.put(id, objects);
}else{
objects[0] = new Date();
objects[1] = ((Integer)objects[1]) + 1;
}
}
}
Calendar instance = Calendar.getInstance();
private boolean checkRetry(LogCollection logCollection) {
TransactionId id = new TransactionId(logCollection.getAppId(), logCollection.getBusCode(), logCollection.getTrxId());
Object[] objects = mapRetryInfo.get(id);
if(objects == null){
return true;
}
Date lastTryTime = (Date) objects[0];
Integer triedCount = (Integer) objects[1];
instance.setTimeInMillis(lastTryTime.getTime());
instance.add(Calendar.SECOND, 2^(triedCount + 1));
long nextExecuteTime = instance.getTimeInMillis();
if(nextExecuteTime < System.currentTimeMillis()){
return true;
}
return false;
}
}