使用异步任务降低API延迟_实践总结
之前在想如何降低API的延迟,这些API里有几个比较耗时的操作且是串行执行,那通过异步执行的方式理论上可以降低运行的时间,如下图所示:

具体的实现比较简单,例如这样:
public class ParallelRetrievalExample {
final CacheRetriever cacheRetriever;
final DBRetriever dbRetriever;
ParallelRetrievalExample(CacheRetriever cacheRetriever,
DBRetriever dbRetriever) {
this.cacheRetriever = cacheRetriever;
this.dbRetriever = dbRetriever;
}
public Object retrieveCustomer(final long id) {
final CompletableFuture<Object> cacheFuture =
CompletableFuture.supplyAsync(() -> {
return cacheRetriever.getCustomer(id);
});
final CompletableFuture<Object> dbFuture =
CompletableFuture.supplyAsync(() -> {
return dbRetriever.getCustomer(id);
});
return CompletableFuture.anyOf(
cacheFuture, dbFuture);
}
用java8引入的CompletableFuture即可。
这里不再赘述。
主要讲一下这样实践遇到的坑和一些自己的理解。
性能测试
优化后的代码需要和未修改(基准)的版本做比较,要考虑在不同负载下的性能情况。
针对API的修改可以使用AB工具,比较方便,能通过设定不同的并发用户模拟不同的负载。
测试是必要的,很多直觉上会提高性能的点可能会在实际表现上收到资源的限制等原因无法提高甚至不如优化前的性能。
适合处理的任务 & 线程池的设定
我们要优化怎样的任务呢?
任务也就三大分类,计算密集,IO密集和混合,其中混合里面也可以通过细化变为前两类。
在一般的web开发中计算不太会成为瓶颈,主要是IO。
一些耗时的阻塞IO操作(数据库,RPC调用)往往是导致接口慢的原因,这里要优化的就是这类操作。
不过与其说是优化,更恰当的说法是让这些阻塞操作异步化,缩短整体的时间,这里也要注意这些任务所在的位置,如果在API的最后面的逻辑里那优化他们也没什么必要,或者在不影响业务逻辑的情况下可以把他们置前。
我们需要的怎样的线程池?
如上所说要优化的任务几乎都是阻塞IO,也就意味着这些任务占用CPU的时间很短,主要是处在waiting状态下,这种线程的增加最大的开销就是内存,对上下文切换影响较小。
其次,线程数必定要有限,java的线程过于重量,不考虑CPU因素也需要考虑内存因素。
最后还要考虑线程池耗尽的情况,最差的情况是回到没优化之前,也就是在调用者线程上执行。
CompletableFuture的runAsync和supplyAsync方法有不带Executor的版本,首先看一下默认的线程池是否合适。
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
useCommonPool的判断是根据ForkJoinPool的并行度,可以简单地先认为多核下会返回true(也可以通过java.util.concurrent.ForkJoinPool.common.parallelism参数进行设定)。
而使用的commonPool()线程数量不是很多(默认和CPU核数相等),其次ForkJoinPool是设计用于短任务的运行,不适合做阻塞IO,我们要优化的主要慢操作几乎都是阻塞IO带来的。
接下去看需求比较接近的Executors.newFixedThreadPool,但通过实现不难发现他的队列是无界的,如果线程耗尽新的任务就会等待,也无法使用拒绝策略。
只有定制了,根据上面说到的需求,定制如下:
private static final ThreadPoolExecutor IO = new ThreadPoolExecutor(20, 20, 0, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
new CallerRunsPolicy());
线程数量定长,数量的多少可以根据测试情况做下调整,使用SynchronousQueue不产生队列,拒绝策略使用在调用者线程上运行,满足了所需。
这个线程池专门为IO密集任务使用,不要让计算密集的代码使用。
在实践中遇到了使用这种方式结果测试时性能降低了5倍左右的情况,一看代码中除了从数据库获取数据还有几个for循环在做修改字段的工作,导致上下文切换带来了很大的开销。
思考
上述实现中,限制线程数量的原因是因为线程的开销(这里主要是在内存上)过大,这就意味着在这里使用了线程过重了,更好的实现应该使用类似绿色线程的技术,和系统线程进行1对多的映射。
此外这种场景下用事件驱动的方式可能会更好。
追究其核心原因还是java世界中同步阻塞操作还是占多数,而主要的优化手段底层还是使用了昂贵的线程,一些在其他语言/平台上很容易实现的扩展在java上就会遇到问题。
此外,异步没有得到语言上的支持,造成异步编程在java上比较麻烦和显式,这点C#的async和await语法糖就要甜的多。
java之后的发展还是任重而道远啊。
参考资料
reactive design pattern 上述的图和ParallelRetrievalExample代码取自这里
使用异步任务降低API延迟_实践总结的更多相关文章
- java异步编程降低延迟
目录 java异步编程降低延迟 一.ExecutorService和CompletionService 二.CompletableFuture(重要) 三.stream中的parallel(并行流) ...
- 异步化DAO的设计和实践
目前,公司技术规划要求未来所有的服务要全面实现异步化接口,使得每个服务能达到1万/秒的单机性能.我们知道,在一个服务请求中,可能会调用其他服务,还会使用memcache.kv以及mysql等.目前,大 ...
- RESTful API 设计指南,RESTful API 设计最佳实践
RESTful API 设计指南,RESTful API 设计最佳实践 网络应用程序,分为前端和后端两个部分.当前的发展趋势,就是前端设备层出不穷(手机.平板.桌面电脑.其他专用设备......). ...
- RESTful API 设计最佳实践
背景 目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个"万能"的设计标准:如何鉴权?API ...
- ****RESTful API 设计最佳实践(APP后端API设计参考典范)
http://blog.jobbole.com/41233/ 背景 目前互联网上充斥着大量的关于RESTful API(为方便,下文中“RESTful API ”简写为“API”)如何设计的文章,然而 ...
- RESTful API 设计最佳实践(转)
摘要:目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API格式如何?你的API ...
- RESTful API 设计最佳实践(转)
背景 目前互联网上充斥着大量的关于RESTful API(为方便,下文中“RESTful API ”简写为“API”)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API 格式如何?你的 ...
- [转] 阿里研究员谷朴:API 设计最佳实践的思考
API是软件系统的核心,而软件系统的复杂度Complexity是大规模软件系统能否成功最重要的因素.但复杂度Complexity并非某一个单独的问题能完全败坏的,而是在系统设计尤其是API设计层面很多 ...
- 关于RestFul API 介绍与实践
之前演示的PPT,直接看图... •参考链接: •RESTful API 设计最佳实践 •RESTful API 设计指南 •SOAPwebserivce和RESTfulwebservice对 ...
随机推荐
- xtrabackup命令用法实战(转)
xtrabackup命令用法实战 转载出自 https://blog.csdn.net/wfs1994/article/details/80399408 完全备份 1.创建备份 [root@linux ...
- ssh 免密登录阿里云主机
在网上找了好几篇教程,都不好使. 终于在这篇找到了答案 解决方案: 在 sshd_config 里面将这一项: AuthorizedKeysFile .ssh/authorized_keys 被我修改 ...
- 【NIFI】 Apache NiFI 授权配置
当NIFI未配置需要单向SSL(例如LDAP,OpenId Connect等)的替代认证机制时,NiFi的Web服务器将要求访问用户界面的用户使用基于证书的客户端身份验证.启用备用身份验证机制会将We ...
- C#算法
递归 任何一个方法既可以调用其他方法又可以调用自己,而当这个方法调用自己时,我们就叫它递归函数或者递归方法! 通常递归有两个特点: 1.递归方法一直会调用自己直到某些条件满足,也就是说一定要有出口; ...
- jquery学习总结24-36
一.jquery的自定义事件 1.自定义事件不能通过eventName()来添加,只能通过on来绑定 2.自定义事件需要通过trigger(自动触发)来进行触发 二.jauery事件命名空间 1.事件 ...
- XNA、FNA以及在VS2017中编写XNA
XNA是早期DX SDK支持的C#游戏开发环境,虽然现游戏开发大多是以Unity3D,Unreal 4为主,但是许多独立游戏开发者,特别是2D游戏,依然使用XNA进行开发, 纠其原因,猜测是C#开源且 ...
- Web打印控件Lodop实现表格物流单的打印
Web打印控件Lodop实现表格物流单的打印 一.lodop打印预览效果图 LODOP.PRINT_SETUP();打印维护效果图 LODOP.PREVIEW();打印预览图 二.写在前面 最近项目用 ...
- Exp2后门原理与实践_20154305 _ 齐 帅
Exp2后门原理与实践 20154305 _ 齐 帅 2.1简单后门 一.后门工具介绍 1.netcat(nc.ncat) 是一个底层工具,进行基本的TCP UDP数据收发.常被与其他工具结合使用,起 ...
- wincc 用脚本记录日志
方法1: 在vbs脚本库里面添加 Sub LogTxt(message) Const ForReading = 1, ForWriting = 2, ForAppending = 8 Di ...
- Golang处理数据库的nil数据
在用golang获取数据库的数据的时候,难免会遇到可控field.这个时候拿到的数据如果直接用string, time.Time这样的类型来解析的话会遇到panic. 那么如何处理这个问题呢,第一个出 ...