通过OpenTelemetry Java SDK实现跨进程上下文传递

接入ARMS应用监控以后,ARMS探针对常见的Java框架进行了自动埋点,因此不需要修改任何代码,就可以实现调用链信息的采集。但如果您需要跨进程传递上下文,可以引入OpenTelemetry Java SDK实现。

ARMS探针支持的组件和框架,请参见ARMS应用监控支持的Java组件和框架

使用场景

使用私有协议跨进程网络通信时,通常客户端和服务端无法在链路中串连,这种场景需要用户自行在客户端获取Trace上下文(traceId、spanId、sampleFlag、baggage等信息)并自行传递到服务端后在服务端还原。

前提条件

引入依赖

请先参考如下Maven代码引入OpenTelemetry Java SDK。更多信息,请参见OpenTelemetry官方文档

<dependencies>
    <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
    </dependency>
    <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk-trace</artifactId>
    </dependency>
    <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-bom</artifactId>
      <version>1.23.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

跨进程传递上下文

下方代码中,CustomRpcCallMessage代表了用户私有协议中跨进程通信的数据承载实体。主要利用其中的headerMap传递上下文信息,如果实际的跨进程调用数据承载实体没有该字段,则需要手动修改代码支持该字段。

class CustomRpcCallMessage {

    private Map<String, String> headerMap;
    //省略其他业务字段

    public String getHeader(String key) {
        return headerMap.get(key);
    }

    public void setHeader(String key, String value) {
        this.headerMap.put(key, value);
    }

    public Map<String, String> getHeaderMap() {
        return headerMap;
    }
}

public class CrossProcessPropagateDemo {
    public static void clientSide(String[] args) {
        CustomRpcCallMessage rpcCall = new CustomRpcCallMessage();
        TextMapSetter<CustomRpcCallMessage> setter = new TextMapSetter<CustomRpcCallMessage>() {
            @Override
            public void set(CustomRpcCallMessage carrier, String key, String value) {
                carrier.setHeader(key, value);
            }
        };
        W3CTraceContextPropagator.getInstance().inject(Context.current(), rpcCall, setter);
        W3CBaggagePropagator.getInstance().inject(Context.current(), rpcCall, setter);
    }

    private static void serverSide() {
        //服务端获取请求
        CustomRpcCallMessage rpcCall ;
        TextMapGetter<CustomRpcCallMessage> getter = new TextMapGetter<CustomRpcCallMessage>() {
            @Override
            public Iterable<String> keys(CustomRpcCallMessage carrier) {
                return carrier.getHeaderMap().keySet();
            }

            @Override
            public String get(CustomRpcCallMessage carrier, String key) {
                return carrier.getHeader(key);
            }
        };
        Context context = W3CTraceContextPropagator.getInstance().extract(Context.current(), rpcCall, getter);
        context = W3CBaggagePropagator.getInstance().extract(context, rpcCall, getter);
        try (Scope scope = context.makeCurrent()) {
            //省略 服务端处理逻辑 
        }
    }
}

在实际的业务场景中,您只需要改造上面代码片段中的setter变量和getter变量的实现类,使得其对应的Set、Get方法可以保证能实现对应的语义,其余部分代码为模板代码可以直接使用。

相关文档

您可以在应用的业务日志中关联调用链的TraceId信息,从而在应用出现问题时,能够通过调用链的TraceId快速关联到业务日志,及时定位、分析解决问题。更多信息,请参见Java应用业务日志关联调用链TraceId