OkHttp 如何在不使用线程的情况下通过看似同步的 HTTP 连接执行并行 HTTP 请求?

Posted

技术标签:

【中文标题】OkHttp 如何在不使用线程的情况下通过看似同步的 HTTP 连接执行并行 HTTP 请求?【英文标题】:How is OkHttp performing parallell HTTP Requests with seemingly synchronous HTTP connections, without the use of threading? 【发布时间】:2014-07-20 07:00:20 【问题描述】:

我对@9​​87654321@ 库进行了一些性能测试,发现它很棒。它向http://httpbin.org/delay/1 发出了 80 个请求,在我的 HTC One 手机上,它在 4.7 秒内故意为每个请求暂停 1 秒。我查看了代码并试图找出为什么它如此之快。开发人员(Square Inc)宣传连接池和异步调用,我相信这两者都有助于实现良好的性能。

我来自 .NET 世界,在 .NET 4.5 中,您拥有一个真正的异步 HTTP 库,其中包含 Async GetResponse-method。通过在等待响应时将线程让给操作系统,您可以释放资源以启动更多 HTTP 请求或其他内容。问题是我看不到与 OkHttp(或我研究过的任何其他 android 的 HTTP 库)相同的模式。那么我怎么还能在 4 秒内执行 80 个 1-second-request 呢?它不是基于线程的,对吧?我没有启动 80(或 20)个线程?

具体来说,在com.squareup.okhttp.Call.beginRequest() 中,我看到sendRequestgetResponse 调用之间的线程没有产生:

if (canceled) return null;

try 
    engine.sendRequest();

    if (request.body() != null) 
        BufferedSink sink = engine.getBufferedRequestBody();
        request.body().writeTo(sink);
    

    engine.readResponse();
 catch (IOException e) 
    HttpEngine retryEngine = engine.recover(e, null);
    if (retryEngine != null) 
        engine = retryEngine;
        continue;
    

    // Give up; recovery is not possible.
    throw e;


Response response = engine.getResponse();

那么,如何实现 80 个“并行”调用?

需要知道这一点才能使用该库,但异步编程让我很感兴趣,我真的很想了解 OkHttp/SquareInc 是如何解决这个问题的。

【问题讨论】:

您是一个接一个地发出请求还是一次发出所有请求?如果是一个接一个,那么这个必须花费>80s。如果一次全部完成,那么它可以任意快速下降到 1 秒。那么你使用多少线程来发出调用呢? 【参考方案1】:

我通过将 OkHttp 源链接到我的项目并将日志记录注入核心请求类 - Call.java 进行了一些测试。我发现 OkHttp 确实为每个调用使用了一个线程,并且在等待响应时 not 产生了,正如我错误地假设的那样。它比 Volley 更快的唯一原因是 Volley 硬编码了 4 个线程限制,而 OkHttp 使用 Integer.MAX_VALUEDipatcher.java 第 58 行):

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
      new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));

这是我“异步”排队和执行 80 个请求时的 LogCat 日志摘录:

05-31 12:15:23.884  27989-28025/nilzor.okhttp I/OKH﹕ Starting request 1
05-31 12:15:23.884  27989-28026/nilzor.okhttp I/OKH﹕ Starting request 2
05-31 12:15:24.044  27989-28199/nilzor.okhttp I/OKH﹕ Starting request 79
05-31 12:15:24.054  27989-28202/nilzor.okhttp I/OKH﹕ Starting request 80
05-31 12:15:25.324  27989-28025/nilzor.okhttp I/OKH﹕ Getting response 1 after 1436ms
05-31 12:15:26.374  27989-28026/nilzor.okhttp I/OKH﹕ Getting response 2 after 2451ms
05-31 12:15:27.334  27989-28199/nilzor.okhttp I/OKH﹕ Getting response 79 after 3289ms
05-31 12:15:26.354  27989-28202/nilzor.okhttp I/OKH﹕ Getting response 80 after 2305ms

xxxxx-yyyyy 格式的第三列表示进程 ID (x) 和线程 ID (y)。请注意每个请求如何获得自己的线程以及同一线程如何处理响应。 Full log。这意味着我们在等待响应时有 80 个阻塞线程,这不是 true 异步编程应该完成的方式。

在 OkHttp / Square Inc 的辩护中,他们从未声称拥有真正的端到端异步 HTTP 通信,他们只为消费者提供了一个异步接口。这很好。它的性能也很好,还能做很多其他的事情。这是一个很好的库,但我误解了它具有真正的异步 HTTP 通信。

我已经明白寻找关键字“NIO”来找到我正在寻找的东西。像 AndroidAsync 和 Ion 这样的库看起来很有希望。

【讨论】:

只是值得注意的是,您不应该将 Volley 与 OkHttp 进行比较。您应该在 OkHttp 之上使用 Volley。 当您调用 enqueue 时,它​​会检查最大总数和每个主机请求。然后它将请求添加到队列或直接执行它。见github.com/square/okhttp/blob/master/okhttp/src/main/java/… 也是一个 .net 开发新的 java & 如果我听到 async 立即假设 IO 完成端口 (windows) 和 WHATEVER / POSIX AIO (linux)?并且想知道 okhttp3 的文档对“在请求线程上处理响应”(WTF?)的含义以及它如何与 RxJava 调用适配器(它需要一个 RxScheduler)一起工作。终于找到了这一行github.com/square/retrofit/blob/… 和繁荣..他们在 rx 中同步调用,使用调度程序代替 okhttp3 调度程序 这不是真正的异步编程应该做的。【参考方案2】:

OkHttp 目前不使用异步套接字。如果您将异步 API 与 enqueue() 一起使用,Dispatcher 将启动多个线程并发出多个并发请求。如果您对所有请求使用相同的 OkHttp 客户端,它会将自身限制为每个主机 5 个连接。

【讨论】:

如果 OkHttp 将自身限制为每个主机 5 个连接,你如何解释我从一个总是在 4.7 秒内等待 1 秒才响应的主机收到 80 个回复? @JesseWilson 我猜这 5 个请求只有在您不通过 setMaxRequestsPerHost() 手动设置不同值的情况下才有效? 没错。你可以设置任何你想要的限制。 5 个连接实际上是指空闲连接。库本身不限制使用单个 okhttpclient 实例 github.com/square/okhttp/blob/master/okhttp/src/main/java/… 可以产生的连接数

以上是关于OkHttp 如何在不使用线程的情况下通过看似同步的 HTTP 连接执行并行 HTTP 请求?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不同步的情况下使用 Delphi (Pre Delphi 2010) 版本更新 GUI 控件

OkHttp使用学习

使用 OkHttp3 和 ReactiveX Java 实现长轮询的正确方法

如何在不使用 C++/C 中的阻塞函数的情况下将值从线程返回到主函数

如何在不等待 pthread_barrier_wait() 的情况下执行 pthread_barrier_destroy()

如何在不冻结 gui 的情况下运行 QProcess 的同步链?