最近在做一个业务流系统,部署在分布式环境下,使用到分布式调度系统,一是为了在系统宕机,系统发布的时候任务能够在可用的机器上重启重新;二是任务需要重试,等接口执行失败之后需要设置调度系统的间隔时间在业务集群上重启启动任务(业务的线程和进程都可能发生变化),因为业务流中的业务接口都是有影响或者不幂等比如关闭应用,这条命令就不能重复执行。
这就要求每个重试/重启单元是幂等,我的思路是能够记录每个外部接口的执行过程,要是某个接口已经执行过某个步骤之后跳过这个阶段,保证命令不会重复下达。
每个业务接口我把它分成3个阶段,命令下达(下达成功、下达失败),命令执行成功(执行承成功、执行失败),命令结果入库成功(入库成功、入库失败),基本模型
public class IdempotentFlag { private Boolean commandSendDown = false; // 2 未知(对于异步任务,命令下达成功,此状态下需要不停检测) 0 失败 1 成功 private int commandSuccess = 2; private Boolean commandResultInDB = false; // 任务的附加信息,比如异步任务返回的TaskID private String attachInfo; }
每个幂等对象都有一个Key,在整个流程是唯一的,幂等对象的入库,我使用的是Enhance动态代理,这样代理对象的获得:若数据库存在从数据库查取数据初始化对象,若是数据库不存在构造默认对象。
public class IdempotentFlagFroxyFactory implements MethodInterceptor { private String contextKey; public IdempotentFlagFroxyFactory(String contextKey) { this.contextKey = contextKey; } public IdempotentFlag getProxy() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(IdempotentFlag.class); enhancer.setCallback(this); IdempotentFlag clearObject = (IdempotentFlag) enhancer.create(); String IdempotentFlagString = getFromDB(contextKey); if (StringUtils.isNotBlank(IdempotentFlagString)) { IdempotentFlag dbObject = IdempotentFlag.toObject(IdempotentFlagString); try { BeanUtils.copyProperties(clearObject, dbObject); } catch (Exception e) { e.printStackTrace(); } } return clearObject; } @Override public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object result = proxy.invokeSuper(target, args); if (method.getName().contains("set")) { IdempotentFlag flag = (IdempotentFlag) target; context.putPersistentString(contextKey, flag.toString()); } return result; } }
在外部接口周围保存阶段再做一个状态机就可以,保证任务的幂等,不会重复下发,并且失败之后还可以重试。