用途
EventListener 是 OkHttp 用来以上帝视角来对请求指标进行收集,通过实现 EventListener 可以对整个请求请求链路进行埋点监控. 在高并发场景中常用来记录详细的处理耗时日志,进行耗时分析,以便对相应过程进行优化
实现
- 实现 EventListener , 因为需要对每个请求都进行监控, 所以需要有一个唯一的标志, 同时还需要这次请求的相关信息 RealCall
@Slf4j
public class HttpEventListener extends EventListener {
/**
* 每次请求的标识
*/
private final long callId;
/**
* 每次请求的开始时间,单位毫秒
*/
private final long callStartMill;
private StringBuilder sbLog;
private Call call;
public HttpEventListener(long callId, Call call) {
this.callId = callId;
this.callStartMill = System.currentTimeMillis();
this.call = call;
this.sbLog = new StringBuilder(call.request().url().toString()).append(" ").append(callId).append(":");
}
private void recordEventLog(String name) {
long elapseMill = System.currentTimeMillis() - callStartMill;
sbLog.append(name).append("=").append(elapseMill).append(";");
if (name.equalsIgnoreCase("callEnd") || name.equalsIgnoreCase("callFailed")) {
long totalCost = System.currentTimeMillis() - callStartMill;
//打印出每个步骤的时间点
log.info(sbLog.toString());
}
}
@Override
public void callStart(Call call) {
super.callStart(call);
recordEventLog("callStart");
}
@Override
public void dnsStart(Call call, String domainName) {
super.dnsStart(call, domainName);
recordEventLog("dnsStart");
}
...... 省略部分
@Override
public void callEnd(Call call) {
super.callEnd(call);
recordEventLog("callEnd");
}
}
- 实现 EventListener.Factory ,该接口用于为每一次请求创建一个 EventListener 对象
public static final Factory FACTORY = new Factory() {
final AtomicLong nextCallId = new AtomicLong(1L);
@Override
public EventListener create(Call call) {
long callId = nextCallId.getAndIncrement();
return new HttpEventListener(callId, call);
}
};
- 覆盖默认 Factory
OkHttpClient httpClient = new OkHttpClient.Builder()
.callTimeout(500, TimeUnit.MILLISECONDS)
.connectTimeout(500, TimeUnit.MILLISECONDS)
//......省略若干
.eventListenerFactory(HttpEventListener.FACTORY)
.build();
EventListener 各个事件介绍
- callStart(Call call)
在 http 客户端执行 enqueued or executed 方法时,将首先执行该方法,无论开启了重试都只执行一次 - dnsStart(Call call, String domainName)
- dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList)
dnsStart 和 dnsEnd 通常成对出现, 在与远程主机建立连接的时候被会触发,因为重试的原因,所以可能被调用多次 - connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy)
- connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol)
- connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol, IOException ioe)
connectStart 要么与 connectEnd成对出现, 要么与 connectFailed 成对出现. 在启动套接字连接之前调用 connectStart , 连接建立成功之后调用 connectEnd,失败则 connectFailed . 当失败时,如果有更多路由可用并且启用了重试策略, 那么 connectStart 和 connectFailed 可能会出现多次
注意:当复用连接池里的连接时, dnsStart、dnsEnd、 connectStart、connectEnd、connectFailed 均不会被触发
connectionAcquired(Call call, Connection connection)
当连接已经被获取到时, 该方法将被触发, 如果 call.request() 的响应是重定向到其他地址, 那么该方法将可能被调用多次connectionReleased(Call call, Connection connection)
当连接被关闭时, 该方法将被触发, 同 connectionAcquired , 如果 call.request() 的响应是重定向到其他地址, 那么该方法将可能被调用多次requestHeadersStart(Call call)
requestHeadersEnd(Call call, Request request)
requestBodyStart(Call call)
requestBodyEnd(Call call, long byteCount)
responseHeadersStart(Call call)
responseHeadersEnd(Call call, Response response)
responseBodyStart(Call call)
responseBodyEnd(Call call, long byteCount)
9 - 16 中的方法可能会因为 redirect 和 重试的原因执行多次, 并且都是成对出现
- callEnd(Call call)
- callFailed(Call call, IOException ioe)
与 callStart 成对出现, 当出现异常时, callFailed将被触发