zipkin链路不上传_zuul使用okhttp使zipkin链路id传递失败的解决方案

在zuul使用Okhttp时,发现traceid、spanid和parentspanid无法传递,导致服务链路追踪中断。通过创建一个OkhttpTraceInterceptor拦截器,并在BeanPostProcessor中注入,将链路追踪信息添加到请求头,成功解决了链路追踪断链的问题。此外,该解决方案也适用于传递其他自定义信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题说明

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

ribbon:

http:

client:

enabled: false

okhttp:

enabled: true

原有分析

使用apache client,链路追踪相关的id会随着handler传递到下一个服务,但是使用okhttp就忽略了链路追踪的id;handler中并没有包含任何id,这样在下一个服务中,就会产生一个新的id,从而是链路追踪断链。

解决方案

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

首先定义一个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等信息的传递。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值