
最近在给 opentelemetry-java-instrumentation
提交了一个 PR,是关于给 gRPC 新增四个 metrics:
rpc.client.request.size
: 客户端请求包大小rpc.client.response.size
:客户端收到的响应包大小rpc.server.request.size
:服务端收到的请求包大小rpc.server.response.size
:服务端响应的请求包大小
这个 PR 的主要目的就是能够在指标监控中拿到 RPC
请求的包大小,而这里的关键就是如何才能拿到这些包的大小。
首先支持的是 gRPC
(目前在云原生领域使用的最多),其余的 RPC 理论上也是可以支持的:
在实现的过程中我也比较好奇 OpenTelemetry
框架是如何给 gRPC
请求创建 span
调用链的,如下图所示:
这是一个 gRPC 远程调用,java-demo 是 gRPC 的客户端,k8s-combat 是 gRPC 的服务端
在开始之前我们可以根据 OpenTelemetry
的运行原理大概猜测下它的实现过程。
首先我们应用可以创建这些链路信息的前提是:使用了 OpenTelemetry
提供的 javaagent
,这个 agent 的原理是在运行时使用了 byte-buddy 增强了我们应用的字节码,在这些字节码中代理业务逻辑,从而可以在不影响业务的前提下增强我们的代码(只要就是创建 span、metrics 等数据)
Spring 的一些代理逻辑也是这样实现的
gRPC 增强原理
而在工程实现上,我们最好是不能对业务代码进行增强,而是要找到这些框架提供的扩展接口。
拿 gRPC
来说,我们可以使用它所提供的 io.grpc.ClientInterceptor
和 io.grpc.ServerInterceptor
接口来增强代码。
打开 io.opentelemetry.instrumentation.grpc.v1_6.TracingClientInterceptor
类我们可以看到它就是实现了 io.grpc.ClientInterceptor
:
而其中最关键的就是要实现 io.grpc.ClientInterceptor#interceptCall
函数:
@Override
public <REQUEST, RESPONSE> ClientCall<REQUEST, RESPONSE> interceptCall(
MethodDescriptor<REQUEST, RESPONSE> method, CallOptions callOptions, Channel next) {
GrpcRequest request = new GrpcRequest(method, null, null, next.authority());
Context parentContext = Context.current();
if (!instrumenter.shouldStart(parentContext, request)) {
return next.newCall(method, callOptions);
}
Context context = instrumenter.start(parentContext, request);
ClientCall<REQUEST, RESPONSE> result;
try (Scope ignored = context.makeCurrent()) {
try {
// call other interceptors
result = next.newCall(method, callOptions);
} catch (Throwable e) {
instrume