之前在想如何降低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因素也需要考虑内存因素。

最后还要考虑线程池耗尽的情况,最差的情况是回到没优化之前,也就是在调用者线程上执行。

CompletableFuturerunAsyncsupplyAsync方法有不带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#asyncawait语法糖就要甜的多。

java之后的发展还是任重而道远啊。

参考资料

apache ab

reactive design pattern 上述的图和ParallelRetrievalExample代码取自这里

多线程的代价及上下文切换

Java CompletableFuture 详解

并发之痛 Thread,Goroutine,Actor

使用异步任务降低API延迟_实践总结的更多相关文章

  1. java异步编程降低延迟

    目录 java异步编程降低延迟 一.ExecutorService和CompletionService 二.CompletableFuture(重要) 三.stream中的parallel(并行流) ...

  2. 异步化DAO的设计和实践

    目前,公司技术规划要求未来所有的服务要全面实现异步化接口,使得每个服务能达到1万/秒的单机性能.我们知道,在一个服务请求中,可能会调用其他服务,还会使用memcache.kv以及mysql等.目前,大 ...

  3. RESTful API 设计指南,RESTful API 设计最佳实践

    RESTful API 设计指南,RESTful API 设计最佳实践 网络应用程序,分为前端和后端两个部分.当前的发展趋势,就是前端设备层出不穷(手机.平板.桌面电脑.其他专用设备......). ...

  4. RESTful API 设计最佳实践

    背景 目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个"万能"的设计标准:如何鉴权?API ...

  5. ****RESTful API 设计最佳实践(APP后端API设计参考典范)

    http://blog.jobbole.com/41233/ 背景 目前互联网上充斥着大量的关于RESTful API(为方便,下文中“RESTful API ”简写为“API”)如何设计的文章,然而 ...

  6. RESTful API 设计最佳实践(转)

    摘要:目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API格式如何?你的API ...

  7. RESTful API 设计最佳实践(转)

    背景 目前互联网上充斥着大量的关于RESTful API(为方便,下文中“RESTful API ”简写为“API”)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API 格式如何?你的 ...

  8. [转] 阿里研究员谷朴:API 设计最佳实践的思考

    API是软件系统的核心,而软件系统的复杂度Complexity是大规模软件系统能否成功最重要的因素.但复杂度Complexity并非某一个单独的问题能完全败坏的,而是在系统设计尤其是API设计层面很多 ...

  9. 关于RestFul API 介绍与实践

    之前演示的PPT,直接看图...     •参考链接: •RESTful API 设计最佳实践 •RESTful API 设计指南 •SOAPwebserivce和RESTfulwebservice对 ...

随机推荐

  1. ListView点击事件失效(item里面有button按钮控件)解决方法

    ListView点击事件失效解决方法: 一般出现这个情况,就是你的item里面有按钮的点击事件,你的item里面有button控件,button控件是抢占焦点的,只要在你的item布局里面这样子写就可 ...

  2. RequestMethod.Post&RequestMethod.GET

    1.GET和POST都是将数据送到服务器 2.GET通过URL请求传递用户的数据,将表单各字段名称以及内容,以成对的字符串连接,置于action所指程序的URL后:POST方法通过HTTP post ...

  3. H5新特性-canvas绘图--渐变对象路径(最复杂)--图片--变形操作

    今天的目标 3.1:canvas绘图--(重点掌握:渐变对象.路径.图片.变形) 3.2:canvas绘图--渐变对象 线性渐变: linearGradient 径向渐变: var g = ctx.c ...

  4. JupyterLab绘制:柱状图,饼状图,直方图,散点图,折线图

    JupyterLab绘图 喜欢python的同学,可以到 https://v3u.cn/(刘悦的技术博客) 里面去看看,爬虫,数据库,flask,Django,机器学习,前端知识点,JavaScrip ...

  5. 父组件传值给子组件的v-model属性

    父组件如何修改子组件中绑定的v-model属性 因为v-model属性是双向数据绑定,而vue的通信方式又是单向通信,所以,当子组件想要改变父组件传过来的值的属性时,就会报错,典型的就是父组件传值给子 ...

  6. Linux(Ubuntu-CentOS)

    2017.3.29 查看已安装软件版本 dpkg-query --list 2017.3.3 Ubuntu 14.04 安装 phpmyadmin make sure apache works wel ...

  7. windows mysql zip 安装

    https://www.cnblogs.com/iathanasy/p/8461429.html

  8. vue数据双向绑定

    Vue的双向绑定是通过数据劫持结合发布-订阅者模式实现的,即通过Object.defineProperty监听各个属性的setter,然后通知订阅者属性发生变化,触发相应的回调. 整个过程分为以下几步 ...

  9. Python基础理论 - Python简介

    1. Python介绍 -程序员减少开发成本 创业性公司 - Python使用较多,开发效率高 老牌大公司 - 有部门使用 -应用领域 •自动化运维 -- 安装Linux 自带 Python -- 现 ...

  10. eclipse里报:An internal error occurred during: "Building workspace". Java heap space(内存溢出)

    当在eclipse中的web工程中增加了extjs4,出现An internal error occurred during: "Building workspace".Java ...