在Springboot自定义Jaeger-client-starter并集成dubbo链路追踪功能

653 阅读3分钟

为了springboot更好的集成jaeger-client,我自定义了一个starter,并且集成了dubbo链路追踪的功能,现在分享给大家,为祖国的开源事业做贡献。

为什么我要自定义jaeger-client-starter?

市场是其实有现成的jaeger-client-starter,但是并没有集成dubbo的链路追踪,满足不了的项目的需求,还需要自己开发,但是我相信这类需求还是很大的,有必要集成一下dubbo,所以自定义了简单的starter,满足基本需求没有问题。

<dependency>
    <groupId>io.opentracing.contrib</groupId>
    <artifactId>opentracing-spring-jaeger-web-starter</artifactId>
    <version>3.3.1</version>
</dependency>

废话不多说,直接上代码:

image.png

  • 自定义了@EnableTrace的注解
  • 在config中实现加载JaegerTracer
  • 集成dubbo的链路追踪

@EnableTrace的注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(JaegerClientConfig.class)
@Documented
public @interface EnableTrace {
}

JaegerClientConfig的实现

代码主要通过已agent方式来上报的,将代理的host和port设置到环境变量中,然后通过System.getenv("JAEGER_SERVICE_NAME")的方式获取变量

@Configuration
@ConditionalOnProperty(value = "nb.tracer", havingValue = "true", matchIfMissing = true)
@AutoConfigureBefore(io.opentracing.contrib.spring.tracer.configuration.TracerAutoConfiguration.class)
@Slf4j
public class JaegerClientConfig {

    @Bean
    public io.opentracing.Tracer tracer() {
        //获取系统变量
        log.info("load tracer env..");
        String jaeger_service_name = System.getenv("JAEGER_SERVICE_NAME");
        String jaeger_agent_host = System.getenv("JAEGER_AGENT_HOST");

        if(jaeger_service_name != null && jaeger_agent_host != null){
            Integer jaeger_agent_port = Integer.valueOf(System.getenv("JAEGER_AGENT_PORT"));
            Integer jaeger_sampler_param = Integer.valueOf(System.getenv("JAEGER_SAMPLER_PARAM"));
            log.info("load jaeger_service_name: {}", jaeger_service_name);
            log.info("load jaeger_agent_host: {}", jaeger_agent_host);
            log.info("load jaeger_agent_port: {}", jaeger_agent_port);
            log.info("load jaeger_sampler_param:{}", jaeger_sampler_param);
            log.info("tracer load successfully ....");
            io.jaegertracing.Configuration config = new io.jaegertracing.Configuration(jaeger_service_name);
            io.jaegertracing.Configuration.SenderConfiguration sender = new io.jaegertracing.Configuration.SenderConfiguration();

            sender.withAgentHost(jaeger_agent_host);
            sender.withAgentPort(jaeger_agent_port);

            config.withSampler(new io.jaegertracing.Configuration.SamplerConfiguration().withType("const").withParam(jaeger_sampler_param));
            config.withReporter(new io.jaegertracing.Configuration.ReporterConfiguration().withSender(sender).withMaxQueueSize(10000).withLogSpans(true));
            B3TextMapCodec b3Codec = new B3TextMapCodec.Builder().build();
            JaegerTracer tracer = config.getTracerBuilder().registerInjector(Format.Builtin.HTTP_HEADERS, b3Codec)
                    .registerExtractor(Format.Builtin.HTTP_HEADERS, b3Codec)
                    .registerInjector(Format.Builtin.TEXT_MAP, b3Codec)
                    .registerExtractor(Format.Builtin.TEXT_MAP, b3Codec)
                    .build();
            GlobalTracer.registerIfAbsent(tracer);
            return tracer;
        }else{
            log.info("tracer load fail ....");
           return null;
        }
    }
}

dubbo相关的代码太多 贴主要代码

package com.nb.spring.jaeger.client.starter.jaegerclientstarter.dubbo;

import com.alibaba.dubbo.rpc.RpcContext;
import com.google.gson.Gson;
import io.opentracing.Span;
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
import io.opentracing.propagation.TextMapAdapter;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
import lombok.extern.slf4j.Slf4j;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

@Slf4j
public class DubboTraceUtil {

    public static void printContextByHeader(RpcContext rpcContext, String header) {
        log.info("ctx local on [{}], uber-trace-id = {}", header, rpcContext.get("uber-trace-id"));
        log.info("ctx remote on [{}], uber-trace-id = {}", header, rpcContext.getAttachment("uber-trace-id"));

    }

    public static void printContextInfo(RpcContext rpcContext) {
        log.info("ctx local on uber-trace-id = {}", rpcContext.get("uber-trace-id"));
        log.info("ctx remote on uber-trace-id = {}", rpcContext.getAttachment("uber-trace-id"));
    }

    public static void attachTraceToRemoteCtx(Span span, final RpcContext rpcContext) {
        Tracer tracer = GlobalTracer.get();
        if (tracer != null) {

            tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new TextMap() {
                @Override
                public void put(String key, String value) {
                    rpcContext.setAttachment(key, value);
                }

                @Override
                public Iterator<Map.Entry<String, String>> iterator() {
                    throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
                }
            });

        } else {
            log.warn("failed to get tracer.");
        }

    }

    public static Span extractTraceFromRemoteCtx(final RpcContext rpcContext) {
        return extractTraceFromCtx(rpcContext, Boolean.FALSE);
    }

    public static void attachTraceToLocalCtx(Span span, final RpcContext rpcContext) {
        Tracer tracer = GlobalTracer.get();
        if (tracer != null) {

            tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new TextMap() {
                @Override
                public void put(String key, String value) {
                    rpcContext.set(key, value);
                }

                @Override
                public Iterator<Map.Entry<String, String>> iterator() {
                    throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
                }
            });

        } else {
            log.warn("failed to get tracer.");
        }

    }

    public static Span extractTraceFromLocalCtx(final RpcContext rpcContext) {
        return extractTraceFromCtx(rpcContext, Boolean.TRUE);
    }

    private static Span extractTraceFromCtx(final RpcContext rpcContext, Boolean isLocalCtx) {
        Span span = null;

        Tracer tracer = GlobalTracer.get();
        if (tracer != null) {
            log.info("serviceName = {}", rpcContext.getUrl().getServiceKey());
            log.info("methodName = {}", rpcContext.getMethodName());
            log.info("arguments = {}", new Gson().toJson(rpcContext.getArguments()));

            String apiName = String.format("%s:%s", rpcContext.getUrl().getServiceKey(), rpcContext.getMethodName());
            Tracer.SpanBuilder spanBuilder = tracer.buildSpan(apiName);

            try {
                TextMap textMap = null;
                if (isLocalCtx) {
                    Map<String, Object> values = rpcContext.get();
                    Map<String, String> contexts = new HashMap<>();
                    for (Map.Entry<String, Object> value : values.entrySet()) {
                        contexts.put(value.getKey(), value.getValue().toString());
                    }
                    textMap = new TextMapAdapter(contexts);
                } else {
                    textMap = new TextMapAdapter(rpcContext.getAttachments());
                }
                SpanContext spanContext = tracer.extract(Format.Builtin.TEXT_MAP, textMap);
                if (spanContext != null) {
                    spanBuilder.asChildOf(spanContext);
                }
            } catch (Exception e) {
                spanBuilder.withTag("Error", "extract from request fail, error msg:" + e.getMessage());
            }

            span = spanBuilder.start();

            // dubbo方法调用的参数
            String arguments = "";
            try {
                arguments = new Gson().toJson(rpcContext.getArguments());
            } catch (Exception ignored) {
            }
            span.setTag("arguments", arguments);

            if (rpcContext.isConsumerSide()) {
                Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT);
            } else if (rpcContext.isProviderSide()) {
                Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_SERVER);
            }
            // TODO record db trace
            Tags.DB_INSTANCE.set(span, "db-instance");

            InetSocketAddress remoteAddress = rpcContext.getRemoteAddress();
            if (remoteAddress != null) {
                Tags.PEER_PORT.set(span, remoteAddress.getPort());
                Tags.PEER_HOSTNAME.set(span, remoteAddress.getHostName());
                Tags.PEER_SERVICE.set(span, rpcContext.getUrl().getServiceKey());

                InetAddress inetAddress = remoteAddress.getAddress();
                if (inetAddress instanceof Inet4Address) {
                    Tags.PEER_HOST_IPV4.set(span, remoteAddress.getHostString());
                } else if (inetAddress instanceof Inet6Address) {
                    Tags.PEER_HOST_IPV6.set(span, remoteAddress.getHostString());
                } else {
                    Tags.PEER_HOST_IPV4.set(span, remoteAddress.getHostString());
                }
            }


        } else {
            log.warn("failed to get tracer.");
        }

        return span;
    }
}

使用说明

  1. 导入自定义的starter
  2. 在主类Application中标注@EnableTrace
  3. 为了区分环境,我们可以通过配置来实现
nb.tracer=true //开启链路追踪
nb.tracer=false //关闭链路追踪,建议local环境下置为false
  1. 实现dubbo接口之间的trace,对生产者和消费者增加自定义filter
消费者:com.nb.spring.jaeger.client.starter.dubbo.RpcConsumerTraceFilter
生产者:com.nb.spring.jaeger.client.starter.dubbo.RpcProviderTraceFilter

5.增加过滤器

dubbo.provider.filter=RpcProviderTraceFilter
dubbo.consumer.filter=RpcConsumerTraceFilter

成功案例:

image.png