1. 背景

  在业务处理完之后,需要调用其他系统的接口,将相应的处理结果通知给对方,若是同步请求,假如调用的系统出现异常或是宕机等事件,会导致自身业务受到影响,事务会一直阻塞,数据库连接不够用等异常现象,可以通过异步回调来防止阻塞,但异步的情况还存在一个问题,若调用一次不成功的话接下来怎么处理?这个地方就需要按时间梯度回调,比如前期按10s间隔回调,回调3次,若不成功按30s回调,回调2次,再不成功按分钟回调,依次类推……相当于给了对方系统恢复的时间,不可能一直处于异常或宕机等异常状态,若是再不成功可以再通过人工干预的手段去处理了,具体业务具体实现。

2. 技术实现

  大体实现思路如下图,此过程用到两个队列,当前队列和Next队列,当前队列用来存放第一次需要回调的数据对象,如果调用不成功则放入Next队列,按照制定的时间策略再继续回调,直到成功或最终持久化后人工接入处理。

  用到的技术如下:

  • http请求库,retrofit2
  • 队列,LinkedBlockingQueue
  • 调度线程池,ScheduledExecutorService

3. 主要代码说明

3.1 回调时间梯度的策略设计

采用枚举来对策略规则进行处理,便于代码上的维护,该枚举设计三个参数,级别、回调间隔、回调次数;

  1. /**
  2. * 回调策略
  3. */
  4. public enum CallbackType {
  5.  
  6. //等级1,10s执行3次
  7. SECONDS_10(1, 10, 3),
  8. //等级2,30s执行2次
  9. SECONDS_30(2, 30, 2),
  10. //等级3,60s执行2次
  11. MINUTE_1(3, 60, 2),
  12. //等级4,5min执行1次
  13. MINUTE_5(4, 300, 1),
  14. //等级5,30min执行1次
  15. MINUTE_30(5, 30*60, 1),
  16. //等级6,1h执行2次
  17. HOUR_1(6, 60*60, 1),
  18. //等级7,3h执行2次
  19. HOUR_3(7, 60*60*3, 1),
  20. //等级8,6h执行2次
  21. HOUR_6(8, 60*60*6, 1);
  22.  
  23. //级别
  24. private int level;
  25. //回调间隔时间 秒
  26. private int intervalTime;
  27. //回调次数
  28. private int count;
  29. }

3.2 数据传输对象设计

声明抽象父类,便于其他对象调用传输继承。

  1. /**
  2. * 消息对象父类
  3. */
  4. public abstract class MessageInfo {
  5.  
  6. //开始时间
  7. private long startTime;
  8. //更新时间
  9. private long updateTime;
  10. //是否回调成功
  11. private boolean isSuccess=false;
  12. //回调次数
  13. private int count=0;
  14. //回调策略
  15. private CallbackType callbackType;
  16. }

要传输的对象,继承消息父类;

  1. /**
  2. * 工单回调信息
  3. */
  4. public class WorkOrderMessage extends MessageInfo {
  5.  
  6. //车架号
  7. private String vin;
  8. //工单号
  9. private String workorderno;
  10. //工单状态
  11. private Integer status;
  12. //工单原因
  13. private String reason;
  14. //操作用户
  15. private Integer userid;
  16. }

3.3 调度线程池的使用

  1. //声明线程池,大小为16
  2. private ScheduledExecutorService pool = Executors.newScheduledThreadPool(16);

  3. ...略
  4.  
  5. while (true){
  6. //从队列获取数据,交给定时器执行
  7. try {
  8. WorkOrderMessage message = MessageQueue.getMessageFromNext();
  9. long excueTime = message.getUpdateTime()+message.getCallbackType().getIntervalTime()* 1000;
  10. long t = excueTime - System.currentTimeMillis();
  11. if (t/1000 < 5) {//5s之内将要执行的数据提交给调度线程池
  12. System.out.println("MessageHandleNext-满足定时器执行条件"+JSONObject.toJSONString(message));
  13. pool.schedule(new Callable<Boolean>() {
  14. @Override
  15. public Boolean call() throws Exception {
  16. remoteCallback(message);
  17. return true;
  18. }
  19. }, t, TimeUnit.MILLISECONDS);
  20. }else {
  21. MessageQueue.putMessageToNext(message);
  22. }
  23. } catch (InterruptedException e) {
  24. System.out.println(e);
  25. }
  26. }

3.4 retrofit2的使用,方便好用。

具体可查看官网相关文档进行了解,用起来还是比较方便的。http://square.github.io/retrofit/

retrofit初始化:

  1. import retrofit2.Retrofit;
  2. import retrofit2.converter.gson.GsonConverterFactory;
  3.  
  4. public class RetrofitHelper {
  5.  
  6. private static final String HTTP_URL = "http://baidu.com/";
  7. private static Retrofit retrofit;
  8.  
  9. public static Retrofit instance(){
  10. if (retrofit == null){
  11. retrofit = new Retrofit.Builder()
  12. .baseUrl(HTTP_URL)
  13. .addConverterFactory(GsonConverterFactory.create())
  14. .build();
  15. }
  16. return retrofit;
  17. }
  18. }

如果需要修改超时时间,连接时间等可以这样初始话,Retrofit采用OkHttpClient

  1. import okhttp3.OkHttpClient;
  2. import retrofit2.Retrofit;
  3. import retrofit2.converter.gson.GsonConverterFactory;
  4.  
  5. import java.util.concurrent.TimeUnit;
  6.  
  7. public class RetrofitHelper {
  8.  
  9. private static final String HTTP_URL = "http://baidu.com/";
  10. private static Retrofit retrofit;
  11.  
  12. public static Retrofit instance(){
  13. if (retrofit == null){
  14. retrofit = new Retrofit.Builder()
  15. .baseUrl(HTTP_URL)
  16. .client(new OkHttpClient.Builder()
  17. .connectTimeout(30, TimeUnit.SECONDS)//连接时间
  18. .readTimeout(30, TimeUnit.SECONDS)//读时间
  19. .writeTimeout(30, TimeUnit.SECONDS)//写时间
  20. .build())
  21. .addConverterFactory(GsonConverterFactory.create())
  22. .build();
  23. }
  24. return retrofit;
  25. }
  26. }

Retrofit使用通过接口调用,要先声明一个接口;

  1. import com.alibaba.fastjson.JSONObject;
  2. import com.woasis.callbackdemo.bean.WorkOrderMessage;
  3. import retrofit2.Call;
  4. import retrofit2.http.Body;
  5. import retrofit2.http.POST;
  6.  
  7. public interface WorkOrderMessageInterface {
  8.  
  9. @POST("/api")
  10. Call<JSONObject> updateBatteryInfo(@Body WorkOrderMessage message);
  11.  
  12. }

接口和实例对象准备好了,接下来就是调用;

  1. private void remoteCallback(WorkOrderMessage message){
  2. //实例接口对象
  3. WorkOrderMessageInterface workOrderMessageInterface = RetrofitHelper.instance().create(WorkOrderMessageInterface.class);
  4.  
  5. //调用接口方法
  6. Call<JSONObject> objectCall = workOrderMessageInterface.updateBatteryInfo(message);
  7. System.out.println("远程调用执行:"+new Date());
  8.  
  9. //异步调用执行
  10. objectCall.enqueue(new Callback<JSONObject>() {
  11. @Override
  12. public void onResponse(Call<JSONObject> call, Response<JSONObject> response) {
  13. System.out.println("MessageHandleNext****调用成功"+Thread.currentThread().getId());
  14. message.setSuccess(true);
  15. System.out.println("MessageHandleNext-回调成功"+JSONObject.toJSONString(message));
  16. }
  17.  
  18. @Override
  19. public void onFailure(Call<JSONObject> call, Throwable throwable) {
  20. System.out.println("MessageHandleNext++++调用失败"+Thread.currentThread().getId());
  21. //失败后再将数据放入队列
  22. try {
  23. //对回调策略初始化
  24. long currentTime = System.currentTimeMillis();
  25. message.setUpdateTime(currentTime);
  26. message.setSuccess(false);
  27. CallbackType callbackType = message.getCallbackType();
  28. //获取等级
  29. int level = CallbackType.getLevel(callbackType);
  30. //获取次数
  31. int count = CallbackType.getCount(callbackType);
  32. //如果等级已经最高,则不再回调
  33. if (CallbackType.HOUR_6.getLevel() == callbackType.getLevel() && count == message.getCount()){
  34. System.out.println("MessageHandleNext-等级最高,不再回调, 线下处理:"+JSONObject.toJSONString(message));
  35. }else {
  36. //看count是否最大,count次数最大则增加level
  37. if (message.getCount()<callbackType.getCount()){
  38. message.setCount(message.getCount()+1);
  39. }else {//如果不小,则增加level
  40. message.setCount(1);
  41. level += 1;
  42. message.setCallbackType(CallbackType.getTypeByLevel(level));
  43. }
  44. MessageQueue.putMessageToNext(message);
  45. }
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. System.out.println("MessageHandleNext-放入队列数据失败");
  49. }
  50. }
  51. });
  52. }

3.5结果实现

4.总结

本次实现了按照时间梯度去相应其他系统的接口,不再导致本身业务因其他系统的异常而阻塞。请大佬们看实现有没有不合理的地方,欢迎批评指正。

源码:https://github.com/liuzwei/callback-demo

Java按时间梯度实现异步回调接口的更多相关文章

  1. 第46天学习打卡(四大函数式接口 Stream流式计算 ForkJoin 异步回调 JMM Volatile)

    小结与扩展 池的最大的大小如何去设置! 了解:IO密集型,CPU密集型:(调优)  //1.CPU密集型 几核就是几个线程 可以保持效率最高 //2.IO密集型判断你的程序中十分耗IO的线程,只要大于 ...

  2. Future 异步回调 大起底之 Java Future 与 Guava Future

    目录 写在前面 1. Future模式异步回调大起底 1.1. 从泡茶的案例说起 1.2. 何为异步回调 1.2.1. 同步.异步.阻塞.非阻塞 1.2.2. 阻塞模式的泡茶案例图解 1.2.3. 回 ...

  3. java单元测试之如何实现异步接口的测试案例

    测试是软件发布的重要环节,单元测试在实际开发中是一种常用的测试方法,java单元测试主要用junit,最新是junit5,本人开发一般用junit4.因为单元测试能够在软件模块组合之前尽快发现问题,所 ...

  4. java 中的异步回调

    异步回调,本来在c#中是一件极为简单和优雅的事情,想不到在java的世界里,却如此烦琐,先看下类图: 先定义了一个CallBackTask,做为外层的面子工程,其主要工作为start 开始一个异步操作 ...

  5. Java异步回调

      作者:禅楼望月(http://www.cnblogs.com/yaoyinglong) 1.开始讲故事: 午饭的时候到了,可是天气太冷,根本不想出办公室的门,于是你拨通了某饭店的订餐电话“喂!你好 ...

  6. 【java回调】同步/异步回调机制的原理和使用方法

    回调(callback)在我们做工程过程中经常会使用到,今天想整理一下回调的原理和使用方法. 回调的原理可以简单理解为:A发送消息给B,B处理完后告诉A处理结果.再简单点就是A调用B,B调用A. 那么 ...

  7. 同步异步,异步回调,线程队列,线程时间Event

    同步异步-阻塞非阻塞 阻塞-非阻塞 指的是程序的运行状态 阻塞:当程序执行过程中遇到了IO操作,在执行IO操作时,程序无法继续执行其他代码,称为阻塞. 非阻塞:程序在正常运行没有遇到IO操作,或者通过 ...

  8. Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO

    Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO Java 非阻塞 IO 和异步 IO 转自https://www.javadoop.com/post/nio-and-aio 本系 ...

  9. 升级springboot导致的业务异步回调积压问题定位

    1. 起因 A与B云侧模块特性联调的过程中,端侧发现云侧返回有延迟的情况. 7月19日与A模块一起抓包初步判断,B业务有积压的情况. 7月18日已经转侧B业务现网版本,由于使用一套逻辑.故可能存在请求 ...

随机推荐

  1. 前端html的简单认识

    一.html 超文本标记语言 hypertext markup language 二.html的结构 三.html标签格式 1.标签由<>把关键字括起来 2.标签通常是成对出现的 , eg ...

  2. Hadoop3集群搭建之——配置ntp服务

    上篇: Hadoop3集群搭建之——虚拟机安装 Hadoop3集群搭建之——安装hadoop,配置环境 下篇: Hadoop3集群搭建之——hive安装 Hadoop3集群搭建之——hbase安装及简 ...

  3. 2019.01.19 codeforces893F.Subtree Minimum Query(线段树合并)

    传送门 线段树合并菜题. 题意简述:给一棵带点权的有根树,多次询问某个点ppp子树内距离ppp不超过kkk的点的点权最小值,强制在线. 思路: 当然可以用dfsdfsdfs序+主席树水过去. 然而线段 ...

  4. 牛客训练二:处女座的签到题(STL+精度+三角形求面积公式)

    题目链接:传送门 知识点: (1)三个点,三角形求面积公式 (2)精度问题: double 15-16位(参考文章) float 6-7位 long long 约20位 int 约10位 unsign ...

  5. nullptr(c++11)

    1.概念 用字面值常量nullptr来初始化或赋值来得到空指针 2.c++11之前使用NULL或0 1)NULL是一个宏定义(预处理变量),定义在cstdlib中,其值就是0:对于预处理变量,预处理器 ...

  6. C++STL容器重点

    string 查找和替换 vector 删除

  7. Silverlight中图片显示

    silverlight中显示一个图片有很多的中方法,xaml中的image控件或者自定编写程序来生成image控件. silverlight中显示的图片只能是Bitmap, JPG, PNG(64位颜 ...

  8. 延时、输入输出接口P0~P3

    1.寄存器 为了知道延时程序是如何工作的,我们必需首先了解延时程序中出现的一些符号,就从R1开始,R1被称之为工作寄存器.什么是工作寄存器呢?让我们从现实生活中来找找答案.如果出一道数学题:123+5 ...

  9. (转)WAMP多站点配置

    转自:http://wislab.net/archives/43.html Wamp正在被广泛使用,其傻瓜式的安装配置,使得我们可以得心应手地完成以往较为烦琐的服务器环境搭建过程,直接进入到网页程序的 ...

  10. day34(注解)

    注解 注解和注释的区别: 注释:是给程序员看的. 注解:是给虚拟机识别的. 注解的作用: 1.代替配置文件 2.标识一个方法,代替一个特殊功能. JDK当中的提供的注解: 标识重写方法 @Overri ...