使用异步任务降低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对 ...
随机推荐
- svn2个小问题的解决
Revision file (r615) lacks trailing newline /svndata/your_project/db/revs /svndata/your_project/db/r ...
- Python爬虫学习--用Python结合Selenium实现 明日之子节目直播时为自己喜欢的选手自动点赞拉票!!!
声明:本脚本纯属娱乐,请勿用来非法点赞拉票,任何使用不当造成的后果自行承担. 闲话: 明日之子第二季开始好久了,作者一直再追,特别喜欢里面那个酷酷的小哥-蔡泽明.前两天晋选9大厂牌,采取的是直播的形式 ...
- dubbo入门学习 二 RPC框架
rpc框架解释 谁能用通俗的语言解释一下什么是 RPC 框架? - 远程过程调用协议RPC(Remote Procedure Call Protocol) 首先了解什么叫RPC,为什么要RPC,RPC ...
- 口试Linq题
LINQ to SQL与IQueryable 理解IQueryable的最简单方式就是,把它看作一个查询,在执行的时候,将会生成结果序列. LINQ to Object和LINQ to SQL有何区别 ...
- Eclipse 安装 AmaterasUML 插件
网上很多Eclipse 安装UML插件教程,可能对高版本Eclipse都无法安装成功,本文提供的安装方式,亲测可用. 一.安装GEF插件 1.打开eclipse官网 https://www.eclip ...
- Python 打印矩形、直角三角形、等腰三角形、菱形
# 1)打印一个星号 print('*') #2)打印一行6个星号 * * * * * * for i in range(6): print('*',end=' ') #3)打印6列星号 * * * ...
- C语言在宏定义中使用语句表达式和预处理器运算符
语句表达式的亮点在于定义复杂功能的宏.使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的歧义和漏洞.下面以一个简单的最小值的宏为例子一步步说明. 1.灰常简单的么,使用条件运算符 ...
- 解决在静态页面上使用动态参数,造成spider多次和重复抓取的问题
我们在使用百度统计中的SEO建议检查网站时,总是发现“静态页参数”一项被扣了18分,扣分原因是“在静态页面上使用动态参数,会造成spider多次和重复抓取”.一般来说静态页面上使用少量的动态参数的话并 ...
- usb2.0、usb3.0、usb3.1、type-c 接口含义与区别
简单说: usb3.0 比2.0的传输速率快,充电快,能向下兼容2.0 usb3.1 通常是指 usb3.1 gen2,比3.0的传输速率更快.充电更快,同兼容 type-c 通常是指 usb3.1的 ...
- jquery1.6中的.prop()和.attr()异同
jquery jQueryHTML5JavaScript浏览器ITeye 最近在iteye的新闻中看到jQuery已经更新到了1.6.1.和之前版本的最大变化是增加了.prop方法.但是.prop( ...