调用链系列三、基于zipkin调用链封装starter实现springmvc、dubbo、restTemplate等实现全链路跟踪

一、实现思路

1、过滤器实现思路

所有调用链数据都通过过滤器实现埋点并收集、同一条链共享一个traceId、每个节点有唯一的spanId。

2、共享传递方式

1、rpc调用:通过隐式传参、dubbo有提供spi在rpc调用之前塞到请求中。参考:dubbo系列六、SPI扩展Filter隐式传参

2、http调用:通过servlet过滤器、在请求前放入requestHead中传递、resTemplate也是如此。

参考:调用链二、Zipkin 和 Brave 实现(springmvc、RestTemplate)服务调用跟踪

3、redis和DB等调用:原理相似,可以通过aop在调用前后拦截。

4、业务代码侵入低。

二、架构图

三、业务项目接入starter步骤

1、pom.xml添加maven包

<dependency>
       <groupId>trace.starter</groupId>
       <artifactId>trace-starter</artifactId>
<version>1.0.2-SNAPSHOT</version>
</dependency>

2、application.yml添加trace配置

dubbo.trace:
  enabled: true
  connectTimeout: 60
  readTimeout: 60
  flushInterval: 0
  compressionEnabled: true
  applicationName: dubbo-zipkin0
  zipkinUrl: http://192.168.1.100:9411

3、在springboot开启注解

@EnableTraceAutoConfigurationProperties,例如:

package com.example.demo;
import com.dubbo.trace.configuration.EnableTraceAutoConfigurationProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableTraceAutoConfigurationProperties
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

四、实现代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>trace.starter</groupId>
    <artifactId>trace-starter</artifactId>
    <version>1.0.2-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--zipkin-brave start-->
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-core</artifactId>
            <version>3.9.0</version>
        </dependency>
        <!--http方式收集-->
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-spancollector-http</artifactId>
            <version>3.9.0</version>
        </dependency>
        <!--zipkin-brave end-->

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.1.31</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>spring</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <!--logback日志-->
        <dependency>
            <groupId>net.logstash.logback</groupId>
            <artifactId>logstash-logback-encoder</artifactId>
            <version>4.8</version>
        </dependency>

        <dependency>
            <groupId>com.google.auto.value</groupId>
            <artifactId>auto-value</artifactId>
            <version>1.2</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

    </dependencies>
</project>
View Code

1、base过滤器、过滤器父类

package com.dubbo.trace.base;

import com.dubbo.trace.TraceContext;
import com.dubbo.trace.send.TraceAgent;
import com.dubbo.trace.utils.IdUtils;
import com.dubbo.trace.utils.NetworkUtils;
import com.twitter.zipkin.gen.Annotation;
import com.twitter.zipkin.gen.BinaryAnnotation;
import com.twitter.zipkin.gen.Endpoint;
import com.twitter.zipkin.gen.Span;
import net.logstash.logback.encoder.org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpRequest;

import javax.servlet.http.HttpServletRequest;


public abstract class BaseFilter {

    /**
     * 创建span信息
     */
    protected Span createSpan() {
        Span span = new Span();

        long id = IdUtils.getId();
        span.setId(id);

        Long traceId = TraceContext.getTraceId();
        // 首次调用
        if (traceId == null) {
            TraceContext.start();
            traceId = id;
            TraceContext.setTraceId(traceId);
        }

        span.setTrace_id(traceId);
        span.setName(TraceContext.getTraceConfig().getApplicationName());

        // 首次调用spanId和parentId相等
        if (TraceContext.getSpanId() == null) {
            span.setParent_id(span.getId());
        }

        span.setParent_id(TraceContext.getSpanId());
        TraceContext.setSpanId(span.getId());

        return span;
    }

    /**
     * 添加节点信息
     */
    public void addToAnnotations(Span span, String traceType, Long timeStamp) {
        span.addToAnnotations(
                Annotation.create(timeStamp, traceType,
                        Endpoint.create(TraceContext.getTraceConfig().getApplicationName(),
                                NetworkUtils.getIp(),
                                TraceContext.getTraceConfig().getServerPort()))
        );
    }

    /**
     * 增加接口信息
     */
    protected void addToBinary_annotations(Span span, String key, String value) {
        span.addToBinary_annotations(BinaryAnnotation.create(key, value,
                Endpoint.create(TraceContext.getTraceConfig().getApplicationName(),
                        NetworkUtils.getIp(),
                        TraceContext.getTraceConfig().getServerPort())));
    }

    /**
     * 结束调用链
     */
    public void endTrace(Span span, Long duration, String traceType) {
        addToAnnotations(span, traceType, System.currentTimeMillis() * 1000);
        span.setDuration(duration);
        TraceAgent.getTraceAgent().send(span);
    }

    protected void getTraceHttpHeader(HttpServletRequest httpReq) {

        String traceId = httpReq.getHeader("trace_id");
        String spanId = httpReq.getHeader("span_id");

        if (StringUtils.isNotBlank(traceId)) {
            TraceContext.setTraceId(Long.parseLong(traceId));
        }

        if (StringUtils.isNotBlank(spanId)) {
            TraceContext.setSpanId(Long.parseLong(spanId));
        }
    }

    protected void setTraceToHttpHeader(HttpRequest httpRequest, Span span) {
        // 内部请求可以携带trace信息,外部请求改行代码注释掉
        httpRequest.getHeaders().set("trace_id", String.valueOf(span.getTrace_id()));
        httpRequest.getHeaders().set("span_id", String.valueOf(span.getId()));
    }

}

2、dubbo消费者过滤器:TraceConsumerFilter.java

package com.dubbo.trace.dubbo;

import com.alibaba.dubbo.rpc.*;
import com.dubbo.trace.TraceContext;
import com.dubbo.trace.base.BaseFilter;
import com.twitter.zipkin.gen.Span;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.util.Arrays;
import java.util.Map;

/**
 * @author 王柱星
 * @version 1.0
 * @title
 * @time 2018年10月25日
 * @since 1.0
 */

@Component
public class TraceConsumerFilter extends BaseFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 开启调用链
        Span span = this.startTrace(invoker, invocation);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 远程调用
        Result result = invoker.invoke(invocation);

        // 结束调用链
        stopWatch.stop();
        // 记录出参
        addToBinary_annotations(span, "results", result.getValue().toString());
        this.endTrace(span, stopWatch.getTotalTimeMillis() * 1000, TraceContext.ANNO_CR);
        return result;
    }

    protected Span startTrace(Invoker<?> invoker, Invocation invocation) {
        Span span = createSpan();

        Long timeStamp = System.currentTimeMillis() * 1000;
        span.setTimestamp(timeStamp);
        span.setName("RPC:" + invoker.getInterface().getSimpleName() + ":" + invocation.getMethodName());

        addToAnnotations(span, TraceContext.ANNO_CS, timeStamp);

        Map<String, String> attaches = invocation.getAttachments();
        attaches.put(TraceContext.TRACE_ID_KEY, String.valueOf(span.getTrace_id()));
        attaches.put(TraceContext.SPAN_ID_KEY, String.valueOf(span.getId()));

        // 记录入参
        addToBinary_annotations(span, "params", Arrays.toString(invocation.getArguments()));
        return span;
    }
}

3、dubbo生产者过滤器:TraceProviderFilter.java

package com.dubbo.trace.dubbo;

import com.alibaba.dubbo.rpc.*;
import com.dubbo.trace.TraceContext;
import com.dubbo.trace.base.BaseFilter;
import com.twitter.zipkin.gen.Span;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.util.Arrays;

/**
 * @author 王柱星
 * @version 1.0
 * @title
 * @time 2018年10月25日
 * @since 1.0
 */

@Component
public class TraceProviderFilter extends BaseFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

        // 开启调用链
        Span span = this.startTrace(invoker, invocation);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 远程调用
        Result result = invoker.invoke(invocation);

        // 结束调用链
        stopWatch.stop();
        // 记录出参
        this.addToBinary_annotations(span, "results", result.getValue().toString());
        this.endTrace(span, stopWatch.getTotalTimeMillis() * 1000, TraceContext.ANNO_SS);

        return result;
    }

    protected Span startTrace(Invoker<?> invoker, Invocation invocation) {
        Long traceId = Long.valueOf(invocation.getAttachment(TraceContext.TRACE_ID_KEY));
        Long spanId = Long.valueOf(invocation.getAttachment(TraceContext.SPAN_ID_KEY));

        TraceContext.setTraceId(traceId);
        TraceContext.setSpanId(spanId);

        Span span = createSpan();

        Long timeStamp = System.currentTimeMillis() * 1000;
        span.setTimestamp(timeStamp);
        addToAnnotations(span, TraceContext.ANNO_SR, timeStamp);

        span.setName("RPC:" + invoker.getInterface().getSimpleName() + ":" + invocation.getMethodName());

        // 记录入参
        addToBinary_annotations(span, "params", Arrays.toString(invocation.getArguments()));

        return span;
    }

}

4、RestTemplate过滤器:RestTemplateFilter.java

package com.dubbo.trace.restTemplate;

import com.dubbo.trace.TraceContext;
import com.dubbo.trace.base.BaseFilter;
import com.dubbo.trace.utils.NetworkUtils;
import com.twitter.zipkin.gen.BinaryAnnotation;
import com.twitter.zipkin.gen.Endpoint;
import com.twitter.zipkin.gen.Span;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StopWatch;

import java.io.IOException;

public class RestTemplateFilter extends BaseFilter implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // 开启调用链
        ClientHttpResponse response = null;
        Span span = this.startTrace(httpRequest);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        try {
            // 内部请求头可以携带trace信息,外部请求改行代码注释掉
            this.setTraceToHttpHeader(httpRequest, span);
            // 保证请求继续被执行
            response = execution.execute(httpRequest, body);

            // 结束调用链
            this.endTrace(span, stopWatch.getTotalTimeMillis() * 1000, TraceContext.ANNO_CR);
        } catch (Exception e) {
            // 异常记录到调用链
            span.addToBinary_annotations(BinaryAnnotation.create("error", e.getMessage(),
                    Endpoint.create(TraceContext.getTraceConfig().getApplicationName(),
                            NetworkUtils.getIp(),
                            TraceContext.getTraceConfig().getServerPort())));
            this.endTrace(span, stopWatch.getTotalTimeMillis() * 1000, TraceContext.ANNO_CR);
            e.printStackTrace();
        }

        return response;
    }

    private Span startTrace(HttpRequest httpRequest) {
        Span span = createSpan();
        Long timeStamp = System.currentTimeMillis() * 1000;
        span.setTimestamp(timeStamp);
        span.setName("restTemplate:" + httpRequest.getURI() + ":" + httpRequest.getMethod());
        addToAnnotations(span, TraceContext.ANNO_CS, timeStamp);
        return span;
    }

}

5、Servlet过滤器:TraceServletFilter.java

package com.dubbo.trace.servlet;

import com.dubbo.trace.TraceContext;
import com.dubbo.trace.base.BaseFilter;
import com.dubbo.trace.utils.NetworkUtils;
import com.twitter.zipkin.gen.BinaryAnnotation;
import com.twitter.zipkin.gen.Endpoint;
import com.twitter.zipkin.gen.Span;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class TraceServletFilter extends BaseFilter implements Filter {

    public TraceServletFilter() {
    }

    public void destroy() {
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpReq = (HttpServletRequest) req;
        BufferedHttpRequestWrapper newReq = new BufferedHttpRequestWrapper(httpReq);

        Span span = this.startTrace(newReq);
        Long timeStamp = System.currentTimeMillis() * 1000;
        span.setTimestamp(timeStamp);
        addToAnnotations(span, TraceContext.ANNO_SR, timeStamp);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        try {
            chain.doFilter(newReq, resp);
            stopWatch.stop();
        } catch (Throwable var15) {
            span.addToBinary_annotations(BinaryAnnotation.create("error", var15.getMessage(),
                    Endpoint.create(NetworkUtils.getIp() + ":" + TraceContext.getTraceConfig().getServerPort() + httpReq.getRequestURL().toString(),
                            NetworkUtils.getIp(),
                            TraceContext.getTraceConfig().getServerPort())));
            this.endTrace(span, stopWatch.getTotalTimeMillis() * 1000, TraceContext.ANNO_SS);
            throw var15;
        } finally {
            HttpServletResponse var12 = (HttpServletResponse) resp;
            var12.setHeader("trace_id", String.valueOf(span.getTrace_id()));
            var12.setHeader("span_id", String.valueOf(span.getId()));
            this.endTrace(span, stopWatch.getTotalTimeMillis() * 1000, TraceContext.ANNO_SS);
        }

    }


    public Span startTrace(HttpServletRequest httpReq) {
        // 处理HTTP头部trace信息
        getTraceHttpHeader(httpReq);
        Span span = createSpan();
        span.setName("HTTP:" + NetworkUtils.ip + ":" + TraceContext.getTraceConfig().getServerPort() + httpReq.getRequestURI());

        // cookies
        // addToBinary_annotations(span,"cookies",Arrays.toString(httpReq.getCookies()));
        return span;
    }

}

6、BufferedHttpRequestWrapper.java

package com.dubbo.trace.servlet;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

public class BufferedHttpRequestWrapper extends HttpServletRequestWrapper {
    private static final Logger log = LoggerFactory.getLogger(BufferedHttpRequestWrapper.class);
    private byte[] reqBody = null;

    public BufferedHttpRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.reqBody = IOUtils.toString(request.getInputStream(), "utf-8").getBytes();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new MyServletInputStream(new ByteArrayInputStream(this.reqBody));

    }

    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getRequestBody() throws UnsupportedEncodingException {
        return new String(this.reqBody, "utf-8");
    }

    class MyServletInputStream extends ServletInputStream {
        private InputStream inputStream;

        public MyServletInputStream(InputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public int read() throws IOException {
            return inputStream.read();
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }
    }

}

7、TraceContext.java 上下文

用于存储共享的traceId和spanId到InheritableThreadLocal中(子线程也能获取到、而ThreadLocal不能)

package com.dubbo.trace;

import com.dubbo.trace.configuration.TraceConfig;
import com.twitter.zipkin.gen.Span;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 王柱星
 * @version 1.0
 * @title
 * @time 2018年10月25日
 * @since 1.0
 */
@Data
@Component
public class TraceContext {
    public static final String TRACE_ID_KEY = "traceId";
    public static final String SPAN_ID_KEY = "spanId";
    public static final String ANNO_CS = "cs";
    public static final String ANNO_CR = "cr";
    public static final String ANNO_SR = "sr";
    public static final String ANNO_SS = "ss";
    public static TraceContext traceContext;
    private static ThreadLocal<Long> TRACE_ID = new InheritableThreadLocal<>();
    private static ThreadLocal<Long> SPAN_ID = new InheritableThreadLocal<>();
    private static ThreadLocal<List<Span>> SPAN_LIST = new InheritableThreadLocal<>();
    @Autowired
    private TraceConfig traceConfig;

    public static Long getSpanId() {
        return SPAN_ID.get();
    }

    public static void setSpanId(Long spanId) {
        SPAN_ID.set(spanId);
    }

    public static Long getTraceId() {
        return TRACE_ID.get();
    }

    public static void setTraceId(Long traceId) {
        TRACE_ID.set(traceId);
    }

    public static void clear() {
        TRACE_ID.remove();
        SPAN_ID.remove();
        SPAN_LIST.remove();
    }

    public static void start() {
        clear();
        SPAN_LIST.set(new ArrayList<Span>());
    }

    public static TraceConfig getTraceConfig() {
        return traceContext.traceConfig;
    }

    public static void addSpan(Span span) {
        List<Span> spanList = SPAN_LIST.get();
        spanList.add(span);
    }

    @PostConstruct
    public void init() {
        traceContext = this;
    }
}

8、工具类用户生成、traceId和spanId、获取本机IP

IdUtils.java

package com.dubbo.trace.utils;

import java.util.Random;

/**
 * @author 王柱星
 * @version 1.0
 * @title
 * @time 2018年10月25日
 * @since 1.0
 */
public class IdUtils {
    public static Long getId() {
        return NetworkUtils.getIp() + System.currentTimeMillis();
    }
}

NetworkUtils.java

package com.dubbo.trace.utils;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author 王柱星
 * @version 1.0
 * @title
 * @time 2018年10月29日
 * @since 1.0
 */
public class NetworkUtils {
    public static String ip;
    private static InetAddress addr;

    static {
        try {
            addr = InetAddress.getLocalHost();
            ip = addr.getHostAddress().toString(); //获取本机ip
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }

    public static int ipToLong(String strIp) {
        int[] ip = new int[4];
        //先找到IP地址字符串中.的位置  
        int position1 = strIp.indexOf(".");
        int position2 = strIp.indexOf(".", position1 + 1);
        int position3 = strIp.indexOf(".", position2 + 1);
        //将每个.之间的字符串转换成整型  
        ip[0] = Integer.parseInt(strIp.substring(0, position1));
        ip[1] = Integer.parseInt(strIp.substring(position1 + 1, position2));
        ip[2] = Integer.parseInt(strIp.substring(position2 + 1, position3));
        ip[3] = Integer.parseInt(strIp.substring(position3 + 1));
        return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
    }

    public static int getIp() {
        return ipToLong(ip);
    }
}

9、收集代理和发送实现类

目前发送http请求、后续可扩展发送MQ

SendHttp.java

package com.dubbo.trace.send;

import com.github.kristofa.brave.http.HttpSpanCollector;
import com.twitter.zipkin.gen.Span;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;


@Component
@DependsOn("zipkinConfig")
public class SendHttp extends TraceAgent {

    @Autowired
    private HttpSpanCollector httpSpanCollector;


    @Override
    public void send(Span span) {
        if (span != null) {
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(span.toString());
                    httpSpanCollector.collect(span);
                    httpSpanCollector.flush();
                }
            });
        }
    }

}

TraceAgent.java

package com.dubbo.trace.send;

import com.twitter.zipkin.gen.Span;

import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * @author 王柱星
 * @version 1.0
 * @title
 * @time 2018年10月25日
 * @since 1.0
 */
public abstract class TraceAgent {
    private static TraceAgent traceAgent;
    private final int THREAD_POOL_COUNT = 5;
    protected final ExecutorService executor =
            Executors.newFixedThreadPool(this.THREAD_POOL_COUNT, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread worker = new Thread(r);
                    worker.setName("TRACE-AGENT-WORKER");
                    worker.setDaemon(true);
                    return worker;
                }
            });

    public static TraceAgent getTraceAgent() {
        return traceAgent;
    }

    public abstract void send(Span span);

    @PostConstruct
    public void init() {
        traceAgent = this;
    }
}

10、配置类

FilterConfig.java

package com.dubbo.trace.configuration;

import com.dubbo.trace.restTemplate.RestTemplateFilter;
import com.dubbo.trace.servlet.TraceServletFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class FilterConfig {


    /**
     * httpClient客户端拦截器,需要clientRequestInterceptor,clientResponseInterceptor分别完成cs和cr操作
     *
     * @param brave
     * @return
     */

    @Autowired
    private RestTemplate restTemplate;

    @Bean
    RestTemplate template() {
        return new RestTemplate();
    }

    // 添加rest template拦截器
    @PostConstruct
    public void init() {
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(restTemplate.getInterceptors());
        interceptors.add(new RestTemplateFilter());
        restTemplate.setInterceptors(interceptors);
    }

    /**
     * servlet过滤器,自定义过滤器完成cs和cr
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean callTrackingServletFilter() {
        TraceServletFilter filter = new TraceServletFilter();
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(filter);
        List<String> urlPatterns = new ArrayList();
        urlPatterns.add("/*");
        registrationBean.setUrlPatterns(urlPatterns);
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

TraceConfig.java

package com.dubbo.trace.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author 王柱星
 * @version 1.0
 * @title
 * @time 2018年10月25日
 * @since 1.0
 */
@ConfigurationProperties(prefix = "dubbo.trace")
@Component
public class TraceConfig {

    private boolean enabled;
    private int connectTimeout;
    private int readTimeout;
    private int flushInterval;
    private boolean compressionEnabled;
    private String zipkinUrl;
    private int serverPort = 9411;
    private String applicationName;

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public int getFlushInterval() {
        return flushInterval;
    }

    public void setFlushInterval(int flushInterval) {
        this.flushInterval = flushInterval;
    }

    public boolean isCompressionEnabled() {
        return compressionEnabled;
    }

    public void setCompressionEnabled(boolean compressionEnabled) {
        this.compressionEnabled = compressionEnabled;
    }

    public String getZipkinUrl() {
        return zipkinUrl;
    }

    public void setZipkinUrl(String zipkinUrl) {
        this.zipkinUrl = zipkinUrl;
    }

    public int getServerPort() {
        return serverPort;
    }

    public void setServerPort(int serverPort) {
        this.serverPort = serverPort;
    }

    public String getApplicationName() {
        return applicationName;
    }

    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }
}

ZipkinConfig.java

package com.dubbo.trace.configuration;

import com.github.kristofa.brave.Brave;
import com.github.kristofa.brave.Brave.Builder;
import com.github.kristofa.brave.EmptySpanCollectorMetricsHandler;
import com.github.kristofa.brave.Sampler;
import com.github.kristofa.brave.SpanCollector;
import com.github.kristofa.brave.http.HttpSpanCollector;
import com.github.kristofa.brave.http.HttpSpanCollector.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ZipkinConfig {

    @Autowired
    TraceConfig traceConfig;

    /**
     * 配置收集器
     *
     * @return
     */
    @Bean("httpSpanCollector")
    public HttpSpanCollector spanCollector() {
        Config config = Config.builder().compressionEnabled(false).connectTimeout(traceConfig.getConnectTimeout())
                .flushInterval(traceConfig.getFlushInterval()).readTimeout(traceConfig.getReadTimeout()).build();
        return HttpSpanCollector.create(traceConfig.getZipkinUrl(), config, new EmptySpanCollectorMetricsHandler());
    }

    /**
     * Brave各工具类的封装
     *
     * @param spanCollector
     * @return
     */
    @Bean
    public Brave brave(SpanCollector spanCollector) {
        Builder builder = new Builder(traceConfig.getApplicationName());// 指定serviceName
        builder.spanCollector(spanCollector);
        builder.traceSampler(Sampler.create(1));// 采集率
        return builder.build();
    }


}

11、启动配置

EnableTraceAutoConfiguration.java

package com.dubbo.trace.configuration;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author 王柱星
 * @version 1.0
 * @title
 * @time 2018年10月25日
 * @since 1.0
 */
@Configuration
@ConditionalOnBean(annotation = EnableTraceAutoConfigurationProperties.class)
@AutoConfigureAfter(SpringBootConfiguration.class)
@EnableConfigurationProperties(TraceConfig.class)
@ComponentScan(basePackages = "com.dubbo.trace")
public class EnableTraceAutoConfiguration {
}

EnableTraceAutoConfigurationProperties.java

package com.dubbo.trace.configuration;

import java.lang.annotation.*;

/**
 * @author 王柱星
 * @version 1.0
 * @title
 * @time 2018年10月25日
 * @since 1.0
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableTraceAutoConfigurationProperties {
}

12、自定义starter配置

resources/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.dubbo.trace.configuration.EnableTraceAutoConfiguration

13、dubbo过滤器配置

resources/META-INF/com.alibaba.dubbo.rpc.Filter

traceConsumerFilter=com.dubbo.trace.dubbo.TraceConsumerFilter
traceProviderFilter=com.dubbo.trace.dubbo.TraceProviderFilter

五、验证

1、启动测试项目:

Springboot-Zipkin0、Springboot-Zipkin1

2、访问:http://192.168.1.100:8080/testDubbo

3、打开zipkin地址:

http://47.52.199.100:9411

六、代码地址

https://github.com/Star-Lordxing/trace_starter_xing

七、扩展思路

1、zipkin这一套目前是把数据存储在assandra内存中、对性能有较高要求可采用ES存储。zipkin本身已经提供该实现、修改启动参数即可。

2、zipkin中目前只能看到调用链简单信息,并不知道报错频率、平均调用时间、调用次数等、需要借助其他工具完成。

3、zipkin只能看到请求报错信息、没法看到业务报错信息,需要查看ELK。

原文地址:https://www.cnblogs.com/wangzhuxing/p/9944135.html