在zuul中使用okhttp集成zipkin的链路追踪解决方案

微服务 同时被 3 个专栏收录
1 篇文章 0 订阅
1 篇文章 0 订阅
1 篇文章 0 订阅

在zuul中使用apache client请求时,traceid | spanid | parentspanid都能传递到下一个服务中,当切换到okhttp时,以上信息没有传递过去,在下一个服务中出现新的traceid,对于服务的链路从gateway开始跟踪产生了影响。

ribbon:
  http:
    client:
      enabled: false
  okhttp:
    enabled: true

以下就是解决该问题的一种方式。

首先定义一个Interceptor,用于将拦截到的请求头里面添加上traceid | spanid | parentspanid等信息:

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import brave.Tracer;
import brave.internal.HexCodec;
import brave.internal.Platform;
import brave.propagation.TraceContext;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * okhttp过滤器,添加与trace集成的功能。 实现方式参考see also
 * @see brave.propagation.B3Propagation
 * @see org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonRequest
 */
public class OkhttpTraceInterceptor implements Interceptor {

    Logger logger = LoggerFactory.getLogger(OkhttpTraceInterceptor.class);

    
    /**
     * 128 or 64-bit trace ID lower-hex encoded into 32 or 16 characters (required)
     */
    static final String TRACE_ID_NAME = "X-B3-TraceId";
    /**
     * 64-bit span ID lower-hex encoded into 16 characters (required)
     */
    static final String SPAN_ID_NAME = "X-B3-SpanId";
    /**
     * 64-bit parent span ID lower-hex encoded into 16 characters (absent on root span)
     */
    static final String PARENT_SPAN_ID_NAME = "X-B3-ParentSpanId";
    /**
     * "1" means report this span to the tracing system, "0" means do not. (absent means defer the
     * decision to the receiver of this header).
     */
    static final String SAMPLED_NAME = "X-B3-Sampled";

    @Autowired
    Tracer tracer;

    /**
     * 将X-B3-TraceId | X-B3-SpanId | X-B3-ParentSpanId添加到请求的头部,再将请求发送出去
     */
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        TraceContext traceContext = tracer.currentSpan().context(); 
        //copy all headers to newheaders
        Headers.Builder headers = request.headers().newBuilder();
        //add traceid | spanid | parentspanid to headers
        headers.add(TRACE_ID_NAME, traceContext.traceIdString());
        // headers.add(SPAN_ID_NAME, traceContext.spanIdString());
        headers.add(SPAN_ID_NAME, HexCodec.toLowerHex(nextId()));//set next spanid
        String parentId = traceContext.parentIdString();
        if(parentId == null) {
            parentId = HexCodec.toLowerHex(traceContext.spanId());//set parentid = spanid(root) when null
        }
        headers.add(PARENT_SPAN_ID_NAME, parentId);
        // headers.add(SAMPLED_NAME,"1");
        
        //rebuild a new request
        request = request.newBuilder().headers(headers.build()).build();
        Response response = chain.proceed(request);

        //将traceid返回去给调用方,如浏览器发起的请求,可在浏览器看到该信息,方便定位
        Headers.Builder responseHeadersBuilder = response.headers()
                                                        .newBuilder()
                                                        .add(TRACE_ID_NAME, traceContext.traceIdString());
        response = response.newBuilder().headers(responseHeadersBuilder.build()).build();

        return response;
    }

    /** Generates a new 64-bit ID, taking care to dodge zero which can be confused with absent */
    long nextId() {
        long nextId = Platform.get().randomLong();
        while (nextId == 0L) {
        nextId = Platform.get().randomLong();
        }
        return nextId;
    }

}

然后定义BeanPostProcessor,在okhttpclient的bean初始化之前将上面的过滤器添加到client中:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 *
 * 为okhttpclient的bean添加过滤器
 */
@Configuration
@Component
public class OkHttpTracePostProcessor implements BeanPostProcessor {
    Logger logger = LoggerFactory.getLogger(OkHttpTracePostProcessor.class);

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof okhttp3.OkHttpClient){
            okhttp3.OkHttpClient client = (okhttp3.OkHttpClient)bean;
            bean = client.newBuilder()
                .addInterceptor(okhttpTraceInterceptor())//添加过滤器,该过滤器主要是将在请求头中添加追踪信息
                .build();
            logger.info("addInterceptor okhttpTraceInterceptor to bean:{}",beanName);
        }
       
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    
     
    @Bean
    OkhttpTraceInterceptor okhttpTraceInterceptor() {
        return new OkhttpTraceInterceptor();
    }
}

如果已经定义好okhttpclient的bean(也即以上的OkHttpTracePostProcessor的init方法能捕捉到okhttpclient),那么此时就完工了。如果还没有client,则自定义一个bean:

import java.util.concurrent.TimeUnit;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import feign.okhttp.OkHttpClient;
import okhttp3.ConnectionPool;

/**
 * 
 * OkHttpClient集成Trace跟踪的自动化配置部分
 */
@Configuration
public class OkHttpTraceAutoConfig {

    /**
     * 如果还未定义OkHttpClient的bean,则采用此OkHttpClient
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(OkHttpClient.class)
    public okhttp3.OkHttpClient okHttpClient(){
        return new okhttp3.OkHttpClient.Builder()
                .connectTimeout(2, TimeUnit.SECONDS)
                .readTimeout(3,TimeUnit.SECONDS)
                .writeTimeout(3, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)
                .connectionPool(new ConnectionPool(20,10L,TimeUnit.SECONDS))
                .build();
    }

}

以上bean会在没有okhttpclient的时候初始化一个bean,然后postprocessor捕捉添加interceptor,便能将信息添加到头部。自此,便完成了traceid等信息的传递。

另,如果需要传递其他信息,比如用户的标签,也可采用类似的方式实现。

  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值