使用异步HTTP提升客户端性能(HttpAsyncClient)

大家都知道,应用层的网络模型有同步、异步之分。

同步,意为着线程阻塞,只有等本次请求全部都完成了,才能进行下一次请求。
异步,好处是不阻塞当前线程,可以“万箭齐发”的将所有请求塞入缓冲区,然后谁的请求先完成就处理谁。

大家也注意到了,同步模式阻塞的只是“线程”。实际上,在异步模式流行之前,人们也经常用多线程的方式处理并发请求。然而,随着数据规模的不断加大,线程开销所带来的CPU、内存剧增,因此这种方法的应用比较有限。

近几年来,随着异步处理方案在node.js、Nginx等系统中的成功应用,异步模式的到了越来越多的关注。另外提一句:客户端与服务器端的异步处理是相互透明的,即允许客户端采用同步而服务器端采用异步。只是一般来说,异步的处理比同步要复杂许多。

下面回到实际问题上。
在近日的工作中,需要从Hadoop Job中调用一个Http计算服务以完成一些处理工作。我们使用了经典的HttpClient 3.x进行了实现。在一个HDFS文件分片上,性能数据大致是这样的:171237个文档、耗时305076ms。

备注:由于我们的Job跑在Hadoop上,在未来是会有N个Mapper同时运行,因此没有采用多线程的处理方式。

看看上面的数据,乍一看似乎还可以:平均每个文档的处理只需要1.8毫秒。然而从整个Map的角度来看,调用Http服务已经成为了整个Job的瓶颈,有必要进行一些优化。

在HttpClient进化到4.x后,官方提供了基于nio的异步版本:HttpAsyncClient。

这个异步版本的客户端,借助了Java并发库和nio进行封装,提供了非常方便的调用方式。

我们来看一下异步的代码:

Java
public void test2() throws InterruptedException, ExecutionException,
IOException {
CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault();
// Start the client
httpclient.start();

// Execute 100 request in async
final HttpGet request = new HttpGet(
"http://xxxx");
request.setHeader("Connection", "close");
List<Future<HttpResponse>> respList = new LinkedList<Future<HttpResponse>>();
for (int i = 0; i < 50; i++) {
respList.add(httpclient.execute(request, null));
}

// Print response code
for (Future<HttpResponse> response : respList) {
response.get().getStatusLine();
// System.out.println(response.get().getStatusLine());
}

httpclient.close();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void test2() throws InterruptedException, ExecutionException,
            IOException {
        CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault();
        // Start the client
        httpclient.start();
 
        // Execute 100 request in async
        final HttpGet request = new HttpGet(
                "http://xxxx");
        request.setHeader("Connection", "close");
        List<Future<HttpResponse>> respList = new LinkedList<Future<HttpResponse>>();
        for (int i = 0; i < 50; i++) {
            respList.add(httpclient.execute(request, null));
        }
 
        // Print response code
        for (Future<HttpResponse> response : respList) {
            response.get().getStatusLine();
            // System.out.println(response.get().getStatusLine());
        }
 
        httpclient.close();
    }

再来看一下同步的:

Java
public void test1() throws ClientProtocolException, IOException {
CloseableHttpClient httpclient = HttpClients.createDefault();

// Execute 500 request in async
for (int i = 0; i < 50; i++) {
HttpGet request = new HttpGet(
"http://xxxx");
request.setHeader("Connection", "close");
CloseableHttpResponse response = httpclient.execute(request);
// System.out.println(response.getStatusLine());
response.getStatusLine();
response.close();
}

httpclient.close();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public void test1() throws ClientProtocolException, IOException {
        CloseableHttpClient httpclient = HttpClients.createDefault();
 
        // Execute 500 request in async
        for (int i = 0; i < 50; i++) {
            HttpGet request = new HttpGet(
                    "http://xxxx");
            request.setHeader("Connection", "close");
            CloseableHttpResponse response = httpclient.execute(request);
            // System.out.println(response.getStatusLine());
            response.getStatusLine();
            response.close();
        }
 
        httpclient.close();
    }

不难发现,异步的代码使用了Future,使得最终的处理异常非常简单。

备注:我这里偷懒没有使用countdown latcher,所以future.get()实际还是会阻塞,但是发送http请求的client.execute阶段是非阻塞的。

下面来测一下性能。

我们采用了与Job中几乎相同的配置:每次batch发起50次请求,共50个batch。

结果如下:

Shell
async 2879 ms
sync 4190 ms
1
2
async 2879 ms
sync 4190 ms

使用异步请求的方式,比同步的时间节约了31%!

当然,尽管使用异步可以提升客户端调用的性能,但实际上是以提升并发为代价的,也就是latency和qps的关系。

换句话说,客户端异步没问题,但服务器端的性能必须跟的上,在我们的系统中,会通过控制batch的数量以及同时并发的mapper数量限制并发,以防止压垮服务器:-)

====2014.11.13 更新====

昨天忘记写如何获取返回的正文了,实际还是用Future返回的,补充如下:

Java
HttpResponse resp = respFuture.get();
InputStream input = resp.getEntity().getContent();
1
2
HttpResponse resp = respFuture.get();
InputStream input = resp.getEntity().getContent();

接下来,如何读取input的content就不再贴代码了。用IOUtil或者裸写BufferedInputStream/ByteArrayOutputStream都可以。

此外再补充下,实际线上效果比上面测试的要好,客户端大约节省了62%的时间开销。

使用异步HTTP提升客户端性能(HttpAsyncClient)的更多相关文章

  1. [.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能

    [.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能 本节导读: 上节说了缓存是以空间来换取时间的技术,介绍了客户端缓存和两种常用服务器缓布,本节主要介绍一种. ...

  2. 提升PHP性能的21种方法

    提升PHP性能的21种方法. 1.用单引号来包含字符串要比双引号来包含字符串更快一些.因为PHP会在双引号包围的字符串中搜寻变量,单引号则不会.2.如果能将类的方法定义成static,就尽量定义成st ...

  3. 极光开发者沙龙 之 移动应用性能优化实践 【一】旧酒新瓶——换个角度提升 App 性能与质量

    旧酒新瓶--换个角度提升 App 性能与质量 主讲人:高亮亮 ---   饿了么移动技术部高级iOS工程师,负责饿了么商家版iOS APP开发,对架构和系统底层有深入研究,擅长移动性能分析,troub ...

  4. 教你如何构建异步服务器和客户端的 Kotlin 框架 Ktor

    Ktor 是一个使用 Kotlin 以最小的成本快速创建 Web 应用程序的框架. Ktor 是一个用于在连接系统(connected systems)中构建异步服务器和客户端的 Kotlin 框架. ...

  5. 提升Web性能的8个技巧总结

    提升Web性能的8个技巧总结 在互联网盛行的今天,越来越多的在线用户希望得到安全可靠并且快速的访问体验.针对Web网页过于膨胀以及第三脚本蚕食流量等问题,Radware向网站运营人员提出以下改进建议, ...

  6. 【gRPC】C++异步服务端客户端API实例及代码解析

    对于同步API而言,程序的吞吐量并不高.因为在每次发送一个gRPC请求时,会阻塞整个线程,必须等待服务端的ack回到客户端才能继续运行或者发送下一个请求,因此异步API是提升程序吞吐量的必要手段. g ...

  7. Android客户端性能优化(魅族资深工程师毫无保留奉献)

    本文由魅族科技有限公司资深Android开发工程师degao(嵌入式企鹅圈原创团队成员)撰写,是degao在嵌入式企鹅圈发表的第一篇原创文章,毫无保留地总结分享其在领导魅族多个项目开发中的Androi ...

  8. HHVM 是如何提升 PHP 性能的?

    背景 HHVM 是 Facebook 开发的高性能 PHP 虚拟机,宣称比官方的快9倍,我很好奇,于是抽空简单了解了一下,并整理出这篇文章,希望能回答清楚两方面的问题: HHVM 到底靠谱么?是否可以 ...

  9. Web 2.0应用客户端性能问题十大根源《转载》

    前言 Web 2.0应用的推广为用户带来了全新的体验,同时也让开发人员更加关注客户端性能问题.最近,资深Web性能诊断专家.知名工具dynatrace的创始人之一Andreas Grabner根据自己 ...

随机推荐

  1. POJ 2115 C Looooops(扩展欧几里得应用)

    题目地址:POJ 2115 水题. . 公式非常好推.最直接的公式就是a+n*c==b+m*2^k.然后能够变形为模线性方程的样子,就是 n*c+m*2^k==b-a.即求n*c==(b-a)mod( ...

  2. 14.8.1 Enabling File Formats

    14.8 InnoDB File-Format Management 14.8.1 Enabling File Formats 14.8.2 Verifying File Format Compati ...

  3. hdu1695(莫比乌斯)或欧拉函数+容斥

    题意:求1-b和1-d之内各选一个数组成数对.问最大公约数为k的数对有多少个,数对是有序的.(b,d,k<=100000) 解法1: 这个能够简化成1-b/k 和1-d/k 的互质有序数对的个数 ...

  4. Delphi的TService的輸入桌面切換(服务程序)(windows登录界面如何截图)(使用了OpenDesktop和GetThreadDesktop等API)

    dfm: object CopyDeskService: TCopyDeskService  OldCreateOrder = False  OnCreate = ServiceCreate  OnD ...

  5. LA - 4043 - Ants

    题意:n只蚂蚁,n棵树,每只蚂蚁要连一棵树,连线(直线)不能相交,给出n只蚂蚁和n棵树的坐标,输出n只蚂蚁所配对的树的编号(1 <= n <= 100, -10000 <= 坐标x, ...

  6. Swift - 选择框(UIPickerView)的用法

    1,选择框可以让用户以滑动的方式选择值.示例如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ...

  7. haproxy 服务端超时时间 timeout server 17000 --后台程序17秒没有响应,返回超时

    haproxy 服务端超时时间: haproxy 配置: timeout server 17000 --后台程序17秒没有响应,返回超时 Jun 27 09:29:56 localhost hapro ...

  8. 使用VC++压缩解压缩文件夹

    前言   项目中要用到一个压缩解压缩的模块, 看了很多文章和源代码,  都不是很称心, 现在把我自己实现的代码和大家分享. 要求: 1.使用Unicode(支持中文). 2.使用源代码.(不使用静态或 ...

  9. JS跑马灯

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML>  <HEA ...

  10. linux 下opensplice的简易安装

    http://www.prismtech.com/opensplice/opensplice-dds-community/software-downloads 下载对应我选择的是: OpenSplic ...