再谈优雅重试(retry)机制

2023-10-27

业务场景

应用中需要实现一个功能: 需要将数据上传到远程存储服务,同时在返回处理成功情况下做其他操作。这个功能不复杂,分为两个步骤:第一步调用远程的Rest服务逻辑包装给处理方法返回处理结果;第二步拿到第一步结果或者捕捉异常,如果出现错误或异常实现重试上传逻辑,否则继续逻辑操作。

解决方案演化

这个问题的技术点在于能够触发重试,以及重试情况下逻辑有效执行。

解决方案一:try-catch-redo简单重试模式

包装正常上传逻辑基础上,通过判断返回结果或监听异常决策是否重试,同时为了解决立即重试的无效执行(假设异常是有外部执行不稳定导致的),休眠一定延迟时间重新执行功能逻辑。

public void commonRetry(Map<String, Object> dataMap) throws InterruptedException {
		Map<String, Object> paramMap = Maps.newHashMap();
		paramMap.put("tableName", "creativeTable");
		paramMap.put("ds", "20160220");
		paramMap.put("dataMap", dataMap);
		boolean result = false;
		try {
			result = uploadToOdps(paramMap);
			if (!result) {
				Thread.sleep(1000);
				uploadToOdps(paramMap);//一次重试
			}
		} catch (Exception e) {
			Thread.sleep(1000);
			uploadToOdps(paramMap);//一次重试
		}
	} 

解决方案二:try-catch-redo-retry strategy策略重试模式

上述方案还是有可能重试无效,解决这个问题尝试增加重试次数retrycount以及重试间隔周期interval,达到增加重试有效的可能性。

public void commonRetry(Map<String, Object> dataMap) throws InterruptedException {
		Map<String, Object> paramMap = Maps.newHashMap();
		paramMap.put("tableName", "creativeTable");
		paramMap.put("ds", "20160220");
		paramMap.put("dataMap", dataMap);
		boolean result = false;
		try {
			result = uploadToOdps(paramMap);
			if (!result) {
				reuploadToOdps(paramMap,1000L,10);//延迟多次重试
			}
		} catch (Exception e) {
			reuploadToOdps(paramMap,1000L,10);//延迟多次重试
		}
	} 

方案一和方案二存在一个问题:正常逻辑和重试逻辑强耦合,重试逻辑非常依赖正常逻辑的执行结果,对正常逻辑预期结果被动重试触发,对于重试根源往往由于逻辑复杂被淹没,可能导致后续运维对于重试逻辑要解决什么问题产生不一致理解。重试正确性难保证而且不利于运维,原因是重试设计依赖正常逻辑异常或重试根源的臆测。

优雅重试方案尝试

那有没有可以参考的方案实现正常逻辑和重试逻辑解耦,同时能够让重试逻辑有一个标准化的解决思路?答案是有:那就是基于代理设计模式的重试工具,我们尝试使用相应工具来重构上述场景。

尝试方案一:应用命令设计模式解耦正常和重试逻辑

命令设计模式具体定义不展开阐述,主要该方案看中命令模式能够通过执行对象完成接口操作逻辑,同时内部封装处理重试逻辑,不暴露实现细节,对于调用者来看就是执行了正常逻辑,达到解耦的目标,具体看下功能实现。(类图结构)

IRetry约定了上传和重试接口,其实现类OdpsRetry封装ODPS上传逻辑,同时封装重试机制和重试策略。与此同时使用recover方法在结束执行做恢复操作。

而我们的调用者LogicClient无需关注重试,通过重试者Retryer实现约定接口功能,同时 Retryer需要对重试逻辑做出响应和处理, Retryer具体重试处理又交给真正的IRtry接口的实现类OdpsRetry完成。通过采用命令模式,优雅实现正常逻辑和重试逻辑分离,同时通过构建重试者角色,实现正常逻辑和重试逻辑的分离,让重试有更好的扩展性。

尝试方案二:spring-retry 规范正常和重试逻辑

spring-retry是一个开源工具包,目前可用的版本为1.1.2.RELEASE,该工具把重试操作模板定制化,可以设置重试策略和回退策略。同时重试执行实例保证线程安全,具体场景操作实例如下:

public void upload(final Map<String, Object> map) throws Exception {
		// 构建重试模板实例
		RetryTemplate retryTemplate = new RetryTemplate();
		// 设置重试策略,主要设置重试次数
		SimpleRetryPolicy policy = new SimpleRetryPolicy(3, Collections.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
		// 设置重试回退操作策略,主要设置重试间隔时间
		FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
		fixedBackOffPolicy.setBackOffPeriod(100);
		retryTemplate.setRetryPolicy(policy);
		retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
		// 通过RetryCallback 重试回调实例包装正常逻辑逻辑,第一次执行和重试执行执行的都是这段逻辑
		final RetryCallback<Object, Exception> retryCallback = new RetryCallback<Object, Exception>() {
			//RetryContext 重试操作上下文约定,统一spring-try包装 
			public Object doWithRetry(RetryContext context) throws Exception {
				System.out.println("do some thing");
				Exception e = uploadToOdps(map);
				System.out.println(context.getRetryCount());
				throw e;//这个点特别注意,重试的根源通过Exception返回
			}
		};
		// 通过RecoveryCallback 重试流程正常结束或者达到重试上限后的退出恢复操作实例
		final RecoveryCallback<Object> recoveryCallback = new RecoveryCallback<Object>() {
			public Object recover(RetryContext context) throws Exception {
				System.out.println("do recory operation");
				return null;
			}
		};
		try {
			// 由retryTemplate 执行execute方法开始逻辑执行
			retryTemplate.execute(retryCallback, recoveryCallback);
		} catch (Exception e) {
			e.printStackTrace();
		}
	} 

简单剖析下案例代码,RetryTemplate 承担了重试执行者的角色,它可以设置SimpleRetryPolicy(重试策略,设置重试上限,重试的根源实体),FixedBackOffPolicy(固定的回退策略,设置执行重试回退的时间间隔)。 RetryTemplate通过execute提交执行操作,需要准备RetryCallback 和RecoveryCallback 两个类实例,前者对应的就是重试回调逻辑实例,包装正常的功能操作,RecoveryCallback实现的是整个执行操作结束的恢复操作实例。

RetryTemplate的execute 是线程安全的,实现逻辑使用ThreadLocal保存每个执行实例的RetryContext执行上下文。

Spring-retry工具虽能优雅实现重试,但是存在两个不友好设计:一个是 重试实体限定为Throwable子类,说明重试针对的是可捕捉的功能异常为设计前提的,但是我们希望依赖某个数据对象实体作为重试实体,但Sping-retry框架必须强制转换为Throwable子类。另一个就是重试根源的断言对象使用的是doWithRetry的Exception 异常实例,不符合正常内部断言的返回设计。

Spring Retry提倡以注解的方式对方法进行重试,重试逻辑是同步执行的,重试的“失败”针对的是Throwable,如果你要以返回值的某个状态来判定是否需要重试,可能只能通过自己判断返回值然后显式抛出异常了。

Spring 对于Retry的抽象

“抽象”是每个程序员必备的素质。对于资质平平的我来说,没有比模仿与理解优秀源码更好地进步途径了吧。为此,我将其核心逻辑重写了一遍…下面就看看Spring Retry对于“重试”的抽象。

“重试”逻辑

while(someCondition()) {try{doSth();break;} catch(Throwable th) {modifyCondition();wait();}
}
if(stillFail) {doSthWhenStillFail();
}

同步重试代码基本可以表示为上述,但是Spring Retry对其进行了非常优雅地抽象,虽然主要逻辑不变,但是看起来却是舒服多了。主要的接口抽象如下图所示:

Spring retry相关接口.jpg

  • RetryCallback: 封装你需要重试的业务逻辑(上文中的doSth)
  • RecoverCallback:封装在多次重试都失败后你需要执行的业务逻辑(上文中的doSthWhenStillFail)
  • RetryContext: 重试语境下的上下文,可用于在多次Retry或者Retry 和Recover之间传递参数或状态(在多次doSth或者doSth与doSthWhenStillFail之间传递参数)
  • RetryOperations : 定义了“重试”的基本框架(模板),要求传入RetryCallback,可选传入RecoveryCallback;
  • RetryListener:典型的“监听者”,在重试的不同阶段通知“监听者”(例如doSth,wait等阶段时通知)
  • RetryPolicy : 重试的策略或条件,可以简单的进行多次重试,可以是指定超时时间进行重试(上文中的someCondition)
  • BackOffPolicy: 重试的回退策略,在业务逻辑执行发生异常时。如果需要重试,我们可能需要等一段时间(可能服务器过于繁忙,如果一直不间隔重试可能拖垮服务器),当然这段时间可以是0,也可以是固定的,可以是随机的(参见tcp的拥塞控制算法中的回退策略)。回退策略在上文中体现为wait();
  • RetryTemplate :RetryOperations的具体实现,组合了RetryListener[],BackOffPolicy,RetryPolicy。

尝试方案三:guava-retryer 分离正常和重试逻辑

Guava retryer工具与spring-retry类似,都是通过定义重试者角色来包装正常逻辑重试,但是Guava retryer有更优的策略定义,在支持重试次数和重试频度控制基础上,能够兼容支持多个异常或者自定义实体对象的重试源定义,让重试功能有更多的灵活性。Guava Retryer也是线程安全的,入口调用逻辑采用的是Java.util.concurrent.Callable的call方法,示例代码如下:

public void uploadOdps(final Map<String, Object> map) {
		// RetryerBuilder 构建重试实例 retryer,可以设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔
		Retryer<Boolean> retryer = RetryerBuilder.<Boolean> newBuilder()
				.retryIfException().//设置异常重试源
				retryIfResult(new Predicate<Boolean>() {//设置自定义段元重试源,
			@Override
			public boolean apply(Boolean state) {//特别注意:这个apply返回true说明需要重试,与操作逻辑的语义要区分
				return true;
			}
		})
		.withStopStrategy(StopStrategies.stopAfterAttempt(5))//设置重试5次,同样可以设置重试超时时间
		.withWaitStrategy(WaitStrategies.fixedWait(100L, TimeUnit.MILLISECONDS)).build();//设置每次重试间隔

		try {
			//重试入口采用call方法,用的是java.util.concurrent.Callable<V>的call方法,所以执行是线程安全的
			boolean result = retryer.call(new Callable<Boolean>() { 
				@Override
				public Boolean call() throws Exception {
					try {
						//特别注意:返回false说明无需重试,返回true说明需要继续重试
						return uploadToOdps(map);
					} catch (Exception e) {
						throw new Exception(e);
					}
				}
			});

		} catch (ExecutionException e) {
		} catch (RetryException ex) {
		}
	} 

示例代码原理分析:

RetryerBuilder是一个factory创建者,可以定制设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔,创建重试者Retryer实例。

RetryerBuilder的重试源支持Exception异常对象 和自定义断言对象,通过retryIfException 和retryIfResult设置,同时支持多个且能兼容。

RetryerBuilder的等待时间和重试限制配置采用不同的策略类实现,同时对于等待时间特征可以支持无间隔和固定间隔方式。

Retryer 是重试者实例,通过call方法执行操作逻辑,同时封装重试源操作。

优雅重试共性和原理

1.正常和重试优雅解耦,重试断言条件实例或逻辑异常实例是两者沟通的媒介。
2.约定重试间隔,差异性重试策略,设置重试超时时间,进一步保证重试有效性以及重试流程稳定性。
3.都使用了命令设计模式,通过委托重试对象完成相应的逻辑操作,同时内部封装实现重试逻辑。
4.Spring-tryer和guava-tryer工具都是线程安全的重试,能够支持并发业务场景的重试逻辑正确性。

优雅重试适用场景

1.功能逻辑中存在不稳定依赖场景,需要使用重试获取预期结果或者尝试重新执行逻辑不立即结束。比如远程接口访问,数据加载访问,数据上传校验等等。
2.对于异常场景存在需要重试场景,同时希望把正常逻辑和重试逻辑解耦。
3.对于需要基于数据媒介交互,希望通过重试轮询检测执行逻辑场景也可以考虑重试方案。

网络安全成长路线图

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成hei客大神,这个方向越往后,需要学习和掌握的东西就会越来越多,以下是学习网络安全需要走的方向:

# 网络安全学习方法

​ 上面介绍了技术分类和学习路线,这里来谈一下学习方法:
​ ## 视频学习

​ 无论你是去B站或者是油管上面都有很多网络安全的相关视频可以学习,当然如果你还不知道选择那套学习,我这里也整理了一套和上述成长路线图挂钩的视频教程,完整版的视频已经上传至CSDN官方,朋友们如果需要可以点击这个链接免费领取。网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

再谈优雅重试(retry)机制 的相关文章

  • 为什么 i++ 不是原子的?

    Why is i Java 中不是原子的 为了更深入地了解 Java 我尝试计算线程中循环的执行频率 所以我用了一个 private static int total 0 在主课中 我有两个线程 主题 1 打印System out prin
  • Java - 将节点添加到列表的末尾?

    这是我所拥有的 public class Node Object data Node next Node Object data Node next this data data this next next public Object g
  • Final字段的线程安全

    假设我有一个 JavaBeanUser这是从另一个线程更新的 如下所示 public class A private final User user public A User user this user user public void
  • JAXb、Hibernate 和 beans

    目前我正在开发一个使用 Spring Web 服务 hibernate 和 JAXb 的项目 1 我已经使用IDE hibernate代码生成 生成了hibernate bean 2 另外 我已经使用maven编译器生成了jaxb bean
  • 无法展开 RemoteViews - 错误通知

    最近 我收到越来越多的用户收到 RemoteServiceException 错误的报告 我每次给出的堆栈跟踪如下 android app RemoteServiceException Bad notification posted fro
  • Android MediaExtractor seek() 对 MP3 音频文件的准确性

    我在使用 Android 时无法在eek 上获得合理的准确度MediaExtractor 对于某些文件 例如this one http www archive org download emma solo librivox emma 01
  • 反射找不到对象子类型

    我试图通过使用反射来获取包中的所有类 当我使用具体类的代码 本例中为 A 时 它可以工作并打印子类信息 B 扩展 A 因此它打印 B 信息 但是当我将它与对象类一起使用时 它不起作用 我该如何修复它 这段代码的工作原理 Reflection
  • Liferay ClassNotFoundException:DLFileEntryImpl

    在我的 6 1 0 Portal 实例上 带有使用 ServiceBuilder 和 DL Api 的 6 1 0 SDK Portlet 这一行 DynamicQuery query DynamicQueryFactoryUtil for
  • 路径中 File.separator 和斜杠之间的区别

    使用有什么区别File separator和一个正常的 在 Java 路径字符串中 与双反斜杠相反 平台独立性似乎不是原因 因为两个版本都可以在 Windows 和 Unix 下运行 public class SlashTest Test
  • Java TestNG 与跨多个测试的数据驱动测试

    我正在电子商务平台中测试一系列商店 每个商店都有一系列属性 我正在考虑对其进行自动化测试 是否有可能有一个数据提供者在整个测试套件中提供数据 而不仅仅是 TestNG 中的测试 我尝试不使用 testNG xml 文件作为机制 因为这些属性
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • getResourceAsStream() 可以找到 jar 文件之外的文件吗?

    我正在开发一个应用程序 该应用程序使用一个加载配置文件的库 InputStream in getClass getResourceAsStream resource 然后我的应用程序打包在一个 jar文件 如果resource是在里面 ja
  • AWS 无法从 START_OBJECT 中反序列化 java.lang.String 实例

    我创建了一个 Lambda 函数 我想在 API 网关的帮助下通过 URL 访问它 我已经把一切都设置好了 我还创建了一个application jsonAPI Gateway 中的正文映射模板如下所示 input input params
  • 如何从终端运行处理应用程序

    我目前正在使用加工 http processing org对于一个小项目 但是我不喜欢它附带的文本编辑器 我使用 vim 编写所有代码 我找到了 pde 文件的位置 并且我一直在从 vim 中编辑它们 然后重新打开它们并运行它们 重新加载脚
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • 如何从指定日期获取上周五的日期? [复制]

    这个问题在这里已经有答案了 如何找出上一个 上一个 星期五 或指定日期的任何其他日期的日期 public getDateOnDay Date date String dayName 我不会给出答案 先自己尝试一下 但是 也许这些提示可以帮助
  • 玩!框架:运行“h2-browser”可以运行,但网页不可用

    当我运行命令时activator h2 browser它会使用以下 url 打开浏览器 192 168 1 17 8082 但我得到 使用 Chrome 此网页无法使用 奇怪的是它以前确实有效 从那时起我唯一改变的是JAVA OPTS以启用
  • 声明的包“”与预期的包不匹配

    我可以编译并运行我的代码 但 VSCode 中始终显示错误 早些时候有一个弹出窗口 我不记得是什么了 我点击了 全局应用 从那以后一直是这样 Output is there but so is the error The declared
  • 使用 JMF 创建 RTP 流时出现问题

    我正处于一个项目的早期阶段 需要使用 RTP 广播DataStream创建自MediaLocation 我正在遵循一些示例代码 该代码目前在rptManager initalize localAddress 出现错误 无法打开本地数据端口
  • 当我从 Netbeans 创建 Derby 数据库时,它存储在哪里?

    当我从 netbeans 创建 Derby 数据库时 它存储在哪里 如何将它与项目的其余部分合并到一个文件夹中 右键单击Databases gt JavaDB in the Service查看并选择Properties This will

随机推荐

  • MATLAB强化学习实战(十二) 创建自定义强化学习算法的智能体

    创建自定义强化学习算法的智能体 创建环境 定义策略 自定义智能体类 智能体属性 构造函数 相关函数 可选功能 创建自定义智能体 训练自定义智能体 自定义智能体仿真 本示例说明如何为您自己的自定义强化学习算法创建自定义智能体 这样做使您可以利
  • 服务器收不到客户端消息,java - 服务器没有收到来自客户端的消息(reader.readLine()== null?)...

    所以我一直在做一个简单的聊天 比有一台服务器和一群客户端连接到它们 在客户端 我有类ConnectionManager来管理创建套接字等 这是它的核心方法 java 服务器没有收到来自客户端的消息 reader readLine null
  • 目标检测新范式!港大同济伯克利提出Sparse R-CNN

    Sparse R CNN End to End Object Detection with Learnable Proposals 作者单位 港大 同济大学 字节AI Lab UC伯克利 沿着目标检测领域中Dense和Dense to Sp
  • java char与int互相转换

    1 int转char 将数字加一个 0 并强制类型转换为char 2 char转int 将字符减一个 0 即可 public class a public static void main String args System out pr
  • 【java】使pagehelper分页与mybatis-plus分页兼容存在

    问题 原先的功能接口都无法保存 出现如下错误 net sf jsqlparser statement select SetOperationList cannot be cast to net sf jsqlparser statement
  • 一张图告诉你,MES系统是什么

    MES系统 就要说到生产 涉及到人 钱 货 信息等资源 产 供 销 还有供应商 客户 合作伙伴 其中 产 就是生产 而生产管理就是通过对生产系统的战略规划 组织 指挥 实施 协调和控制 实现生产系统的物质转化 产品生产和价值提升的过程 擅长
  • 解决Python OpenCV 读取视频并抽帧出现error while decoding的问题

    解决Python OpenCV 读取视频抽帧出现error while decoding的问题 1 问题 2 解决 3 源代码 参考 1 问题 读取H264视频 抽帧视频并保存 报错如下 aac 00000220b9a07fc0 Input
  • 华为OD机试 - 火星文计算(Java)

    目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 已知火星人使用的运算符为 其与地球人的等价公式如下 x y 2 x 3 y 4 x y 3 x y 2 其中x y是无符号整数 地球人公式按C语言规则计算 火星人公式中
  • Work with shaders and shader resources

    It s time to learn how to work with shaders and shader resources in developing your Microsoft DirectX game for Windows 8
  • 如何输出*.ply文件

    所谓ply文件格式 是由斯坦福大学开发的一套三维mesh模型数据格式 图形学领域内很多著名的模型数据 比如斯坦福的三维扫描数据库 其中包括很多文章中会见到的Happy Buddha Dragon Bunny兔子 最初的模型都是基于这个格式的
  • 夜莺(Nightingale)企业级监控平台

    元芳算法服务部署 监控篇 内部 简介 夜莺 Nightingale 是滴滴基础平台联合滴滴云研发和开源的企业级监控解决方案 旨在满足云原生时代企业级的监控需求 Nightingale在产品完成度 系统高可用 以及用户体验方面 达到了企业级的
  • java压缩文件工具类

    java压缩文件的各种方式及速度对比 1使用场景 2 maven依赖 3 压缩文件工具类 4 对比结果查看 1使用场景 开发过程中可能会用到压缩文件的需求 是用什么方式更快呢 本次就对几种常见的压缩方式做一个对比 2 maven依赖 引用c
  • java.lang.NoClassDefFoundError: org/springframework/jdbc/core/JdbcTemplate dao.EmployeeDAO.addEmplo

    文章目录 报错 java lang NoClassDefFoundError org springframework jdbc core JdbcTemplate lib文件要放到WEB INF下 jar包文件夹的名字一定要是lib 如果是
  • 树的后序遍历(递归和非递归)

    树的后序遍历 左右根 代码写起来还是很简单的 就几行代码 public void postOrder Node node if node getLeft null postOrder node getLeft if node getRigh
  • 关于arxiv的PDF加载过慢的解决办法

    arxiv 的 PDF 下载速度很慢 下面是一些加速方法 1 命令行直接下载 我们知道可以用wget命令下载一些网络文件 不过arxiv 上的论文使用wget下载时需要加参数 user agent Lynx 速度才能较快 下面是使用的例子
  • 自学测试半年,我终于收到了腾讯的offer,收到消息的那一刻我激动的哭出了声...

    我是一名毕业于普通一本的化学专业学生 毕业的两年时间里 我一直奔波在化工厂里 每天工作三班倒 下了班就是一包烟一瓶酒 生活过得非常堕落 原本想着虽然每天很累 但是至少稳定 然而没有想到的是 化工行业也有职业危机 越来越多的高科技代替人工 我
  • public class Solution { public int JumpFloorII(int target) { int a=1; int b=2; if(target...

    类似于青蛙跳台阶 当n 1时 只有一种横向排列的方式 当n等于二时 2 2有两种选择 横向或者是竖向 当n等于3的时候对于2 3来说 如果选择的是竖向排列 则剩下的就是2 2排列 如果选择的是横向 则对于2 n剩下的则只有1 n的一种选择
  • 总结内存(RAM或ROM)和FLASH存储的真正区别

    本文主要向大家介绍了内存 RAM或ROM 和FLASH存储的真正区别 通过具体的分析 让大家能够了解它们 希望对大家学习内存 RAM或ROM 和FLASH存储有所帮助 1 什么是内存 什么是内存呢 在计算机的组成结构中 有一个很重要的部分
  • C++ 迭代器,常用五种迭代器

    一文读懂C 五种常用的迭代器 c STL迭代器 iterator C 迭代器 什么是迭代器 好怪异的名词 搞了好多年都没有搞懂 说白了 迭代器 就是 开瓶器 而已 开瓶器的种类 有开啤酒瓶的 有开红酒瓶的 还有其他xx的 它们的目的 都是为
  • 再谈优雅重试(retry)机制

    业务场景 应用中需要实现一个功能 需要将数据上传到远程存储服务 同时在返回处理成功情况下做其他操作 这个功能不复杂 分为两个步骤 第一步调用远程的Rest服务逻辑包装给处理方法返回处理结果 第二步拿到第一步结果或者捕捉异常 如果出现错误或异