分布式日志追踪的最佳实践1
Posted keep-go-on
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式日志追踪的最佳实践1相关的知识,希望对你有一定的参考价值。
分布式日志追踪
分布式环境中无可避免的需要做微服务之间的调用,这导致追踪到整个的业务流程变得麻烦。分布式日志追踪解决了那些问题:
- 分布式的性能优化。通过日志追踪可以看出各个环境,各个服务消耗的时间,为性能优化定位问题。
- 追踪业务流程。通过链路追踪码,可以追踪到业务发生的整体流程。
- 异常追踪。但服务发生异常时,服务可以通过埋点的方式收集追踪码,从而定位问题。
适用范围
框架除
Gateway
外全部采用Servlet
,本文代码不适用与WebFlux
等非阻塞模型。
框架采用了
PlumeLog
分布式框架,因此在工具类中使用了PlumeLog
的工具类。
如何在主流的微服务框架中实现日志追踪 ?
本文以使用Feign
和RestTemplate
进行微服务直接调用的案例作为实例,如下图:
图中是一个比较常见的服务调用案例,在请求在各个微服务模块中传递时,我们可以通过服务的拦截器对请求添加标识
(链路追踪码),标记请求的流转过程。在微服务模块中需要对业务处理流程添加标识
,这样标识
就标记了本次请求的整个生命周期。
具体的请求顺序对应的操作如下:
- 前端向网关发起请求。
网关
对请求添加标记
(一般放到请求头中),然后调用微服务1
。具体见下方 :Servlet日志追踪
微服务1
在Servlet拦截器中拦截请求,并从请求中获取标记
,同时将标记
标记当前线程,然后通过Feign
调用微服务2
,这时触发Feign拦截器,拦截器见标记
添加到请求中,发送给下个微服务。具体见下方 :Servlet日志追踪
和Feign的日志追踪
微服务2
接到请求后,同样接收标记
并应用,随后通过RestTemplate调用微服务3
,这时触发RestTemplate拦截器,拦截器见标记
添加到请求中,发送给下个微服务。具体见下方 :Servlet日志追踪
和RestTemplate的日志追踪
微服务3
接到请求后,同样接收标记
并标记业务流程,处理完成后返回给微服务2
。微服务2
接到返回后,继续处理业务,完成后返回给微服务1
。微服务
1完成业务后,返回给网关
。网关
将请求结果返回给前端
。
Feign的日志追踪
Feign
的日志追踪可以通过Feign的拦截器
进行处理,如下:
import feign.RequestInterceptor;
import feign.RequestTemplate;
public class FeignTraceRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
RequestContextHolder.setRequestAttributes(attributes, true);
if(ObjectUtil.isNull(attributes)) {
return;
}
HttpServletRequest request = attributes.getRequest();
String traceId = request.getHeader(TraceUtil.TRACE_NAME);
traceId = StringUtils.isBlank(traceId) ? TraceUtil.get() : traceId;
traceId = StringUtils.isBlank(traceId) ? TraceUtil.getTraceId() : traceId;
// 如果追踪码不是空白就传递
requestTemplate.header(TraceUtil.TRACE_NAME,traceId);
}
}
当feign调用时,拦截器会拦截到Feign的请求 :
- 首先我们从请求中获取追踪码。有可能追踪码来源于上一个服务的请求。
- 如果追踪码为空,就中线程中(业务流程中)获取当前追踪码。
- 如果追踪码还是空,就新创建一个追踪码。
- 最后将追踪码放到请求头中,传递给下个服务。
应用Feign拦截器
首先,声明Feign配置:
public class FeignHeaderConfig{
@Bean
public RequestInterceptor requestInterceptor(){
return new FeignBasicAuthRequestInterceptor();
}
@Bean
public FeignTraceRequestInterceptor FeignTraceRequestInterceptor(){
return new FeignTraceRequestInterceptor();
}
}
声明配置器时需要将写的
拦截器
注入到配置
中
然后应用配置:
@FeignClient(value = "feign-test-service",configuration = FeignHeaderConfig.class)
RestTemplate的日志追踪
RestTemplate
的日志追踪同样使用拦截器
处理:
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
public class RestTraceInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
// 从头里获取追踪码
String traceId = TraceUtil.getTraceId(httpRequest);
// 如果追踪码是个空,就从 线程里去追踪码
traceId = StringUtils.isBlank(traceId) ? TraceUtil.get() : traceId;
// 如果线程追踪码还是空,就创建新的追踪码
traceId = StringUtils.isBlank(traceId) ? TraceUtil.getTraceId() : traceId;
// 请求头传递参数
httpRequest.getHeaders().add(TraceUtil.TRACE_NAME, traceId);
// 保证请求继续被执行
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
}
当RestTemplate调用时,拦截器会拦截到请求 几乎和Feign一致:
- 首先我们从请求中获取追踪码。有可能追踪码来源于上一个服务的请求。
- 如果追踪码为空,就中线程中(业务流程中)获取当前追踪码。
- 如果追踪码还是空,就新创建一个追踪码。
- 最后将追踪码放到请求头中,传递给下个服务。
应用RestTemplate拦截器
在创建RestTemplate时,声明使用拦截器即可:
@Configuration
public class MyLoadBalanceConfig {
@Bean
public RestTraceInterceptor restTraceInterceptor(){
return new RestTraceInterceptor();
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(restTraceInterceptor()));
return restTemplate;
}
}
如果在分布式环境中必须要加上
@LoadBalanced
注解,否则服务使用服务名进行调用。
Servlet中日志追踪
同样,在servlet中以旧使用Servlet拦截器处理:
import com.kgo.trace.utils.TraceUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TraceInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求中获取追踪码
String traceId = TraceUtil.getTraceId(request);
// 如果追踪码是个空,就从 线程里去追踪码
traceId = StringUtils.isBlank(traceId) ? TraceUtil.get() : traceId;
// 如果线程追踪码还是空,就创建新的追踪码
traceId = StringUtils.isBlank(traceId) ? TraceUtil.getTraceId() : traceId;
// 放到线程中
TraceUtil.set(traceId);
return true;
}
}
处理逻辑与RestTemplate和Feign的处理逻辑几乎一致,不再赘述。
应用 Servlet拦截器
这个比较简单,不做赘述:
@Configuration
public class ContractMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getTraceInterceptor()).addPathPatterns("/**");
}
@Bean
public TraceInterceptor getTraceInterceptor() {
return new TraceInterceptor();
}
}
如果在分布式环境中必须要加上
@LoadBalanced
注解,否则服务使用服务名进行调用。
日志工具类源码请见下篇分解
以上是关于分布式日志追踪的最佳实践1的主要内容,如果未能解决你的问题,请参考以下文章
RocketMQ x OpenTelemetry 分布式全链路追踪最佳实践
Java 基础学习总结(203)—— 生成唯一 ID 打印日志记录最佳实践
Java 基础学习总结(203)—— 生成唯一 ID 打印日志记录最佳实践