接口补偿机制需求分析&方案设计
接口补偿机制需求分析&方案设计
文章目录
接口补偿机制需求分析&方案设计
需求分析
背景
解决方案
业务示例
注意事项
示例
业务Controller
实现
重试信息类&数据处理入库
接口重试的主要方法
需求分析
背景
业务系统逐渐开始与多个第三方系统进行对接,在对接时,需要调用外部系统接口进行数据的交换,如果在接口请求的过程中发生了网络抖动或其他问题,会导致接口调用失败;
对于此类问题,需要一个长效的接口重新调用机制,在发生网络抖动时可以进行自动地补偿调用,或者记录下来通知人工处理。
解决方案
建立 “补偿接口信息表” ,主要字段:
全类名(即包名+类名):class_name
方法名:method_name
参数类型数组:method_param_types 按照方法签名的顺序插入数组
参数值数组:method_param_valuesTips:对象-->JsonString、null-->'null',按照方法签名的顺序插入数组 ,组成字符串数组
错误信息: error_msg
重试次数:retry_count
最大次数:max_retry_count
重试有效期: retry_expiry_date
数据防重code:unique_hash_code Unique_key Hash(class_name+method_name+method_param_values)
状态:status 10:未解决;20:已解决
编写InterfaceRetryInfo类用以记录类名、方法名、参数值数组、最大次数、重试有效期等信息,开发人员在需要重试的业务方法中调用第三方系统接口失败时,给这些信息赋值并调用InterfaceRetryInfoService.asyncRetry(retryInfo)方法异步存入数据库,之后抛出RetryFlagException异常,以方便补偿方法可以判断重试调用成功与否;
编写接口补偿方法public boolean processRetryInfo(InterfaceRetryInfo retryInfo),通过反射获取方法和参数并调用;
编写遍历方法doRetry(),遍历数据库中的所有未解决的接口补偿数据,并提供Restful接口;
接入公司定时任务系统DING,定时调用doRetry()进行接口补偿,如果补偿成功,则修改数据状态为**20:已解决**。
业务示例
注意事项
需要补偿的第三方接口需满足幂等性
调用第三方接口的逻辑需为单独的处理方法,和业务逻辑分离;
第三方接口调用失败或异常的情况下,需保证处理方法一定要抛出RetryFlagException异常。
处理方法的参数,类型可以为T、List<String>、List<T>、Map<String,String>,T代表Java基础类型或者POJO,且POJO的属性中如果有Map<K,V>,则K、V必须是String或其他Java基础类型;否则处理会出错。
如果处理方法使用了@Async注解实现异步处理,则返回值必须为Future且异常处理最后一定要return false。
示例
业务Controller
@RestController
@Slf4j
@RequestMapping(value = "/retry/demo")
public class RetryDemoController {
@Autowired
private InterfaceRetryInfoService interfaceRetryInfoService;
@RequestMapping(method = RequestMethod.GET)
public BaseResult<String> retryDemo(){
List<SampleBoxDO> sampleBoxDOList = new ArrayList<>();
List<InventoryOwnCardDO> demoList = new LinkedList<>();
Map<String, String> demoMap = new HashMap<>();
demoMap.put("test11", "test11");
demoMap.put("test22", "test22");
//省略初始化&赋值代码
sampleBoxDO.setDemoList(demoList);
sampleBoxDO.setDemoMap(demoMap);
sampleBoxDOList.add(sampleBoxDO);
sampleBoxDOList.add(sampleBoxDO2);
//调用第三方接口的逻辑需为单独的处理方法,和业务逻辑分离
String msg = notifySomeone("Hello World", null, sampleBoxDOList, demoMap);
return BaseResultUtils.ok(msg);
}
//调用第三方接口的处理方法
@Async
private Boolean notifySomeone(String param1, List<String> param2, List<SampleBoxDO> param3, Map<String, String> param4) {
try {
Random random = new Random();
int a = random.nextInt(10);
if (a < 5){ //模拟调用第三方失败
//如果调用第三方接口异常,异步保持处理方法的信息到数据库,等待定时任务进行补偿重试
log.error("notifySomeone error--->");
String className = RetryDemoController.class.getName();
String methodName = "notifySomeone";
//方法参数值是不定长参数,param1、param2...paramn
InterfaceRetryInfo retryInfo = new InterfaceRetryInfo(className, methodName, e.getMessage(), param1, param2, param3, param4);
interfaceRetryInfoService.asyncRetry(retryInfo);
throw new RetryFlagException("调用第三方失败,需要重试");
}else {//模拟调用第三方成功
System.out.println("param1->"+param1);
if (CollectionUtils.isNotEmpty(param2)){
param2.forEach(p -> System.out.println(p));
}
if (CollectionUtils.isNotEmpty(param3)){
param3.forEach(p -> System.out.println(p.toString()));
}
}
} catch (Exception e) {
//如果调用第三方接口异常,异步保持处理方法的信息到数据库,等待定时任务进行补偿重试
log.error("notifySomeone error--->",e);
String className = RetryDemoController.class.getName();
String methodName = "notifySomeone";
//方法参数值是不定长参数,param1、param2...paramn
InterfaceRetryInfo retryInfo = new InterfaceRetryInfo(className, methodName, e.getMessage(), param1, param2, param3, param4);
interfaceRetryInfoService.asyncRetry(retryInfo);
//处理方法最后一定要return false
return new AsyncResult<>(false);
}
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
实现
重试信息类&数据处理入库
@Data
public class InterfaceRetryInfo implements Serializable {
//。。。省略属性定义
/*构造方法
* 通过反射获取方法的参数类型数组
* 使用JsonObject将参数值序列化为字符串
* 存入数据库
*/
public InterfaceRetryInfo(String className, String methodName, String errorMsg, Object... methodParamValues) {
Assert.notNull(className);
Assert.notNull(methodName);
Assert.notNull(errorMsg);
this.className = className;
this.methodName = methodName;
if (methodParamValues != null && methodParamValues.length > 0) {
try {
//反射获取类的Class,并获取所有的声明方法,遍历之,获取需要进行重试的方法(该方法不可重载,否则无法获取准确的方法)
Class<?> clazz = Class.forName(className);
Method[] methods = clazz.getDeclaredMethods();
Method calledMethod=null;
for(Method method:methods){
if(method.getName().equals(methodName)){
calledMethod=method;
break;
}
}
//获取方法的参数类型,遍历参数值数组
Class<?>[] paramTypes = calledMethod.getParameterTypes();
List<String> paramValueStrList = new LinkedList<>();
for (int i = 0; i < methodParamValues.length; i++) {
Object paramObj = methodParamValues[i];
//如果值为空,则存入 "null"
if (null == paramObj){
paramValueStrList.add("null");
}else {
//如果参数是String类型,则直接存入
if (paramTypes[i] == String.class){
paramValueStrList.add((String) paramObj);
}else {
//如果参数是POJO或集合类型,则序列化为字符串
paramValueStrList.add(JSONObject.toJSONString(paramObj));
}
}
}
this.methodParamValues = JSONObject.toJSONString(paramValueStrList);
this.methodParamTypes = JSONObject.toJSONString(paramTypes);
} catch (ClassNotFoundException e ) {}
}
this.errorMsg = errorMsg;
this.uniqueHashCode = MD5.getInstance().getMD5String((className + methodName + this.methodParamValues).getBytes());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
接口重试的主要方法
@Override
public boolean processRetryInfo(InterfaceRetryInfo retryInfo) {
String className = retryInfo.getClassName();
String methodName = retryInfo.getMethodName();
String paramValuesStr = retryInfo.getMethodParamValues();
//反射获取类的Class,并定位到要重试的方法
try {
Class<?> clazz = Class.forName(className);
Object object = context.getBean(clazz);
Method[] methods = clazz.getDeclaredMethods();
Method calledMethod=null;
for(Method method:methods){
if(method.getName().equals(methodName)){
calledMethod=method;
break;
}
}
Object[] paramValueList = null;
//如果方法有参数,则进行参数解析
if (StringUtils.isNotEmpty(paramValuesStr)){
List<String> paramValueStrList = JSONObject.parseArray(paramValuesStr, String.class);
//获取方法所有参数的Type
Type[] paramTypes = calledMethod.getGenericParameterTypes();
paramValueList = new Object[paramTypes.length];
for (int i = 0; i < paramValueStrList.size(); i++) {
String paramStr = paramValueStrList.get(i);
//如果参数值为空,则置为null
if ("null".equalsIgnoreCase(paramStr)){
paramValueList[i] = null;
}else {
//如果参数是String类型,则直接赋值
if (paramTypes[i] == String.class){
paramValueList[i] = paramStr;
// 如果参数是带泛型的集合类或者不带泛型的List,则需要特殊处理
}else if(paramTypes[i] instanceof ParameterizedType || paramTypes[i] == List.class){
Type genericType = paramTypes[i];
//如果是不带泛型的List 直接解析数组
if (genericType == List.class){
paramValueList[i] = JSON.parseObject(paramStr, List.class);
}else if (((ParameterizedTypeImpl) genericType).getRawType() == List.class){
// 如果是带泛型的List,则获取其泛型参数类型
ParameterizedType pt = (ParameterizedType) genericType;
//得到泛型类型对象
Class<?> genericClazz = (Class<?>)pt.getActualTypeArguments()[0];
//反序列化
paramValueList[i] = JSON.parseArray(paramStr, genericClazz);
}else {
//如果是带泛型的其他集合类型,直接反序列化
paramValueList[i] = JSON.parseObject(paramStr, paramTypes[i], Feature.OrderedField);
}
}else {
//如果是POJO类型,则直接解析对象
paramValueList[i] = JSON.parseObject(paramStr, paramTypes[i], Feature.OrderedField);
}
}
}
}
//设置访问权限,否则会调用失败,throw IllegalAccessException
calledMethod.setAccessible(true);
//反射调用方法
boolean asyncFlag = false;
Annotation[] annotations = calledMethod.getDeclaredAnnotations();
if (annotations != null && annotations.length > 0){
for (Annotation annotation : annotations) {
if (annotation.annotationType().getTypeName().equalsIgnoreCase("org.springframework.scheduling.annotation.Async")){
asyncFlag = true;
}
}
}
if (asyncFlag){
Future<Boolean> future = (Future) calledMethod.invoke(object, paramValueList);
Boolean flag = future.get();
if(!flag){
throw new RetryFlagException();
}
}else {
calledMethod.invoke(object, paramValueList);
}
retryInfo.setStatus(InterfaceRetryInfoStatusEnum.HAS_DONE.getCode());
} catch (ClassNotFoundException | IllegalAccessException | InterruptedException | ExecutionException e ) {
log.error("反射异常-->",e);
return false;
}catch (InvocationTargetException | RetryFlagException e){
log.error("重试调用失败,更新次数-->",e);
}
retryInfo.setRetryTimes((retryInfo.getRetryTimes()) + 1);
interfaceRetryInfoDAO.updateByPrimaryKeySelective(retryInfo);
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
————————————————
版权声明:本文为CSDN博主「忙里偷闲得几回」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/panyongcsd/article/details/81485298
接口补偿机制需求分析&方案设计的更多相关文章
- 关于分布式事务、两阶段提交、一阶段提交、Best Efforts 1PC模式和事务补偿机制的研究 转载
1.XA XA是由X/Open组织提出的分布式事务的规范.XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接 ...
- web集群和分布式服务以及消息补偿机制几种方案
一.为什么要集群? 1.JavaEE项目,如果部署在一台Tomcat上,所有的请求,都由这一台服务器处理,存在很大风险: A:并发处理能力有限(一般单台服务器处理的并发量为250左右,超过250,可能 ...
- (转)Android之接口回调机制
开发中,接口回调是我们经常用到的. 接口回调的意思即,注册之后并不立马执行,而在某个时机触发执行. 举个例子: A有一个问题不会,他去问B,B暂时解决不出来,B说,等我(B)解决了再告诉你(A)此时A ...
- 弄明白Android 接口回调机制
以前对于这个机制理解不够深刻,现在重新整理下思路. 一.建模 我理解的接口回调就是,我这个类实现了一个接口里的方法doSomething,然后注册到你这里,然后我就去做别的事情去了,你在某个触发的时机 ...
- rabbitmq~消息失败后重试达到 TTL放到死信队列(事务型消息补偿机制)
这是一个基于消息的分布式事务的一部分,主要通过消息来实现,生产者把消息发到队列后,由消费方去执行剩下的逻辑,而当消费方处理失败后,我们需要进行重试,即为了最现数据的最终一致性,在rabbitmq里,它 ...
- Java接口回调机制
一.前言 最近在看android Fragment与Activity进行数据传递的部分,看到了接口回调的内容,今天来总结一下. 二.回调的含义和用途 1.什么是回调? 一般来说,模块之间都存在一定的调 ...
- 使用Guava retryer优雅的实现接口重试机制
转载自: 使用Guava retrying优雅的实现接口重调机制 Guava retrying:基于 guava 的重试组件 实际项目中,为了考虑网络抖动,加锁并发冲突等场景,我们经常需要对异常操作进 ...
- App架构设计经验谈:接口”安全机制”的设计
[原文地址 点击打开链接] 原创文章,转载请注明:转载自Keegan小钢 并标明原文链接:http://keeganlee.me/post/architecture/20160107 微信订阅号:ke ...
- Android接口回调机制
开发中,接口回调是我们经常用到的. 接口回调的意思即,注册之后并不立马执行,而在某个时机触发执行. 举个例子: A有一个问题不会,他去问B,B暂时解决不出来,B说,等我(B)解决了再告诉你(A)此时A ...
随机推荐
- JS经典理解例子
1. var name = 'the window'; var obj = { name:"my obj", getNameFunc:function(){ return func ...
- 【转】spring IOC和AOP的理解
spring 的优点?1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦 2.可以使用容易提供的众多服务,如事务管理,消息服务等 3.容器提供单例模式支持 4.容器提供了AOP技术,利用它很容易实 ...
- 吴裕雄 Bootstrap 前端框架开发——Bootstrap 表格:为任意 <table> 添加基本样式 (只有横向分隔线)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- C# Stream篇(—) -- Stream基类-----转载
C# Stream篇(—) -- Stream基类 写在前头: Stream系列文章共收录7篇,本着备忘和归纳的目的本着备忘和归纳的目的,全部收录于本分类中. 下面是有原文连接,望各位看官还是到原作者 ...
- python学习 —— python3简单使用pymysql包操作数据库
python3只支持pymysql(cpython >= 2.6 or >= 3.3,mysql >= 4.1),python2支持mysqldb. 两个例子: import pym ...
- .Net后台实现微信小程序支付
最近一直再研究微信支付和支付宝支付,官方支付文档中一直在讲与第三方支付打交道的原理,却没有介绍我们自己项目中的APP与后台该怎么交互(哈哈,人家也没必要介绍这一块).拜读了官方文档和前辈们的佳作,自己 ...
- C语言的常用的数据类型有哪些_所占字节分别是多少
整型 整形打印使用%d short:短整型,占16位,2个字节 int:占32位,4个字节 long:长整型,占4个字节,本来意思比int更多,但是目前来看基本都是和int一样 浮点型 浮点型计算会影 ...
- python对文件中光标的操作迭代器
seek() 默认从文件开头开始.seek(10) seek(10,1) 需要以b的模式读取文件,从相对位置进行移动光标 seek(-3,2) 倒着移动光标的模式 例如: f= open( ...
- Java中Comparator的使用
在某些特殊情况,我们需要对一个对象数组或集合依照对应的属性排序:此时,我们就可以用Comparator接口处理. 上代码 TestComparaTo 类 package com.test.interf ...
- python中时间戳的探索
声明 本文章只针对python3.6及以上版本. 问题提出 首先,我们先import一些必要模块: In [1]: from datetime import datetime, timezone, t ...