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

背景:

我们的组件(简称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、重试的机制还可以设计得更复杂点,初步设计是有人工重试的情景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/520210.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里重磅开源Blink:为什么我们等了这么久?

12月20日,由阿里巴巴承办的 Flink Forward China 峰会在北京国家会议中心召开,来自阿里、华为、腾讯、美团点评、滴滴、字节跳动等公司的技术专家与参会者分享了各公司基于 Flink 的应用和实践经验。 感兴趣的开发者可以看云栖社区的对于大会的主会5场分…

支持OpenStack,红帽将开源进行到底

戳蓝字“CSDN云计算”关注我们哦!作者 | 刘丹责编 | 阿秃出品 | CSDN云计算(ID:CSDNcloud)早在去年,红帽全球副总裁兼中国区总裁曹衡康表示,红帽已经转型了,不仅是Linux,红帽是全球最…

GAN是一种特殊的损失函数?

数据科学家Jeremy Howard在fast.ai的《生成对抗网络(GAN)》课程中曾经讲过这样一句话: “从本质上来说,生成对抗网络(GAN)是一种特殊的损失函数。” 你是否能够理解这句话的意思?读完本文&…

matlab 三维 作图 坐标轴_这张图(不全),想利用matlab画一张三维图,X Y z 轴分别为经度 纬度 频率,这...

xrangeminx:dx:maxx; yrangeminy:dy:maxy;[X,Y] meshgrid(xrange,yrange);griddata(lon,lat,SST,X,Y);mesh(X,Y,Z), hold onplot3(lon,lat,SST,o),hold offmatlab 作图方法2113:plot3 三维曲线图;plot3(x1,y1,z1,x2,y2,z2,…,xn,yn,zn): surf(x,y,z)…

(Python)零起步数学+神经网络入门

在这篇文章中,我们将在Python中从头开始了解用于构建具有各种层神经网络(完全连接,卷积等)的小型库中的机器学习和代码。最终,我们将能够写出如下内容: 假设你对神经网络已经有一定的了解,这篇文…

必须要看的网上冲浪安全攻略!

戳蓝字“CSDN云计算”关注我们哦!作者 | Nirnay Butle翻译|风车云马责编 | 阿秃出品 | CSDN云计算(ID:CSDNcloud)不管我们是不是技术迷,无可否认的是,现在我们各自的生活都对互联网产生了高度依…

当自己犹豫时,坐下来读一读

物来顺应:事情已经过去了,就不要纠结,应该顺应和面对 未来不迎:千万不要为了没有发生的事,而感到焦虑 当时不杂:专注于做好当前的事,不要三心二意 既过不恋:已经过去的事情&#…

java ajax查询_java-如何计时ajax查询(发送查询,处理,接收响应)

编辑以澄清我的意图:(基于初始答案)我有一个网络应用程序.该服务器由一组Java POJO组成,我正在使用Jersey将它们公开为REST API.浏览器使用jquery ajax调用这些API,并执行操作.我想记录我的Ajax查询所用的持续时间,并希望细分>将查询从浏览器发送到服务器花费了多…

短视频宝贝=慢?阿里巴巴工程师这样秒开短视频

前言 随着短视频兴起,各大APP中短视频随处可见,feeds流、详情页等等。怎样让用户有一个好的视频观看体验显得越来越重要了。大部分feeds里面滑动观看视频的时候,有明显的等待感,体验不是很好。针对这个问题我们展开了一波优化&am…

AI又被彩虹吹?!新浪财经:应届博士算法毕业,80万年薪被疯抢

在2019人工智能计算大会上发布了《2019—2020中国人工智能计算力发展评估报告》。根据报告,预计2023年,中国人工智能基础架构市场将超过80亿美元。人工智能市场布局火力全开巨头哭诉:全球只有30万人才截至2017年,我国人工智能市场…

Haproxy 管控台介绍

Queue 队列 简称全称说明Curcurrent queued requests当前的队列请求数量Maxmax queued requests最大的队列请求数量Limit队列限制数量 Session rate (每秒的连接回话)列表 简称全称说明scurcurrent sessions每秒的当前回话的限制数量smaxmax sessions每秒的新的最大的回话量s…

阿里云时空数据库引擎HBase Ganos上线,场景、功能、优势全解析

随着全球卫星导航定位系统、传感网、移动互联网、IoT等技术的快速发展,越来越多的终端设备连接至网络,由此产生了大规模的时空位置信息,如车辆轨迹、个人轨迹、群体活动、可穿戴设备时空位置等。这些数据具有动态变化(数据写入频繁…

云栖专辑|阿里开发者们的第二个感悟:PG大V德哥的使命感与开放心态

2015年12月20日,云栖社区上线。2018年12月20日,云栖社区3岁。 阿里巴巴常说“晴天修屋顶”。 在我们看来,寒冬中,最值得投资的是学习,是增厚的知识储备。 所以社区特别制作了这个专辑——分享给开发者们20个弥足珍贵的…

【经典必看】14个实用的数据库设计技巧

戳蓝字“CSDN云计算”关注我们哦!作者 | 程序IT圈责编 | 阿秃1. 原始单据与实体之间的关系可以是一对一、一对多、多对多的关系。在一般情况下,它们是一对一的关系:即一张原始单据对应且只对应一个实体。在特殊情况下,它们可能是一…

VS Code 全局配置

文章目录1. settings.json2. 在项目根目录添加.eslintrc.js3. 在项目根目录添加.prettierrc.json1. settings.json ctrlshirtp 搜索settings.json替换为下面内容即可 {// 主题颜色 浅色主题"workbench.colorTheme": "Monokai","workbench.iconTheme…

云栖专辑 | 阿里开发者们的第3个感悟:从身边开源开始学习,用过才能更好理解代码

2015年12月20日,云栖社区上线。2018年12月20日,云栖社区3岁。 阿里巴巴常说“晴天修屋顶”。 在我们看来,寒冬中,最值得投资的是学习,是增厚的知识储备。 所以社区特别制作了这个专辑——分享给开发者们20个弥足珍贵的…

个人帐目管理系统java_Java 项目 个人帐目管理系统

目录第一部分项目描述 31.1项目目的 3第二部分需求和开发环境 32.1使用技术和开发环境 32.2项目需求 32.3详细功能 32.4 E-R图 32.5数据库的设计 32.5.1数据表的设计 32.5.2数据库约束的设计 42.5.3数据库序列的设计 42.5.4数据库索引的设计 42.5.5数据库视图的设计 52.5.6数据…

Visual Studio Code

文章目录一、基础配置1. 中文界面2. 高效插件3. vue代码片段4. 全局配置5. VSCode 常用快捷键6. VSCode高级操作技巧一、基础配置 1. 中文界面 中文界面 2. 高效插件 VSCode 高效必备开发插件 3. vue代码片段 Visual Studio Code Vue代码片段 4. 全局配置 Visual Studi…

KubeCon 2018 参会记录 —— FluentBit Deep Dive

在最近的上海和北美KubeCon大会上,来自于Treasure Data的Eduardo Silva(Fluentd Maintainer)带来了最期待的关于容器日志采集工具FluentBit的最新进展以及深入解析的分享;我们知道Fluentd是在2016年底正式加入CNCF,成为…