一、前言

本章主要是承接上一篇讲CompletableFuture的文章,想了解的可以先去看看案例:

https://juejin.cn/post/7091132240574283813

CompletableFuture已经提供了串行、并行等常用异步编排的方案,但在细节上还是有许多不足,比如回调方面,编排复杂顺序方面,就捉襟见肘了。

之前我有关注过Gitee上star量还不错的一款开源工具AsyncTool:

https://gitee.com/jd-platform-opensource/asyncTool

是由京东零售的高级工程师编写的,提供了非常丰富的异步编排功能,并且经过了京东内部的测试,是对CompletableFuture的封装和补足,试用了一下挺不错。

二、用法

1、引入

1)、不推荐:maven引入,这个比较坑,客观原因经常会导致依赖下载不下来,不推荐使用;

2)、推荐:直接下载源码,因为代码量很少,就几个核心类和测试类。

如下图所示,把下载的源码拷贝进来即可,核心代码放到java目录里面,测试代码放到test目录里面。

2、编写worker

1)、worker是AsyncTool中的一个思想,专门来处理任务的,比如查询、rpc调用等耗时操作,一个任务就是一个worker;

2)、构建worker十分简单,只需要实现IWorker和ICallback接口即可;

3)、这里,我们承接上一篇文章的案例,分别创建查询二十四节气和查询星座的worker;

4)、其中begin方法是构建开始时会执行,result方法是获取到结果后会执行,action方法就是处理具体任务的地方,一般业务就在这里编写,defaultValue方法提供超时异常时返回的默认值。

1)、二十四节气worker

package com.example.async.worker;

import cn.hutool.http.HttpUtil;
import com.jd.platform.async.callback.ICallback;
import com.jd.platform.async.callback.IWorker;
import com.jd.platform.async.executor.timer.SystemClock;
import com.jd.platform.async.worker.WorkResult;
import com.jd.platform.async.wrapper.WorkerWrapper;
import lombok.extern.slf4j.Slf4j; import java.util.Map;
import java.util.concurrent.TimeUnit; /**
* <p>
* 二十四节气worker
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-27 18:01
*/
@Slf4j
public class TwentyFourWorker implements IWorker<String, String>, ICallback<String, String> { public static final String APPKEY = "xxxxxx";// 你的appkey
public static final String URL = "https://api.jisuapi.com/jieqi/query"; @Override
public void begin() {
// System.out.println(Thread.currentThread().getName() + "- start --" + System.currentTimeMillis());
} @Override
public void result(boolean success, String param, WorkResult<String> workResult) {
if (success) {
System.out.println("callback twentyFourWorker success--" + SystemClock.now() + "----" + workResult.getResult()
+ "-threadName:" +Thread.currentThread().getName());
} else {
System.err.println("callback twentyFourWorker failure--" + SystemClock.now() + "----" + workResult.getResult()
+ "-threadName:" +Thread.currentThread().getName());
}
} /**
* 查询二十四节气
*/
@Override
public String action(String object, Map<String, WorkerWrapper> allWrappers) {
String url = URL + "?appkey=" + APPKEY;
String result = HttpUtil.get(url); // 模拟时长
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
log.error("[二十四节气]>>>> 异常: {}", e.getMessage(), e);
} return result;
} @Override
public String defaultValue() {
return "twentyFourWorker";
}
}

2)、星座worker

package com.example.async.worker;

import cn.hutool.http.HttpUtil;
import com.jd.platform.async.callback.ICallback;
import com.jd.platform.async.callback.IWorker;
import com.jd.platform.async.executor.timer.SystemClock;
import com.jd.platform.async.worker.WorkResult;
import com.jd.platform.async.wrapper.WorkerWrapper;
import lombok.extern.slf4j.Slf4j; import java.util.Map;
import java.util.concurrent.TimeUnit; /**
* <p>
* 星座worker
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-27 18:01
*/
@Slf4j
public class ConstellationWorker implements IWorker<String, String>, ICallback<String, String> { public static final String APPKEY = "xxxxxx";// 你的appkey
public static final String URL = "https://api.jisuapi.com/astro/all"; @Override
public void begin() {
// System.out.println(Thread.currentThread().getName() + "- start --" + System.currentTimeMillis());
} @Override
public void result(boolean success, String param, WorkResult<String> workResult) {
if (success) {
System.out.println("callback constellationWorker success--" + SystemClock.now() + "----" + workResult.getResult()
+ "-threadName:" +Thread.currentThread().getName());
} else {
System.err.println("callback constellationWorker failure--" + SystemClock.now() + "----" + workResult.getResult()
+ "-threadName:" +Thread.currentThread().getName());
}
} /**
* 查询星座
*/
@Override
public String action(String object, Map<String, WorkerWrapper> allWrappers) {
String url = URL + "?appkey=" + APPKEY;
String result = HttpUtil.get(url); // 模拟异常
// int i = 1/0; // 模拟时长
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
log.error("[星座]>>>> 异常: {}", e.getMessage(), e);
} return result;
} @Override
public String defaultValue() {
return "constellationWorker";
}
}

3、异步编排

1)、新建一个AsyncToolService,在里面进行worker的声明、构建、编排;

2)、Async.beginWork就是执行异步任务,参数分别为超时时间和worker,其中超时时间可以自己设短一点看效果;

3)、最后封装结果返回即可,这里为演示案例节省时间直接用map返回。

package com.example.async.service;

import com.example.async.worker.ConstellationWorker;
import com.example.async.worker.TwentyFourWorker;
import com.jd.platform.async.executor.Async;
import com.jd.platform.async.wrapper.WorkerWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException; /**
* <p>
* AsyncTools服务
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-27 17:56
*/
@Service
@Slf4j
public class AsyncToolService { /**
* 异步返回结果
* ---- 方式:AsyncTool并行处理
*
* @return 结果
*/
public Map<String, Object> queryAsync() throws ExecutionException, InterruptedException {
// 声明worker
TwentyFourWorker twentyFourWorker = new TwentyFourWorker();
ConstellationWorker constellationWorker = new ConstellationWorker(); // 构建二十四节气worker
WorkerWrapper<String, String> twentyFourWrapper = new WorkerWrapper.Builder<String, String>()
.worker(twentyFourWorker)
.callback(twentyFourWorker)
.param("0")
.build(); // 构建星座worker
WorkerWrapper<String, String> constellationWrapper = new WorkerWrapper.Builder<String, String>()
.worker(constellationWorker)
.callback(constellationWorker)
.param("1")
.build(); // 开始工作,这里设定超时时间10s,测试时可以设短一点看效果。
Async.beginWork(10000, twentyFourWrapper, constellationWrapper); // 打印当前线程数
log.debug("----------------- 当前线程数 ----------------");
log.debug(Async.getThreadCount()); // 打印结果
log.debug("----------------- 二十四节气 ----------------");
log.debug("结果: {}", twentyFourWrapper.getWorkResult());
log.debug("----------------- 星座 ----------------");
log.debug("结果: {}", constellationWrapper.getWorkResult()); // 返回
Map<String, Object> map = new HashMap<>();
map.put("twentyFour", twentyFourWrapper.getWorkResult());
map.put("constellation", constellationWrapper.getWorkResult()); // 关闭(spring web类应用不用关闭,否则第二次执行会报线程池异常。)
// Async.shutDown(); return map;
}
}

4、测试效果

上一篇的案例有演示同步执行的结果,在10秒左右,而CompletableFuture的结果在5秒多点。

这里测试后发现,AsyncTool的结果也是5秒左右,和CompletableFuture差不多,但AsyncTool提供的编排更丰富。

我们把其中一个星座worker的任务耗时调大,模拟一下超时的效果。可以发现,AsyncTool直接返回了我们上面defaultValue方法中设置的默认值。

三、常用编排

AsyncTool其实提供了很丰富的异步编排方式,包括较复杂的编排,但以我呆过的中小企业为例,几乎用不到复杂编排,最常用的的还是并行以及串行+并行。

AsyncTool的QuickStart.md已经做了简洁的说明:https://gitee.com/jd-platform-opensource/asyncTool/blob/master/QuickStart.md

1)、任务并行

也就是本篇我们案例使用的编排,是我个人平时最常用的一种。

2)、串行+并行

这种其实就是通过next()来做串行和并行的衔接,有些场景也会用到。

3)、依赖其他任务的结果

这也是很常见的场景,B任务依赖A任务的结果来实现业务,最后返回。

AsyncTool也提供了很方便的方式:

1)、在service中构建worker-A时设置一个id名称;

2)、你可以发现worker的action方法第二个入参是个map,里面就是所有的wrapper;

3)、在worker-B的action方法中get这个id来获取wrapper从而拿到A的结果即可。

四、避坑经验

1、勿关闭线程池

AsyncTool提供了很多测试类,里面包含了所有编排方式,可以一一查看并验证,但使用过程中要注意一点,如果是spring-web项目,比如springboot,不需要手动Async.shutdown()处理,否则会执行一次后就关闭线程池了,这是不少人直接拷贝test代码疏忽的地方。

2、自定义线程池

这个问题可以在AsyncTool的issue中看到相关讨论,作者君是根据京东零售的业务来决定使用什么线程池的,他们使用的默认线程池就是newCachedThreadPool,无限制长度的线程池,且具备复用特性,按照作者君的说法,因为京东的场景多数为低耗时(10ms)高并发,瞬间冲击的场景,所以最适合这种线程池。

而根据我的经验,不同公司的业务和项目都不同,中小企业往往依靠企事业单位生存,对接第三方厂家较多,rpc接口耗时往往较长且不可控,不符合京东零售低耗时高并发的特点,直接使用Integer.MAX_VALUE的无限制核心线程数的方式不太合适。

我建议中小企业使用自定义线程池,根据自身硬件水平和压测结果调整最终核心线程数和任务队列长度,确定合适的拒绝策略,比如直接拒绝或走主线程,这样会比较稳妥。

五、示例代码

完整示例代码提供给大家,里面有我的极速数据API的key,每天100次免费调用,省去注册账号,先到先测,慢点就只能等明天了哦。

链接:https://pan.baidu.com/doc/share/kJyph2LX076okHVWv38tlw-159275174957933

提取码:yqms


原创文章纯手打,觉得有点帮助就请点个推荐吧。



不定期分享工作经验和趣事,喜欢的话就请关注一下吧。

【Java分享客栈】一文搞定京东零售开源的AsyncTool,彻底解决异步编排问题。的更多相关文章

  1. 【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。

    前言   工作中你可能会遇到很多这样的场景,一个接口,要从其他几个service调用查询方法,分别获取到需要的值之后再封装数据返回.   还可能在微服务中遇到类似的情况,某个服务的接口,要使用好几次f ...

  2. 一文搞定 SonarQube 接入 C#(.NET) 代码质量分析

    1. 前言 C#语言接入Sonar代码静态扫描相较于Java.Python来说,相对麻烦一些.Sonar检测C#代码时需要预先编译,而且C#代码必须用MSbuid进行编译,如果需要使用SonarQub ...

  3. 【Java分享客栈】我为什么极力推荐XXL-JOB作为中小厂的分布式任务调度平台

    前言   大家好,我是福隆苑居士,今天给大家聊聊XXL-JOB的使用.   XXL-JOB是本人呆过的三家公司都使用到的分布式任务调度平台,前两家都是服务于传统行业(某大型移动基地和某大型电网),现在 ...

  4. 【Java分享客栈】SpringBoot线程池参数搜一堆资料还是不会配,我花一天测试换你此生明白。

    一.前言   首先说一句,如果比较忙顺路点进来的,可以先收藏,有时间或用到了再看也行:   我相信很多人会有一个困惑,这个困惑和我之前一样,就是线程池这个玩意儿,感觉很高大上,用起来很fashion, ...

  5. 【Java分享客栈】SpringBoot整合WebSocket+Stomp搭建群聊项目

    前言 前两周经常有大学生小伙伴私信给我,问我可否有偿提供毕设帮助,我说暂时没有这个打算,因为工作实在太忙,现阶段无法投入到这样的领域内,其中有两个小伙伴又问到我websocket该怎么使用,想给自己的 ...

  6. 【Java分享客栈】超简洁SpringBoot使用AOP统一日志管理-纯干货干到便秘

    前言 请问今天您便秘了吗?程序员坐久了真的会便秘哦,如果偶然点进了这篇小干货,就麻烦您喝杯水然后去趟厕所一边用左手托起对准嘘嘘,一边用右手滑动手机看完本篇吧. 实现 本篇AOP统一日志管理写法来源于国 ...

  7. Java性能调优攻略全分享,5步搞定!(附超全技能图谱)

    对于很多研发人员来说,Java 性能调优都是很头疼的问题,为什么这么说?如今,一个简单的系统就囊括了应用程序.数据库.容器.操作系统.网络等技术,线上一旦出现性能问题,就可能要你协调多方面组件去进行优 ...

  8. 一文搞定MySQL的事务和隔离级别

    一.事务简介 事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成. 一个数据库事务通常包含了一个序列的对数据库的读/写操作.它的存在包含有以下两个目的: 为数据库操作序列提供 ...

  9. 一文搞定scrapy爬取众多知名技术博客文章保存到本地数据库,包含:cnblog、csdn、51cto、itpub、jobbole、oschina等

    本文旨在通过爬取一系列博客网站技术文章的实践,介绍一下scrapy这个python语言中强大的整站爬虫框架的使用.各位童鞋可不要用来干坏事哦,这些技术博客平台也是为了让我们大家更方便的交流.学习.提高 ...

随机推荐

  1. springboot项目配置类

    一.在springboot项目中,如果不进行配置,直接访问静态页面是无法访问的,需要进行配置,springboot舍弃了XML文件的配置方式,这里我们采用开发配置类的方式.新建MvcConfig类,加 ...

  2. mybatis中jdbcType和javaType

    1.MyBatis 通过包含的jdbcType类型 BIT.FLOAT.CHAR .TIMESTAMP . OTHER .UNDEFINEDTINYINT .REAL .VARCHAR .BINARY ...

  3. 为什么 Thread 类的 sleep()和 yield ()方法是静态的?

    Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行.所以在其他处于等待状态的线程上调用这些方法是没有意义的.这就是为什么这些方法是静态的.它们可以在当前正在执行的线程 ...

  4. 学习 Haproxy (四)

    一. haproxy 的安装配置 # cat /etc/redhat-release CentOS release 6.6 (Final) # uname -r 2.6.32-504.el6.i686 ...

  5. Volcano:在离线作业混部管理平台,实现智能资源管理和作业调度

    摘要:本文结合华为CCE团队在混合部署方面的研究和实战,介绍了混合部署的背景.概念.混部技术的设计方案和实际落地情况,以及对未来的计划和展望. 现代互联网数据中心的规模随着应用服务需求的快速增长而不断 ...

  6. 动态规划 洛谷P1048 [NOIP2005 普及组] 采药

    洛谷P1048 [NOIP2005 普及组] 采药 洛谷的一个谱架-的题目,考的是01背包问题,接下来分享一下我的题解代码. AC通过图: 我的代码: 1 //动态规划 洛谷P1048 [NOIP20 ...

  7. 联想电脑“此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态” 解决方法

    当在虚拟机上安装Ubuntu系统时,出现 "此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态" 弹窗此时需要进入BIOS修改相关的设置,此处以联想ideap ...

  8. 用纯RUST手撸一个开源流媒体服务(RTMP/HTTPFLV/HLS)XIU

    作者工作目前在音视频流媒体行业,用了大概一年的业余时间学习Rust,并且实现了一个简单的音视频流媒体服务,虽然据说Rust已经连续多年被评为最受程序员喜欢的语言,但是在国内还是比较冷门,作者比较看好R ...

  9. java中到底什么是继承?

    1.何为继承?What is Inheritance? 在上图中,对于车来讲,汽车就是子类.对于汽车来讲,奔驰就是子类.车是汽车的基类,超类,或说父类.到底什么是继承?马克-to-win,子类把父类的 ...

  10. 下载jar包方法

    第一种通用下载jar包方法 apache官网下载jar包地址:http://ftp.cuhk.edu.hk/pub/packages/apache.org/   第二种通用下载jar包方法  mave ...