跳到内容

事件

事件允许你捕获应用程序 HTTP 调用的指标。使用事件来监控

  • 应用程序发出的 HTTP 调用的规模和频率。如果你的调用太多,或者调用的数据量太大,你应该了解这一点!
  • 这些调用在底层网络上的性能。如果网络性能不足,你需要改进网络或者减少使用量。

EventListener

子类化 EventListener 并覆盖你感兴趣的事件方法。在没有重定向或重试的成功 HTTP 调用中,事件序列按此流程描述。

Events Diagram

这是一个示例事件监听器,它会打印每个事件并附带时间戳。

class PrintingEventListener extends EventListener {
  private long callStartNanos;

  private void printEvent(String name) {
    long nowNanos = System.nanoTime();
    if (name.equals("callStart")) {
      callStartNanos = nowNanos;
    }
    long elapsedNanos = nowNanos - callStartNanos;
    System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name);
  }

  @Override public void callStart(Call call) {
    printEvent("callStart");
  }

  @Override public void callEnd(Call call) {
    printEvent("callEnd");
  }

  @Override public void dnsStart(Call call, String domainName) {
    printEvent("dnsStart");
  }

  @Override public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
    printEvent("dnsEnd");
  }

  ...
}

我们进行几次调用

Request request = new Request.Builder()
    .url("https://publicobject.com/helloworld.txt")
    .build();

System.out.println("REQUEST 1 (new connection)");
try (Response response = client.newCall(request).execute()) {
  // Consume and discard the response body.
  response.body().source().readByteString();
}

System.out.println("REQUEST 2 (pooled connection)");
try (Response response = client.newCall(request).execute()) {
  // Consume and discard the response body.
  response.body().source().readByteString();
}

然后监听器打印相应的事件

REQUEST 1 (new connection)
0.000 callStart
0.010 dnsStart
0.017 dnsEnd
0.025 connectStart
0.117 secureConnectStart
0.586 secureConnectEnd
0.586 connectEnd
0.587 connectionAcquired
0.588 requestHeadersStart
0.590 requestHeadersEnd
0.591 responseHeadersStart
0.675 responseHeadersEnd
0.676 responseBodyStart
0.679 responseBodyEnd
0.679 connectionReleased
0.680 callEnd
REQUEST 2 (pooled connection)
0.000 callStart
0.001 connectionAcquired
0.001 requestHeadersStart
0.001 requestHeadersEnd
0.002 responseHeadersStart
0.082 responseHeadersEnd
0.082 responseBodyStart
0.082 responseBodyEnd
0.083 connectionReleased
0.083 callEnd

注意第二次调用没有触发连接事件。它重用了第一次请求的连接,显著提升了性能。

EventListener.Factory

在前面的示例中,我们使用字段 callStartNanos 来跟踪每个事件的耗时。这很方便,但如果多个调用并发执行则无法正常工作。为了解决这个问题,使用 Factory 为每个 Call 创建一个新的 EventListener 实例。这允许每个监听器保持特定于调用的状态。

这个示例工厂为每个调用创建一个唯一的 ID,并在日志消息中使用该 ID 来区分调用。

class PrintingEventListener extends EventListener {
  public static final Factory FACTORY = new Factory() {
    final AtomicLong nextCallId = new AtomicLong(1L);

    @Override public EventListener create(Call call) {
      long callId = nextCallId.getAndIncrement();
      System.out.printf("%04d %s%n", callId, call.request().url());
      return new PrintingEventListener(callId, System.nanoTime());
    }
  };

  final long callId;
  final long callStartNanos;

  public PrintingEventListener(long callId, long callStartNanos) {
    this.callId = callId;
    this.callStartNanos = callStartNanos;
  }

  private void printEvent(String name) {
    long elapsedNanos = System.nanoTime() - callStartNanos;
    System.out.printf("%04d %.3f %s%n", callId, elapsedNanos / 1000000000d, name);
  }

  @Override public void callStart(Call call) {
    printEvent("callStart");
  }

  @Override public void callEnd(Call call) {
    printEvent("callEnd");
  }

  ...
}

我们可以使用此监听器来竞速一对并发 HTTP 请求

Request washingtonPostRequest = new Request.Builder()
    .url("https://www.washingtonpost.com/")
    .build();
client.newCall(washingtonPostRequest).enqueue(new Callback() {
  ...
});

Request newYorkTimesRequest = new Request.Builder()
    .url("https://www.nytimes.com/")
    .build();
client.newCall(newYorkTimesRequest).enqueue(new Callback() {
  ...
});

在家用 WiFi 上运行这场竞速显示,Times (0002) 比 Post (0001) 稍早完成。

0001 https://www.washingtonpost.com/
0001 0.000 callStart
0002 https://www.nytimes.com/
0002 0.000 callStart
0002 0.010 dnsStart
0001 0.013 dnsStart
0001 0.022 dnsEnd
0002 0.019 dnsEnd
0001 0.028 connectStart
0002 0.025 connectStart
0002 0.072 secureConnectStart
0001 0.075 secureConnectStart
0001 0.386 secureConnectEnd
0002 0.390 secureConnectEnd
0002 0.400 connectEnd
0001 0.403 connectEnd
0002 0.401 connectionAcquired
0001 0.404 connectionAcquired
0001 0.406 requestHeadersStart
0002 0.403 requestHeadersStart
0001 0.414 requestHeadersEnd
0002 0.411 requestHeadersEnd
0002 0.412 responseHeadersStart
0001 0.415 responseHeadersStart
0002 0.474 responseHeadersEnd
0002 0.475 responseBodyStart
0001 0.554 responseHeadersEnd
0001 0.555 responseBodyStart
0002 0.554 responseBodyEnd
0002 0.554 connectionReleased
0002 0.554 callEnd
0001 0.624 responseBodyEnd
0001 0.624 connectionReleased
0001 0.624 callEnd

EventListener.Factory 还使得将指标限制在一部分调用成为可能。这个示例捕获随机 10% 调用的指标

class MetricsEventListener extends EventListener {
  private static final Factory FACTORY = new Factory() {
    @Override public EventListener create(Call call) {
      if (Math.random() < 0.10) {
        return new MetricsEventListener(call);
      } else {
        return EventListener.NONE;
      }
    }
  };

  ...
}

带失败的事件

当操作失败时,会调用一个失败方法。在建立到服务器的连接失败时调用 connectFailed(),当 HTTP 调用永久失败时调用 callFailed()。发生失败时,可能存在 start 事件没有对应的 end 事件。

Events Diagram

带重试和后续操作的事件

OkHttp 具有弹性,可以自动从某些连接失败中恢复。在这种情况下,connectFailed() 事件不是终结性的,其后不会跟着 callFailed()。尝试重试时,事件监听器会收到多个相同类型的事件。

单个 HTTP 调用可能需要发起后续请求来处理身份验证质询、重定向和 HTTP 层超时。在这种情况下,可能会尝试多个连接、请求和响应。后续操作是单个调用可能触发多个相同类型事件的另一个原因。

Events Diagram

可用性

事件功能在 OkHttp 3.11 中作为公共 API 提供。未来版本可能会引入新的事件类型;你需要覆盖相应的方法来处理它们。