Java按时间梯度实现异步回调接口
1. 背景
在业务处理完之后,需要调用其他系统的接口,将相应的处理结果通知给对方,若是同步请求,假如调用的系统出现异常或是宕机等事件,会导致自身业务受到影响,事务会一直阻塞,数据库连接不够用等异常现象,可以通过异步回调来防止阻塞,但异步的情况还存在一个问题,若调用一次不成功的话接下来怎么处理?这个地方就需要按时间梯度回调,比如前期按10s间隔回调,回调3次,若不成功按30s回调,回调2次,再不成功按分钟回调,依次类推……相当于给了对方系统恢复的时间,不可能一直处于异常或宕机等异常状态,若是再不成功可以再通过人工干预的手段去处理了,具体业务具体实现。
2. 技术实现
大体实现思路如下图,此过程用到两个队列,当前队列和Next队列,当前队列用来存放第一次需要回调的数据对象,如果调用不成功则放入Next队列,按照制定的时间策略再继续回调,直到成功或最终持久化后人工接入处理。
用到的技术如下:
- http请求库,retrofit2
- 队列,LinkedBlockingQueue
- 调度线程池,ScheduledExecutorService

3. 主要代码说明
3.1 回调时间梯度的策略设计
采用枚举来对策略规则进行处理,便于代码上的维护,该枚举设计三个参数,级别、回调间隔、回调次数;
/**
* 回调策略
*/
public enum CallbackType { //等级1,10s执行3次
SECONDS_10(1, 10, 3),
//等级2,30s执行2次
SECONDS_30(2, 30, 2),
//等级3,60s执行2次
MINUTE_1(3, 60, 2),
//等级4,5min执行1次
MINUTE_5(4, 300, 1),
//等级5,30min执行1次
MINUTE_30(5, 30*60, 1),
//等级6,1h执行2次
HOUR_1(6, 60*60, 1),
//等级7,3h执行2次
HOUR_3(7, 60*60*3, 1),
//等级8,6h执行2次
HOUR_6(8, 60*60*6, 1); //级别
private int level;
//回调间隔时间 秒
private int intervalTime;
//回调次数
private int count;
}
3.2 数据传输对象设计
声明抽象父类,便于其他对象调用传输继承。
/**
* 消息对象父类
*/
public abstract class MessageInfo { //开始时间
private long startTime;
//更新时间
private long updateTime;
//是否回调成功
private boolean isSuccess=false;
//回调次数
private int count=0;
//回调策略
private CallbackType callbackType;
}
要传输的对象,继承消息父类;
/**
* 工单回调信息
*/
public class WorkOrderMessage extends MessageInfo { //车架号
private String vin;
//工单号
private String workorderno;
//工单状态
private Integer status;
//工单原因
private String reason;
//操作用户
private Integer userid;
}
3.3 调度线程池的使用
//声明线程池,大小为16
private ScheduledExecutorService pool = Executors.newScheduledThreadPool(16);
...略 while (true){
//从队列获取数据,交给定时器执行
try {
WorkOrderMessage message = MessageQueue.getMessageFromNext();
long excueTime = message.getUpdateTime()+message.getCallbackType().getIntervalTime()* 1000;
long t = excueTime - System.currentTimeMillis();
if (t/1000 < 5) {//5s之内将要执行的数据提交给调度线程池
System.out.println("MessageHandleNext-满足定时器执行条件"+JSONObject.toJSONString(message));
pool.schedule(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
remoteCallback(message);
return true;
}
}, t, TimeUnit.MILLISECONDS);
}else {
MessageQueue.putMessageToNext(message);
}
} catch (InterruptedException e) {
System.out.println(e);
}
}
3.4 retrofit2的使用,方便好用。
具体可查看官网相关文档进行了解,用起来还是比较方便的。http://square.github.io/retrofit/
retrofit初始化:
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitHelper { private static final String HTTP_URL = "http://baidu.com/";
private static Retrofit retrofit; public static Retrofit instance(){
if (retrofit == null){
retrofit = new Retrofit.Builder()
.baseUrl(HTTP_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
如果需要修改超时时间,连接时间等可以这样初始话,Retrofit采用OkHttpClient
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory; import java.util.concurrent.TimeUnit; public class RetrofitHelper { private static final String HTTP_URL = "http://baidu.com/";
private static Retrofit retrofit; public static Retrofit instance(){
if (retrofit == null){
retrofit = new Retrofit.Builder()
.baseUrl(HTTP_URL)
.client(new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)//连接时间
.readTimeout(30, TimeUnit.SECONDS)//读时间
.writeTimeout(30, TimeUnit.SECONDS)//写时间
.build())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
Retrofit使用通过接口调用,要先声明一个接口;
import com.alibaba.fastjson.JSONObject;
import com.woasis.callbackdemo.bean.WorkOrderMessage;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST; public interface WorkOrderMessageInterface { @POST("/api")
Call<JSONObject> updateBatteryInfo(@Body WorkOrderMessage message); }
接口和实例对象准备好了,接下来就是调用;
private void remoteCallback(WorkOrderMessage message){
//实例接口对象
WorkOrderMessageInterface workOrderMessageInterface = RetrofitHelper.instance().create(WorkOrderMessageInterface.class);
//调用接口方法
Call<JSONObject> objectCall = workOrderMessageInterface.updateBatteryInfo(message);
System.out.println("远程调用执行:"+new Date());
//异步调用执行
objectCall.enqueue(new Callback<JSONObject>() {
@Override
public void onResponse(Call<JSONObject> call, Response<JSONObject> response) {
System.out.println("MessageHandleNext****调用成功"+Thread.currentThread().getId());
message.setSuccess(true);
System.out.println("MessageHandleNext-回调成功"+JSONObject.toJSONString(message));
}
@Override
public void onFailure(Call<JSONObject> call, Throwable throwable) {
System.out.println("MessageHandleNext++++调用失败"+Thread.currentThread().getId());
//失败后再将数据放入队列
try {
//对回调策略初始化
long currentTime = System.currentTimeMillis();
message.setUpdateTime(currentTime);
message.setSuccess(false);
CallbackType callbackType = message.getCallbackType();
//获取等级
int level = CallbackType.getLevel(callbackType);
//获取次数
int count = CallbackType.getCount(callbackType);
//如果等级已经最高,则不再回调
if (CallbackType.HOUR_6.getLevel() == callbackType.getLevel() && count == message.getCount()){
System.out.println("MessageHandleNext-等级最高,不再回调, 线下处理:"+JSONObject.toJSONString(message));
}else {
//看count是否最大,count次数最大则增加level
if (message.getCount()<callbackType.getCount()){
message.setCount(message.getCount()+1);
}else {//如果不小,则增加level
message.setCount(1);
level += 1;
message.setCallbackType(CallbackType.getTypeByLevel(level));
}
MessageQueue.putMessageToNext(message);
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("MessageHandleNext-放入队列数据失败");
}
}
});
}
3.5结果实现

4.总结
本次实现了按照时间梯度去相应其他系统的接口,不再导致本身业务因其他系统的异常而阻塞。请大佬们看实现有没有不合理的地方,欢迎批评指正。
源码:https://github.com/liuzwei/callback-demo
Java按时间梯度实现异步回调接口的更多相关文章
- 第46天学习打卡(四大函数式接口 Stream流式计算 ForkJoin 异步回调 JMM Volatile)
小结与扩展 池的最大的大小如何去设置! 了解:IO密集型,CPU密集型:(调优) //1.CPU密集型 几核就是几个线程 可以保持效率最高 //2.IO密集型判断你的程序中十分耗IO的线程,只要大于 ...
- Future 异步回调 大起底之 Java Future 与 Guava Future
目录 写在前面 1. Future模式异步回调大起底 1.1. 从泡茶的案例说起 1.2. 何为异步回调 1.2.1. 同步.异步.阻塞.非阻塞 1.2.2. 阻塞模式的泡茶案例图解 1.2.3. 回 ...
- java单元测试之如何实现异步接口的测试案例
测试是软件发布的重要环节,单元测试在实际开发中是一种常用的测试方法,java单元测试主要用junit,最新是junit5,本人开发一般用junit4.因为单元测试能够在软件模块组合之前尽快发现问题,所 ...
- java 中的异步回调
异步回调,本来在c#中是一件极为简单和优雅的事情,想不到在java的世界里,却如此烦琐,先看下类图: 先定义了一个CallBackTask,做为外层的面子工程,其主要工作为start 开始一个异步操作 ...
- Java异步回调
作者:禅楼望月(http://www.cnblogs.com/yaoyinglong) 1.开始讲故事: 午饭的时候到了,可是天气太冷,根本不想出办公室的门,于是你拨通了某饭店的订餐电话“喂!你好 ...
- 【java回调】同步/异步回调机制的原理和使用方法
回调(callback)在我们做工程过程中经常会使用到,今天想整理一下回调的原理和使用方法. 回调的原理可以简单理解为:A发送消息给B,B处理完后告诉A处理结果.再简单点就是A调用B,B调用A. 那么 ...
- 同步异步,异步回调,线程队列,线程时间Event
同步异步-阻塞非阻塞 阻塞-非阻塞 指的是程序的运行状态 阻塞:当程序执行过程中遇到了IO操作,在执行IO操作时,程序无法继续执行其他代码,称为阻塞. 非阻塞:程序在正常运行没有遇到IO操作,或者通过 ...
- Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO
Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO Java 非阻塞 IO 和异步 IO 转自https://www.javadoop.com/post/nio-and-aio 本系 ...
- 升级springboot导致的业务异步回调积压问题定位
1. 起因 A与B云侧模块特性联调的过程中,端侧发现云侧返回有延迟的情况. 7月19日与A模块一起抓包初步判断,B业务有积压的情况. 7月18日已经转侧B业务现网版本,由于使用一套逻辑.故可能存在请求 ...
随机推荐
- 26、xcode8.0 解决没有iPhone4模拟器问题
第一步:随便打开Xcode项目 ,点击电脑屏幕右上角 Xcode->Preference 第二步: 点击下载ios 8.1 Simulator 等到下载完成即可在xcode中添加iphone4s ...
- 前端之javascript的DOM对象和标签
一 DOM对象介绍 什么是HTML DOM 1.1 HTML Document Object Model(文档对象模型) 1.2 HTML DOM 定义了访问和操作HTML文档的标准方法. 1.3 H ...
- oracle 查看表空间使用情况
查看表空间剩余: ||'M' from dba_free_space group by tablespace_name 查看表空间总大小.使用大小.剩余大小,使用率.剩余率 ) useded, tru ...
- Web 开发
Django(发音:[`dʒæŋɡəʊ]) 是一个开放源代码的Web应用框架,由Python写成.采用了MTV的框架模式,模型(Model).模板(Template)和视图(Views).
- 2018.12.30 洛谷P4238 【模板】多项式求逆
传送门 多项式求逆模板题. 简单讲讲? 多项式求逆 定义: 对于一个多项式A(x)A(x)A(x),如果存在一个多项式B(x)B(x)B(x),满足B(x)B(x)B(x)的次数小于等于A(x)A(x ...
- git安装项目步骤
1.git clone git@gitee(github).com:项目地址.git 2.cd 项目根目录 3.composer install 4.如果需要数据迁移,cmd中到项目根目录 php a ...
- yii2自定义json格式success,error跳转
/** * ---------------------------------------------- * 操作成功跳转的快捷方法 * @access protected * @param stri ...
- Linux 第三天
2.文件处理命令 1)touch 创建空文件 语法:touch文件名 2)cat 显示文件内容 英文原意:concatenate 语法:cat 文件名 常用选项: -n:number,显示行号 3)t ...
- idea在哪执行maven clean?
- jmeter读取csv文件
操作步骤: 1.读取csv文件 2.编辑httpSampler