Java中分布式事务补偿机制方案

Java中分布式事务补偿机制,当A服务调用B服务失败时,使用该异步注解则,会把失败调用数据保存到数据库中,进行重试,从而保证B服务调用成功,即使调用不成功,也可以拿到报错信息,留下对应的调用记录,代码如下:

annotation:
package com.lx.annotation;


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *
 *类描述:异步任务注解
 *
 *@Author:liuweiping
 *@Version:1.0.0
 */
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AsyncTask {

	/**
	 *  功能描述
	 */
	String value() default "";
	
	/**
	 * 
	 * 延迟执行,当需要当前事务提交后才执行可设置
	 */
	long delay() default 3000;
	
	
	/**
	 * 过滤重复参数任务
	 */
	boolean filterRepeatCall() default true;

}

entiry:

package com.lx.task.entity;


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.lx.conmon.BaseEntity;

import java.util.Date;

/**
 * 
 *类描述:异步任务表
 *
 *@Author:liuweiping
 *@date:2021年07月09日
 *@Version:1.0.0
 */
@TableName("t_async_task")
public class AsyncTaskEntity extends BaseEntity {

	private static final long serialVersionUID = 1L;
	/**
	 * ID
	 */
    @TableId(type=IdType.AUTO)
    private Integer id;
		    
    /**
	 * 仓库编码
	 */
    private String locno;
		    
    /**
	 * 任务名称
	 */
    private String name;
		    
    /**
	 * 业务ID
	 */
    private String sid;
		    
    /**
	 * 类
	 */
    private String execBean;
		    
    /**
	 * 方法
	 */
    private String execMethod;
		    
    /**
	 * 参数
	 */
    private String execParams;
		    
    /**
	 * 执行结果
	 */
    private String execResult;
		    
    /**
	 * 执行签名MD5(类,方法,参数)
	 */
    private String execSign;
		    
    /**
	 * 执行时间
	 */
    private Integer execTime;
		    
    /**
	 * 执行次数
	 */
    private Integer tryCount;
		    
    /**
	 * 是否执行成功(0:否,1:是)
	 */
    private Integer successFlag;
		    
    /**
	 * 首次执行时间
	 */
    private Date firstTime;
		    
    /**
	 * 最后执行时间
	 */
    private Date lastTime;
		    
    /**
	 * 下次重试时间
	 */
    private Date nextTime;
		    
    /**
	 * 备注
	 */
    private String remarks;
									    	
	/**
	 * 获取:ID
	 */
	public Integer getId() {
		return id;
	}
	
	/**
	 * 设置:ID
	 */
	public void setId(Integer id) {
		this.id = id;
	}
	    	
	/**
	 * 获取:仓库编码
	 */
	public String getLocno() {
		return locno;
	}
	
	/**
	 * 设置:仓库编码
	 */
	public void setLocno(String locno) {
		this.locno = locno;
	}
	    	
	/**
	 * 获取:任务名称
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * 设置:任务名称
	 */
	public void setName(String name) {
		this.name = name;
	}
	    	
	/**
	 * 获取:业务ID
	 */
	public String getSid() {
		return sid;
	}
	
	/**
	 * 设置:业务ID
	 */
	public void setSid(String sid) {
		this.sid = sid;
	}
	    	
	/**
	 * 获取:类
	 */
	public String getExecBean() {
		return execBean;
	}
	
	/**
	 * 设置:类
	 */
	public void setExecBean(String execBean) {
		this.execBean = execBean;
	}
	    	
	/**
	 * 获取:方法
	 */
	public String getExecMethod() {
		return execMethod;
	}
	
	/**
	 * 设置:方法
	 */
	public void setExecMethod(String execMethod) {
		this.execMethod = execMethod;
	}
	    	
	/**
	 * 获取:参数
	 */
	public String getExecParams() {
		return execParams;
	}
	
	/**
	 * 设置:参数
	 */
	public void setExecParams(String execParams) {
		this.execParams = execParams;
	}
	    	
	/**
	 * 获取:执行结果
	 */
	public String getExecResult() {
		return execResult;
	}
	
	/**
	 * 设置:执行结果
	 */
	public void setExecResult(String execResult) {
		this.execResult = execResult;
	}
	    	
	/**
	 * 获取:执行签名MD5(类,方法,参数)
	 */
	public String getExecSign() {
		return execSign;
	}
	
	/**
	 * 设置:执行签名MD5(类,方法,参数)
	 */
	public void setExecSign(String execSign) {
		this.execSign = execSign;
	}
	    	
	/**
	 * 获取:执行时间
	 */
	public Integer getExecTime() {
		return execTime;
	}
	
	/**
	 * 设置:执行时间
	 */
	public void setExecTime(Integer execTime) {
		this.execTime = execTime;
	}
	    	
	/**
	 * 获取:执行次数
	 */
	public Integer getTryCount() {
		return tryCount;
	}
	
	/**
	 * 设置:执行次数
	 */
	public void setTryCount(Integer tryCount) {
		this.tryCount = tryCount;
	}
	    	
	/**
	 * 获取:是否执行成功(0:否,1:是)
	 */
	public Integer getSuccessFlag() {
		return successFlag;
	}
	
	/**
	 * 设置:是否执行成功(0:否,1:是)
	 */
	public void setSuccessFlag(Integer successFlag) {
		this.successFlag = successFlag;
	}
	    	
	/**
	 * 获取:首次执行时间
	 */
	public Date getFirstTime() {
		return firstTime;
	}
	
	/**
	 * 设置:首次执行时间
	 */
	public void setFirstTime(Date firstTime) {
		this.firstTime = firstTime;
	}
	    	
	/**
	 * 获取:最后执行时间
	 */
	public Date getLastTime() {
		return lastTime;
	}
	
	/**
	 * 设置:最后执行时间
	 */
	public void setLastTime(Date lastTime) {
		this.lastTime = lastTime;
	}
	    	
	/**
	 * 获取:下次重试时间
	 */
	public Date getNextTime() {
		return nextTime;
	}
	
	/**
	 * 设置:下次重试时间
	 */
	public void setNextTime(Date nextTime) {
		this.nextTime = nextTime;
	}
	    	
	/**
	 * 获取:备注
	 */
	public String getRemarks() {
		return remarks;
	}
	
	/**
	 * 设置:备注
	 */
	public void setRemarks(String remarks) {
		this.remarks = remarks;
	}
	                                
}
package com.lx.task.entity;

import java.util.Date;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.lx.conmon.BaseEntity;

/**
 * 
 *类描述:异步任务历史表
 *
 *@Author:liuweiping
 *@date:2021年08月12日
 *@Version:1.0.0
 */
@TableName("t_async_task_history")
public class AsyncTaskHistoryEntity extends BaseEntity {

	private static final long serialVersionUID = 1L;
	/**
	 * ID
	 */
    @TableId(type=IdType.AUTO)
    private Integer id;
		    
    /**
	 * 仓库编码
	 */
    private String locno;
		    
    /**
	 * 任务名称
	 */
    private String name;
		    
    /**
	 * 业务ID
	 */
    private String sid;
		    
    /**
	 * 类
	 */
    private String execBean;
		    
    /**
	 * 方法
	 */
    private String execMethod;
		    
    /**
	 * 参数
	 */
    private String execParams;
		    
    /**
	 * 执行结果
	 */
    private String execResult;
		    
    /**
	 * 执行签名MD5(类,方法,参数)
	 */
    private String execSign;
		    
    /**
	 * 执行时间
	 */
    private Integer execTime;
		    
    /**
	 * 执行次数
	 */
    private Integer tryCount;
		    
    /**
	 * 是否执行成功(0:否,1:是)
	 */
    private Integer successFlag;
		    
    /**
	 * 首次执行时间
	 */
    private Date firstTime;
		    
    /**
	 * 最后执行时间
	 */
    private Date lastTime;
		    
    /**
	 * 下次重试时间
	 */
    private Date nextTime;
		    
    /**
	 * 备注
	 */
    private String remarks;
									    	
	/**
	 * 获取:ID
	 */
	public Integer getId() {
		return id;
	}
	
	/**
	 * 设置:ID
	 */
	public void setId(Integer id) {
		this.id = id;
	}
	    	
	/**
	 * 获取:仓库编码
	 */
	public String getLocno() {
		return locno;
	}
	
	/**
	 * 设置:仓库编码
	 */
	public void setLocno(String locno) {
		this.locno = locno;
	}
	    	
	/**
	 * 获取:任务名称
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * 设置:任务名称
	 */
	public void setName(String name) {
		this.name = name;
	}
	    	
	/**
	 * 获取:业务ID
	 */
	public String getSid() {
		return sid;
	}
	
	/**
	 * 设置:业务ID
	 */
	public void setSid(String sid) {
		this.sid = sid;
	}
	    	
	/**
	 * 获取:类
	 */
	public String getExecBean() {
		return execBean;
	}
	
	/**
	 * 设置:类
	 */
	public void setExecBean(String execBean) {
		this.execBean = execBean;
	}
	    	
	/**
	 * 获取:方法
	 */
	public String getExecMethod() {
		return execMethod;
	}
	
	/**
	 * 设置:方法
	 */
	public void setExecMethod(String execMethod) {
		this.execMethod = execMethod;
	}
	    	
	/**
	 * 获取:参数
	 */
	public String getExecParams() {
		return execParams;
	}
	
	/**
	 * 设置:参数
	 */
	public void setExecParams(String execParams) {
		this.execParams = execParams;
	}
	    	
	/**
	 * 获取:执行结果
	 */
	public String getExecResult() {
		return execResult;
	}
	
	/**
	 * 设置:执行结果
	 */
	public void setExecResult(String execResult) {
		this.execResult = execResult;
	}
	    	
	/**
	 * 获取:执行签名MD5(类,方法,参数)
	 */
	public String getExecSign() {
		return execSign;
	}
	
	/**
	 * 设置:执行签名MD5(类,方法,参数)
	 */
	public void setExecSign(String execSign) {
		this.execSign = execSign;
	}
	    	
	/**
	 * 获取:执行时间
	 */
	public Integer getExecTime() {
		return execTime;
	}
	
	/**
	 * 设置:执行时间
	 */
	public void setExecTime(Integer execTime) {
		this.execTime = execTime;
	}
	    	
	/**
	 * 获取:执行次数
	 */
	public Integer getTryCount() {
		return tryCount;
	}
	
	/**
	 * 设置:执行次数
	 */
	public void setTryCount(Integer tryCount) {
		this.tryCount = tryCount;
	}
	    	
	/**
	 * 获取:是否执行成功(0:否,1:是)
	 */
	public Integer getSuccessFlag() {
		return successFlag;
	}
	
	/**
	 * 设置:是否执行成功(0:否,1:是)
	 */
	public void setSuccessFlag(Integer successFlag) {
		this.successFlag = successFlag;
	}
	    	
	/**
	 * 获取:首次执行时间
	 */
	public Date getFirstTime() {
		return firstTime;
	}
	
	/**
	 * 设置:首次执行时间
	 */
	public void setFirstTime(Date firstTime) {
		this.firstTime = firstTime;
	}
	    	
	/**
	 * 获取:最后执行时间
	 */
	public Date getLastTime() {
		return lastTime;
	}
	
	/**
	 * 设置:最后执行时间
	 */
	public void setLastTime(Date lastTime) {
		this.lastTime = lastTime;
	}
	    	
	/**
	 * 获取:下次重试时间
	 */
	public Date getNextTime() {
		return nextTime;
	}
	
	/**
	 * 设置:下次重试时间
	 */
	public void setNextTime(Date nextTime) {
		this.nextTime = nextTime;
	}
	    	
	/**
	 * 获取:备注
	 */
	public String getRemarks() {
		return remarks;
	}
	
	/**
	 * 设置:备注
	 */
	public void setRemarks(String remarks) {
		this.remarks = remarks;
	}
	                                
}

mapper:

package com.lx.task.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lx.task.entity.AsyncTaskEntity;
import org.apache.ibatis.annotations.Mapper;

/**
 * 
 *类描述:异步任务表
 *
 *@Author:liuweiping
 *@date:2021年04月11日
 *@Version:1.0.0
 */
@Mapper
public interface AsyncTaskDao extends BaseMapper<AsyncTaskEntity> {
	
}




package com.lx.task.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lx.task.entity.AsyncTaskHistoryEntity;
import org.apache.ibatis.annotations.Mapper;


/**
 * 
 *类描述:异步任务历史表
 *
 *@Author:liu wei ping    
 *@date:2019年08月12日
 *@Version:1.0.0
 */
@Mapper
public interface AsyncTaskHistoryDao extends BaseMapper<AsyncTaskHistoryEntity> {
	
}

service:

package com.lx.task.service.impl;


import java.lang.reflect.Method;
import java.util.Date;
import java.util.UUID;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import com.alibaba.fastjson.JSON;
import com.lx.annotation.AsyncTask;
import com.lx.conmon.UserInfo;
import com.lx.task.entity.AsyncTaskEntity;
import com.lx.task.mapper.AsyncTaskDao;
import com.lx.utils.IpUtils;
import com.lx.utils.Md5Utils;


/**
 * 异步任务
 * @author liu wei ping
 */
@Aspect
@Configuration
public class AsyncTaskAspect {

    private static final Logger logger = LoggerFactory.getLogger(AsyncTaskAspect.class);

    @Autowired
    private AsyncTaskExecutor asyncTaskExecutor;

    @Autowired
    private AsyncTaskDao asyncTaskDao;

    @Pointcut("@annotation(com.lx.annotation.AsyncTask)")
    public void asyncTaskPointcut() {
    }

    @Around("asyncTaskPointcut()")
    public Object asyncTaskRunning(ProceedingJoinPoint point) throws Throwable {
        if (asyncTaskExecutor.isJobRunning()) {
            return point.proceed();
        }
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        Class<?> targetClass = point.getTarget().getClass();
        Method thisMethod = targetClass.getMethod(method.getName(), method.getParameterTypes());
        AsyncTask asyncTask = thisMethod.getAnnotation(AsyncTask.class);
        String taskName = asyncTask.value();
        String sid = UUID.randomUUID().toString();
        AsyncTaskEntity entity = new AsyncTaskEntity();
        entity.setName(taskName);
        entity.setExecBean(targetClass.getName());
        entity.setExecMethod(method.getName());
        entity.setExecParams(JSON.toJSONString(point.getArgs()));
        entity.setExecSign(asyncTask.filterRepeatCall() ? Md5Utils.encryption(JSON.toJSONString(entity)) : sid);
        entity.setSid(sid);
        UserInfo dto = UserInfo.getUserInfo();
        entity.setLocno(dto.getLocno());
        entity.setCreateUserId(dto.getCreateUserId());
        entity.setCreateUserName(dto.getCreateUserName());
        entity.setUpdateUserId(dto.getUpdateUserId());
        entity.setUpdateUserName(dto.getCreateUserName());            
        
        entity.setCreateTime(new Date());
        entity.setNextTime(new Date(System.currentTimeMillis() + asyncTask.delay()));
        entity.setTryCount(0);
        entity.setSuccessFlag(0);
        entity.setRemarks(IpUtils.getLocalIP());
        asyncTaskDao.insert(entity);
        return null;
    }

}
package com.lx.task.service.impl;


import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lx.task.entity.AsyncTaskEntity;
import com.lx.task.entity.AsyncTaskHistoryEntity;
import com.lx.task.mapper.AsyncTaskDao;
import com.lx.task.mapper.AsyncTaskHistoryDao;
import com.lx.utils.IpUtils;
import com.lx.utils.RedisLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;


/**
 * 异步任务执行service
 * @author liuweiping
 */
@Service
public class AsyncTaskExecutor {

    private static final Logger logger = LoggerFactory.getLogger(AsyncTaskExecutor.class);

    /**
     * 定时任务调度线程池
     */
    private ScheduledExecutorService mainExecutorService = new ScheduledThreadPoolExecutor(1);

    /**
     * 异步任务执行线程池
     */
    private ExecutorService workExecutorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors() * 2, 10L,
            TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(1024));

    /**
     * 启动后几秒开始检查
     */
    private final static Integer INIT_DELAY_SEC = 30;

    /**
     * 每1分钟检查一次
     */
    private final static Integer PERIOD_SEC = 60;

    /***
     * 最大锁定时间(30分钟) - 任务执行
     */
    private static final int TIMEOUT_FOR_LOCK_JOB_SECONDS = 60 * 30;

    /**
     * 执行失败延迟执行时间
     */
    private static final int TIME_OUT_FOR_NEXT = 1000 * 60 * 1;

    /**
     * 任务最大重试次数
     */
    private static final int MAX_JOB_TRY_COUNT = 5;

    /**
     * 备注字段最大长度
     */
    private int MAX_REMARKS_LEN = 1000;

    /**
     * 标记是否执行任务
     */
    private ThreadLocal<Boolean> jobRunning = new ThreadLocal<>();

    /**
     * 标记是否转移异步任务数据
     */
    private ThreadLocal<Boolean> storeJob = new ThreadLocal<>();

    /**
     * 标记是否转移异步任务数据
     */
    private ThreadLocal<Integer> jobId = new ThreadLocal<>();

    /**
     * spring context
     */
    private final ApplicationContext applicationContext;

    @Value("${spring.application.name}")
    private String appName;

    @Resource
    private AsyncTaskDao asyncTaskDao;

    @Resource
    private AsyncTaskHistoryDao asyncTaskHistoryDao;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    public AsyncTaskExecutor(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void init() {
        mainExecutorService.scheduleAtFixedRate(() -> {selectJobs();},
                INIT_DELAY_SEC, PERIOD_SEC, TimeUnit.SECONDS);
    }

    public boolean isJobRunning() {
        return null != jobRunning.get() && jobRunning.get();
    }

    public Integer getJobId() {
        return jobId.get();
    }

    private void selectJobs() {
        try {
       	
            QueryWrapper<AsyncTaskEntity> wrapper = new QueryWrapper<AsyncTaskEntity>();           		
            wrapper.lt("next_time", new Date());
            wrapper.eq("success_flag", 0);
            wrapper.le("try_count", MAX_JOB_TRY_COUNT);
            wrapper.last(" limit 0,20");
            List<AsyncTaskEntity> jobs = asyncTaskDao.selectList(wrapper);
            if (jobs != null && jobs.size() > 0) {
                jobs.forEach(item -> {
                    workExecutorService.submit(() -> {
                        doSingleJobWithLock(item);
                    });
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doSingleJobWithLock(AsyncTaskEntity job) {
        String lockKey = appName + ":async:lock:job:" + job.getId();
        logger.info("doSingleJob , lock key {} ,id:{} ", lockKey, job.getId());
        RedisLock lock = new RedisLock(stringRedisTemplate, lockKey);
        if (lock.tryLock(TIMEOUT_FOR_LOCK_JOB_SECONDS)) {
            try {
                // 防止重复执行
                QueryWrapper<AsyncTaskEntity> wrapper = new QueryWrapper<AsyncTaskEntity>();           		
                wrapper.lt("next_time", new Date());
                wrapper.eq("success_flag", 0);
                wrapper.le("try_count", MAX_JOB_TRY_COUNT);
                wrapper.eq("id", job.getId());
                List<AsyncTaskEntity> asyncTaskEntityList = asyncTaskDao.selectList(wrapper);
                if (CollectionUtils.isNotEmpty(asyncTaskEntityList)) {
                    execSingleJob(asyncTaskEntityList.get(0));
                } else {
                    logger.info("doSingleJob already run id:{} ", job.getId());
                }
            } finally {
                lock.unlock();
            }
        } else {
            logger.info("doSingleJob , lock failed for key {} ,id:{} ", lockKey, job.getId());
        }
    }

    private void execSingleJob(AsyncTaskEntity job) {
        jobRunning.set(true);
        storeJob.set(false);
        jobId.set(job.getId());
        logger.info("exec async job id {} , sid:{} , name {} , sign {} ", job.getId(), job.getSid(),
                job.getName(), job.getExecSign());
        Date startTime = new Date();
        try {
            if (job.getFirstTime() == null) {
                job.setFirstTime(startTime);
            }
            job.setLastTime(startTime);
            job.setTryCount(job.getTryCount() + 1);
            JSONArray paramArray = JSON.parseArray(job.getExecParams());
            Class<?> clazz = Class.forName(job.getExecBean());
            Object bean = applicationContext.getBean(clazz);
            Method method = null;
            for (Method m : clazz.getMethods()) {
                if (m.getName().equals(job.getExecMethod())
                        && m.getParameterCount() == paramArray.size()) {
                    method = m;
                    break;
                }
            }
            Class<?>[] parameterTypes = method.getParameterTypes();
            int paramsLen = paramArray.size();
            Object result = null;
            if (null != parameterTypes && 0 != paramsLen) {
                Object[] params = new Object[paramsLen];
                Type[] types = method.getGenericParameterTypes();
                for (int i = 0; i < paramsLen; i++) {
                    params[i] = caseParam(paramArray, parameterTypes[i], types[i], i);
                }
                result = method.invoke(bean, params);
            } else {
                result = method.invoke(bean);
            }
            job.setExecResult(JSON.toJSONString(result));
            job.setRemarks(IpUtils.getLocalIP() + "执行成功");
            job.setSuccessFlag(1);
            job.setExecTime((int) (System.currentTimeMillis() - startTime.getTime()));
            storeJob(job);
        } catch (Exception e) {
            logger.error("exec async job failed job-id-" + job.getId() + ", sid " + job.getSid()
                    + " " + e.getMessage(), e);
            job.setNextTime(new Date(System.currentTimeMillis() + TIME_OUT_FOR_NEXT * job.getTryCount()));
            Throwable cause = e.getCause() != null ? e.getCause() : e;
            job.setRemarks(IpUtils.getLocalIP() + "执行失败:" + cause.toString());
            if (job.getRemarks().length() > MAX_REMARKS_LEN) {
                job.setRemarks(job.getRemarks().substring(0, MAX_REMARKS_LEN));
            }
            asyncTaskDao.updateById(job);
        } finally {
            jobRunning.remove();
            storeJob.remove();
            jobId.remove();
        }
    }

    public void storeJob(AsyncTaskEntity job) {
        if (Boolean.FALSE.equals(storeJob.get())) {
            if (null != job.getId()) {
                asyncTaskDao.deleteById(job.getId());
            }
            AsyncTaskHistoryEntity history = new AsyncTaskHistoryEntity();
            BeanUtils.copyProperties(job, history);
            history.setId(null);
            asyncTaskHistoryDao.insert(history);
            storeJob.set(true);
        }
    }

    private Object caseParam(JSONArray paramArray, Class<?> ctype, Type type, int index) {
        if (ctype.isAssignableFrom(List.class) && type instanceof ParameterizedTypeImpl) {
            ParameterizedTypeImpl typeImpl = (ParameterizedTypeImpl) type;
            return JSON.parseArray(JSON.toJSONString(paramArray.get(index)),
                    (Class) typeImpl.getActualTypeArguments()[0]);
        } else {
            return paramArray.getObject(index, ctype);
        }
    }

}
package com.lx.task.service.impl;

import java.util.Date;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import com.alibaba.fastjson.JSON;
import com.lx.task.entity.AsyncTaskEntity;
import com.lx.task.mapper.AsyncTaskDao;
import com.lx.utils.IpUtils;

/**
 * 异步任务
 * @author liu wei ping
 */
@Aspect
@Configuration
public class TransactionalAspect {

    private static final Logger logger = LoggerFactory.getLogger(TransactionalAspect.class);

    @Autowired
    private AsyncTaskExecutor asyncTaskExecutor;

    @Autowired
    private AsyncTaskDao asyncTaskDao;

    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalPointcut() {
    }

    @Around("transactionalPointcut()")
    public Object transactionalRunning(ProceedingJoinPoint point) throws Throwable {
        if (asyncTaskExecutor.isJobRunning()) {
            Date startTime = new Date();
            Object result = point.proceed();
            AsyncTaskEntity job = asyncTaskDao.selectById(asyncTaskExecutor.getJobId());
            job.setExecResult(JSON.toJSONString(result));
            job.setRemarks(IpUtils.getLocalIP() + " 执行成功");
            job.setSuccessFlag(1);
            job.setExecTime((int) (System.currentTimeMillis() - startTime.getTime()));
            asyncTaskExecutor.storeJob(job);
            return result;
        } else {
            return point.proceed();
        }
    }

}

sql脚本:

CREATE TABLE `t_async_task` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `locno` varchar(10) NOT NULL DEFAULT '' COMMENT '仓库编码',
  `name` varchar(500) NOT NULL DEFAULT '' COMMENT '任务名称',
  `sid` varchar(50) NOT NULL DEFAULT '' COMMENT '业务ID',
  `exec_bean` varchar(500) NOT NULL DEFAULT '' COMMENT '类',
  `exec_method` varchar(500) NOT NULL DEFAULT '' COMMENT '方法',
  `exec_params` longtext COMMENT '参数',
  `exec_result` varchar(3000) DEFAULT '' COMMENT '执行结果',
  `exec_sign` varchar(50) NOT NULL DEFAULT '' COMMENT '执行签名MD5(类,方法,参数)',
  `exec_time` int(11) NOT NULL DEFAULT '0' COMMENT '执行时间',
  `try_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行次数',
  `success_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否执行成功(0:否,1:是)',
  `first_time` datetime DEFAULT NULL COMMENT '首次执行时间',
  `last_time` datetime DEFAULT NULL COMMENT '最后执行时间',
  `next_time` datetime NOT NULL COMMENT '下次重试时间',
  `remarks` varchar(3000) NOT NULL DEFAULT '' COMMENT '备注',
  `trace_id` varchar(128) DEFAULT NULL COMMENT '日志跟踪id',
  `create_user_id` varchar(50) DEFAULT NULL COMMENT '创建人ID',
  `create_user_name` varchar(50) DEFAULT NULL COMMENT '创建人名称',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_user_id` varchar(50) DEFAULT NULL COMMENT '更新人ID',
  `update_user_name` varchar(50) DEFAULT NULL COMMENT '更新人名称',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标识(0-正常,1-删除)',
  PRIMARY KEY (`id`),
  KEY `next_time` (`next_time`),
  KEY `sid` (`sid`),
  KEY `success_flag` (`success_flag`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='异步任务表';




CREATE TABLE `t_async_task_history` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `locno` varchar(10) NOT NULL DEFAULT '' COMMENT '仓库编码',
  `name` varchar(500) NOT NULL DEFAULT '' COMMENT '任务名称',
  `sid` varchar(50) NOT NULL DEFAULT '' COMMENT '业务ID',
  `exec_bean` varchar(500) NOT NULL DEFAULT '' COMMENT '类',
  `exec_method` varchar(500) NOT NULL DEFAULT '' COMMENT '方法',
  `exec_params` longtext COMMENT '参数',
  `exec_result` varchar(3000) DEFAULT '' COMMENT '执行结果',
  `exec_sign` varchar(50) NOT NULL DEFAULT '' COMMENT '执行签名MD5(类,方法,参数)',
  `exec_time` int(11) NOT NULL DEFAULT '0' COMMENT '执行时间',
  `try_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行次数',
  `success_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否执行成功(0:否,1:是)',
  `first_time` datetime DEFAULT NULL COMMENT '首次执行时间',
  `last_time` datetime DEFAULT NULL COMMENT '最后执行时间',
  `next_time` datetime NOT NULL COMMENT '下次重试时间',
  `remarks` varchar(3000) NOT NULL DEFAULT '' COMMENT '备注',
  `trace_id` varchar(128) DEFAULT NULL COMMENT '日志跟踪id',
  `create_user_id` varchar(50) DEFAULT NULL COMMENT '创建人ID',
  `create_user_name` varchar(50) DEFAULT NULL COMMENT '创建人名称',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_user_id` varchar(50) DEFAULT NULL COMMENT '更新人ID',
  `update_user_name` varchar(50) DEFAULT NULL COMMENT '更新人名称',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标识(0-正常,1-删除)',
  PRIMARY KEY (`id`),
  KEY `next_time` (`next_time`),
  KEY `sid` (`sid`),
  KEY `success_flag` (`success_flag`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='异步任务表';

测试如下:

 

这样就实现了分布式事务失败补偿机制,当重试6次失败,则需要人为去处理失败异常!

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java分布式事务指的是在分布式系统对多个数据库或服务进行操作时,保证数据一致性和事务的原子性。Java提供了多种方式来实现分布式事务,下面介绍几种常见的方式: 1. 两阶段提交(2PC):在分布式事务,协调者负责协调所有参与者的操作,通过两个阶段来实现事务的提交。第一阶段,协调者询问所有参与者是否可以提交事务,参与者将结果反馈给协调者。第二阶段,协调者根据参与者的反馈决定是否提交或回滚事务。 2. 补偿事务(TCC):TCC是一种基于补偿机制分布式事务解决方案。它将一个大的事务拆分为多个小的事务,并为每个小事务定义了Try、Confirm和Cancel三个阶段。通过执行相应的补偿操作,可以实现最终的一致性。 3. 消息队列:使用消息队列作为分布式事务间件,将事务操作封装成消息发送到队列,并由消费者进行处理。当所有操作都成功完成时,提交事务;如果有一个或多个操作失败,则回滚事务。 4. 分布式数据库:通过使用支持分布式事务的数据库,如MySQL Cluster、PostgreSQL等,可以实现跨多个数据库的事务操作。这些数据库提供了事务一致性和隔离级别的保证。 需要注意的是,分布式事务在实现过程需要考虑网络延迟、单点故障、数据冲突等问题,因此在设计分布式系统时需谨慎权衡各种方案,并根据具体业务需求选择适合的分布式事务解决方案
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值