广东省自然资源厅测绘院,关键词优化公司排名榜,wordpress怎么连接数据库配置文件,怎么查网站建设是哪家公司背景#xff1a;我们的组件(简称A)#xff0c;在业务链中属于数据支撑节点。其中与组件B存在接口同步数据的直接关系(API接口直接调用进行数据交互)问题#xff1a;我们的上游有另一个组件C(带有界面)#xff0c;调用A(us)进行数据的变更操作#xff0c;此时需要A调用B服务…背景我们的组件(简称A)在业务链中属于数据支撑节点。其中与组件B存在接口同步数据的直接关系(API接口直接调用进行数据交互)问题我们的上游有另一个组件C(带有界面)调用A(us)进行数据的变更操作此时需要A调用B服务接口进行同步问题出在这里C调用A通常速度比较快比较稳定但是A调用B经常超时或者失败网络原因or 组件B自己的设计原因吧反正是推不动方案经沟通考察这条数据的变更在可接受的时间范围只要最终一致即可于是首先我们先将事物中的调用B服务的一系列逻辑抽出来做成异步的让C操作数据后能及时返回在这个异步调用B服务接口同步过程中出现异常时自动记录这次接口调用失败的日志再开一个Worker线程任务去轮询调用设计1、第三方接口调用中涉及增删改的逻辑脱离事物管理异步执行2、接口调用后出现异常记录下该方法调用的详细日志到数据库表中3、开启一个单独的任务轮询改表中待重试状态的记录依次重试重试成功或失败均更改状态对于重试若干次仍然失败的界面上展示通知排查实现接口的异步调用就不讲了springboot的异步方案很多这里主要讲异常日志如何自动入库并提供补偿1、日志获取思路(1)调用B服务的api接口异常时需要抛出具体的异常这里假设叫BusinessException,该异常继承RuntimeException,异常中可以带出错误码错误描述等信息(2)自定义收集日志的注解作用在方法上收集日志(3)异常信息入库注意使用摘要加密保证唯一性2、单独开启一个线程处理收集的状态为待重试的记录对调用失败的进行retry编码1、自定义注解import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** 自定义注解-处理调用第三方接口数据交互异常时日志收集* * RetentionPolicy.RUNTIME JVM在运行期也保留注解可以通过反射机制读取注解信息* ElementType.METHOD 方法上生效*/Retention(RetentionPolicy.RUNTIME)Target(ElementType.METHOD)publicinterface ExceptionCollect {String value()default ;String beanName()default ;}自定义的属性可自行调整2、定义注解的方法签名importorg.aspectj.lang.annotation.Pointcut;/*** description 公用的pointCut* 定义各个pointCut方法签名*/public classPointCuts {/*** 切入点:注解ExceptionCollect*/Pointcut(annotation(com.xxx.config.aop.ExceptionCollect))public voidexceptionPointCut() {}}3、定义具体的切面类执行逻辑/*** description 切面类* 打印日志采集异常日志入库*seeExceptionCollect*/Slf4jAspectComponentpublic classAspectService {AutowiredprivateExceptionLogService exceptionLogService;/*** 方法上注解ExceptionCollect抛出异常后将收集日志信息入库**parampoint 切面*parame 抛出的异常*/AfterThrowing(value com.xxx.config.aop.PointCuts.exceptionPointCut(), throwing e)public voidafterThrowing(JoinPoint point, BusinessException e) {MethodInfo methodInfo newMethodInfo(point);ExceptionLog exceptionLogconvert(methodInfo, e);String beanName methodInfo.getMethod().getAnnotation(ExceptionCollect.class).beanName();exceptionLog.setBeanName(beanName);//保证幂等性ExceptionLog oldLog exceptionLogService.findById(exceptionLog.getId());//新记录为待重试if (oldLog null) {exceptionLog.setRetryCount(1);exceptionLog.setStatus(ExceptionStatusEnum.RETRY.getType());exceptionLogService.create(exceptionLog);}else{//已有记录说明是重试后仍失败oldLog.setRetryCount(oldLog.getRetryCount() 1);oldLog.setStatus(ExceptionStatusEnum.FAIL.getType());oldLog.setUpdateTime(CommonUtils.localDateTimeNow());exceptionLogService.update(oldLog);}}/***parammethodInfo*parame*return*/privateExceptionLog convert(MethodInfo methodInfo, BusinessException e) {methodInfo.init();ExceptionLog exceptionLog newExceptionLog();exceptionLog.setClassName(methodInfo.getClassName());exceptionLog.setMethodName(methodInfo.getMethodName());exceptionLog.setJsonArgs(methodInfo.getJsonArgs());exceptionLog.setErrorCode(e.getCode());exceptionLog.setErrorMsg(e.getMessage());exceptionLog.setCreateTime(CommonUtils.localDateTimeNow());exceptionLog.setUpdateTime(CommonUtils.localDateTimeNow());//唯一键String id Md5Util.getStrMD5(methodInfo.getClassName() methodInfo.getMethodName() methodInfo.getJsonArgs());exceptionLog.setId(id);returnexceptionLog;}}4、补上异常日志的实体importlombok.Data;importjava.io.Serializable;importjava.time.LocalDateTime;/*** description 接口异常日志实体*/Datapublic class ExceptionLog implementsSerializable {private static final long serialVersionUID 1L;/*** 根据类名方法名入参生成一个摘要值*/privateString id;/*** 类型(定义处理方式定时补偿或人工补偿)*/privateInteger type;/*** 处理状态*/privateInteger status;/*** 重试次数*/privateInteger retryCount;/*** 错误码*/privateString errorCode;/*** 错误描述*/privateString errorMsg;/*** 完整类名*/privateString className;/*** bean名*/privateString beanName;/*** 方法名*/privateString methodName;/*** 方法入参*/privateString jsonArgs;/*** 备注*/privateString remark;/*** 创建时间*/privateLocalDateTime createTime;/*** 修改时间*/privateLocalDateTime updateTime;}5、辅助类packagecom.hikvision.idatafusion.dglist.config.aop;importcom.alibaba.fastjson.JSONObject;importcom.alibaba.fastjson.serializer.SerializerFeature;importlombok.Data;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.reflect.MethodSignature;importjava.lang.reflect.Method;/*** description 接口方法信息* aop中获取某个方法的参数信息*/Datapublic classMethodInfo {/*** 切入点信息*/privateJoinPoint joinPoint;/*** 方法签名*/privateMethodSignature signature;/*** 方法信息*/privateMethod method;/*** 类信息*/private ClasstargetClass;/*** 参数信息*/privateObject[] args;/*** 参数信息String*/privateString jsonArgs;/*** 类名*/privateString className;/*** 方法名*/privateString methodName;publicMethodInfo(JoinPoint joinPoint) {this.joinPoint joinPoint;}public voidinit() {this.signature (MethodSignature) joinPoint.getSignature();this.method signature.getMethod();this.methodName method.getName();this.targetClass method.getDeclaringClass();this.className targetClass.getName();this.args joinPoint.getArgs();this.jsonArgs JSONObject.toJSONString(args, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty);}}接下来我们在具体的调用外部API接口的方法上加上注解ExceptionCollect(beanName xxxService)6、api接口调用方法OverrideExceptionCollect(beanName xxxService)Retryable(value {BusinessException.class}, maxAttempts 5, backoff Backoff(delay 5000, multiplier 2))publicvoid test(TestBean person) {String urlgetUrl();//api完整请求路径String result;try{resultHttpUtils.post(url, param, header).body();}catch(HttpException e) {log.error(post API test failed!, e);throw newBusinessException(ErrorCodeEnum.API_INTERFACE_EXCEPTION.getCode());}//解析请求结果的逻辑简化如下Result result JSON.parseObject(result, new TypeReference() {});if(ErrorCodeEnum.SUCCESS.getCode().equals(result.getCode())) {log.info(XFACE add person success!);}else{log.info(call API:test exceptioncode{}msg{}, result.getCode(), result.getMsg());throw newBusinessException(ErrorCodeEnum.ADD_PERSON_EXCEPTION.getCode());}}在这里标注了ExceptionCollect的方法test()会在exceptionPointCut()方法签名的切入点被切入如果执行中抛出异常由AspectService 类中标注了AfterThrowing的afterThrowing()方法来处理异常要做的逻辑这里我们对异常的执行做了四种状态-100(失败)-1(取消)0(待重试)100(成功)初次入库的记录均为待重试(0)在重试了若干次仍失败后改为-100成功改为100Retryable,定义改方法如果抛出异常自动重试最大重试5次下一次重试执行与上一次间隔按倍数(2)增加5s,10s,20s.......重试7、ExceptionWorker线程轮询补偿调用/*** description 接口调用异常work线程补偿* 服务启动后定时扫描t_exception_log表status0的记录* 间隔5分钟*/ComponentSlf4jpublic classExceptionWorkerTask {AutowiredprivateExceptionLogService exceptionLogService;AutowiredprivatexxxService xxxService;/*** 任务只重试status0的* 每隔5分钟一次每次每条记录重试1次*/Scheduled(initialDelay 10000, fixedDelay 300000)public voidretry() {List list exceptionLogService.getRetryList();for(ExceptionLog e : list) {String methodNamee.getMethodName();String jsonArgse.getJsonArgs();JSONObject argsJoJSON.parseObject(jsonArgs);xxxService.test(argsJo);//如果调用成功更新状态为e.setRetryCount(e.getRetryCount()1);e.setStatus(ExceptionStatusEnum.CONFIRM.getType());e.setUpdateTime(CommonUtils.localDateTimeNow());exceptionLogService.update(e);}}}设计缺点1、不通用业务耦合比较强2、ExceptionLog的定义还待改进3、重试的机制还可以设计得更复杂点初步设计是有人工重试的情景