zuul使用okhttp使zipkin链路id传递失败的解决方案
问题说明
在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等信息的传递。
另,如果需要传递其他信息,比如用户的标签,也可采用类似的方式实现。
转载完善自:https://blog.csdn.net/shuidexiongdi/article/details/95359196
标题:zuul使用okhttp使zipkin链路id传递失败的解决方案
作者:码霸霸
地址:https://blog.lupf.cn/articles/2020/11/09/1604915008530.html