背景:

我们的组件(简称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)public@interface 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*/@Slf4j

@Aspect

@Componentpublic 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 exceptionLog=convert(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 Class>targetClass;/*** 参数信息*/

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接口调用方法

@Override

@ExceptionCollect(beanName= "xxxService")

@Retryable(value= {BusinessException.class}, maxAttempts = 5, backoff = @Backoff(delay = 5000, multiplier = 2))publicvoid test(TestBean person) {

String url=getUrl();//api完整请求路径String result;try{

result=HttpUtils.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 exception,code={},msg={}", result.getCode(), result.getMsg());throw newBusinessException(ErrorCodeEnum.ADD_PERSON_EXCEPTION.getCode());

}

}

在这里,标注了@ExceptionCollect的方法test()会在exceptionPointCut()方法签名的切入点被切入

如果执行中抛出异常,由AspectService 类中,标注了@AfterThrowing的afterThrowing()方法来处理异常要做的逻辑

这里我们对异常的执行做了四种状态:-100(失败),-1(取消),0(待重试),100(成功)

初次入库的记录均为待重试(0),在重试了若干次仍失败后改为-100,成功改为100

@Retryable,定义改方法如果抛出异常,自动重试,最大重试5次,下一次重试执行与上一次间隔按倍数(2)增加,5s,10s,20s.......重试

7、ExceptionWorker线程轮询补偿调用

/*** description 接口调用异常work线程补偿

* 服务启动后定时扫描t_exception_log表status=0的记录

* 间隔5分钟*/@Component

@Slf4jpublic classExceptionWorkerTask {

@AutowiredprivateExceptionLogService exceptionLogService;

@AutowiredprivatexxxService xxxService;/*** 任务只重试status=0的

* 每隔5分钟一次,每次每条记录重试1次*/@Scheduled(initialDelay= 10000, fixedDelay = 300000)public voidretry() {

List list =exceptionLogService.getRetryList();for(ExceptionLog e : list) {

String methodName=e.getMethodName();

String jsonArgs=e.getJsonArgs();

JSONObject argsJo=JSON.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、重试的机制还可以设计得更复杂点,初步设计是有人工重试的情景

java 异常补偿解决_第三方接口调用异常补偿机制实现实例记录相关推荐

  1. JAVA 油站管理系统_基于JAVA的全国加油站[实时油价]接口调用代码实例

    代码描述:基于JAVA的全国加油站[实时油价]接口调用代码实例 接口地址:https://www.juhe.cn/docs/api/id/7 1.[代码][Java]代码 import java.io ...

  2. 获得周公解梦数据接口java_基于JAVA的免费周公解梦接口调用代码实例

    代码描述:基于JAVA的免费周公解梦接口调用代码实例 接口地址:http://www.juhe.cn/docs/api/id/64 1.[代码][Java]代码 import java.io.Buff ...

  3. java runtime异常如何解决_成都汇智动力-Java中常见的RunTime异常及异常详解

    java.lang.ArithmeticException算术条件异常.譬如:整数除零等.java.lang.ArrayIndexOutOfBoundsException数组索引越界异常.当对数组的索 ...

  4. Android+Java中使用RSA加密实现接口调用时的校验功能

    场景 RSA加密 RSA算法是一种非对称加密算法,那么何为非对称加密算法呢? 一般我们理解上的加密是这样子进行的:原文经过了一把钥匙(密钥)加密后变成了密文,然后将密文传递给接收方,接收方再用这把钥匙 ...

  5. Java Web系统常用的第三方接口

    1.    Web Service 接口 1.1 接口方式说明和优点 在笔者的开发生涯中,当作为接口提供商给第三方提供接口时,以及作为客户端去调用第三方提供的接口时,大部分时候都是使用 Web  Se ...

  6. ESB接口调用异常汇总

    文章目录 一.ESB接口前置知识 1. ESB接口简述 2. 生成的代码组成部分 二.常见的异常汇总 2.1. 场景1:不能解析某域名 2.2. 场景2:调用服务连接超时 三.调用服务前异常 3.1. ...

  7. 网易云信第三方接口调用超详细Demo

    ** 正文 直接进入正题 ** //单人通知推送private static String PUSH_INFORM_URL="https://api.netease.im/nimserver ...

  8. 与第三方接口调用时白名单功能

    近一年来一直做与其他方做接口方面的工作.做接口肯定就会涉及到一些白名单.授权.验证方面的问题.这里看一下白名单问题. 做过一个dll插件的接口,方式是我们把此dll发给第三方,第三方将此dll放到他们 ...

  9. java叮咚云短信验证码接口调用

    最近在写后台,所以有些方法想记录下来,下面是叮咚云短信验证码接口调用,叮咚云的地址http://www.dingdongcloud.com/,里面有详细的信息,下面是我写的一个方法,仅供参考. imp ...

最新文章

  1. 中国财团收购飞利浦照明业务遭美封杀
  2. 我的mongo学习之路
  3. 【算法】《algorithm-note》算法笔记中文版正式发布!
  4. div高度、宽度100% div width、height 100%
  5. GNU make 与 override指令
  6. Android开发之如何在debug模式下打出release正式包
  7. 数据结构-图的进阶代码
  8. Snagit 2021 for mac截图工具 v2021.4.2(98044)汉化版
  9. Expression Blend4 中文
  10. linux故障解决-centos7删除冲突的包
  11. cvCompareHist() 直方图匹配
  12. 易语言编写影视大全的整体思路及ACF浏览器和cheni纯组件列表灵活运用
  13. intel 9260AC网卡修改成Killer 1550
  14. WPS 2019解决不能启用宏问题
  15. 华三路由交换配置命令_华三交换机-路由器配置命令
  16. CSDN公式插入——关于对数
  17. 生物医学数据大爆炸,大数据“挖掘机”哪家强?
  18. 搞笑git 程序员改bug_这些神奇又搞笑的bug,真的让程序员万万没想到!
  19. Fluentd (td-agent) 日志处理
  20. 什么是RPC?RPC好处?常用的RPC框架?

热门文章

  1. html怎样实现数据列表的下拉效果
  2. Mobius函数计算 定义+代码模板
  3. 邻近算法(KNN算法)
  4. 线程和std::shared_ptr智能指针使用
  5. Alibaba Cloud Toolkit 中SLS插件助力线上服务问题排查
  6. 技术干货 | mPaaS 框架下如何使用 Crash SDK 对闪退进行分析?
  7. 阿里云移动研发平台EMAS,是如何连续5年安全护航双11的?
  8. NLP深度学习:近期趋势概述(二)
  9. 基于TableStore构建简易海量Topic消息队列
  10. 数据结构是如何装入 CPU 寄存器的?