【分布式日志系统】springboot+zipkin+dubbo实现链路跟踪(下)
一、基本操作上一篇写了《【分布式日志系统】springboot+zipkin+dubbo实现链路跟踪(上)》《【分布式日志系统】springboot+zipkin+dubbo实现链路跟踪(中)》,有兴趣的小伙伴可以往回翻翻,这一篇解决如何跟踪。
pom添加依赖(常规操作);
定义公共跟踪配置类
dubbo服务端和调用端集成相应配置(主要是yaml中配置);
验证。
提示:可能不同的框架版本会导致有些地方不生效(如sleuth不支持apache版的dubbo),大家在集成的过程中有问题可以私信我,共同探讨。主框架版本如下:springboot 2.6.6、 dubbo 2.7.12、 zipkin 2.16.3、 brave 5.13.3 nacos-discovery-spring-boot-starter 0.2.10、 nacos-config-spring-boot-starter 0.2.10、
三 工程结构basic-platform:父工程,管理框架版本等基础依赖;
basic-common:基础工具包,“定义公共跟踪基础配置类”等均在此工程,其他工程均依赖该工程;
basic-portal:门户的父工程(pom工程),其下包含三个子工程:
server:前后端分离对应的后端服务 ;
portal-api:定义dubbo服务的接口;
portal-api-impl:定义portal-api对应的接口实现;
basic-foundation:基础的父工程(pom工程),其下包含两个子工程:
foundation-api:定义dubbo服务的接口;
foundation-api-impl:定义 foundation-api对应的接口实现;
主要的代码均在basic-common的pom中,添加如下依赖:
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubboartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>io.zipkin.bravegroupId>
<artifactId>brave-instrumentation-dubboartifactId>
dependency>
<dependency>
<groupId>io.zipkin.bravegroupId>
<artifactId>brave-spring-beansartifactId>
dependency>
<dependency>
<groupId>io.zipkin.bravegroupId>
<artifactId>brave-context-slf4jartifactId>
dependency>
<dependency>
<groupId>io.zipkin.reporter2groupId>
<artifactId>zipkin-sender-okhttp3artifactId>
dependency>
<dependency>
<groupId>io.zipkin.bravegroupId>
<artifactId>brave-instrumentation-spring-webmvcartifactId>
dependency>
<dependency>
<groupId>io.zipkin.bravegroupId>
<artifactId>brave-instrumentation-httpclientartifactId>
dependency>
<dependency>
<groupId>io.zipkin.reporter2groupId>
<artifactId>zipkin-sender-okhttp3artifactId>
dependency>
另外,需要定义一个配置类,来处理RPC的tracing,要不会不生效。
五、其他工程添加配置package com.wd.basic.common.support.trace;
import brave.CurrentSpanCustomizer;
import brave.SpanCustomizer;
import brave.Tracing;
import brave.context.slf4j.MDCScopeDecorator;
import brave.http.HttpTracing;
import brave.propagation.B3Propagation;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.ThreadLocalCurrentTraceContext;
import brave.rpc.RpcTracing;
import brave.sampler.Sampler;
import brave.servlet.TracingFilter;
import brave.spring.webmvc.SpanCustomizingAsyncHandlerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import zipkin2.Span;
import zipkin2.codec.Encoding;
import zipkin2.reporter.AsyncReporter;
import zipkin2.reporter.Sender;
import zipkin2.reporter.okhttp3.OkHttpSender;
import javax.servlet.Filter;
public class TracingConfig implements WebMvcConfigurer {
Sender sender( String url) {
return OkHttpSender.newBuilder()
.encoding(Encoding.PROTO3)
.endpoint(url)
.build();
}
/**
* Configuration for how to buffer spans into messages for Zipkin
*/
AsyncReporter spanReporter(Sender sender) {
AsyncReporter.Builder builder = AsyncReporter.builder(sender);
builder.queuedMaxSpans(50000);
builder.queuedMaxBytes(104857600);
return builder.build();
}
/**
* Controls aspects of tracing such as the service name that shows up in the UI
*/
Tracing tracing(Boolean enable, AsyncReporter spanReporter) { String applicationName,
Tracing.Builder builder = Tracing.newBuilder()
.localServiceName(applicationName)
.propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
.currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
// puts trace IDs into logs
.addScopeDecorator(MDCScopeDecorator.create())
.build()
);
if (enable) {
builder.spanReporter(spanReporter);
builder.sampler(Sampler.ALWAYS_SAMPLE);
} else {
builder.sampler(Sampler.NEVER_SAMPLE);
}
return builder.build();
}
SpanCustomizer spanCustomizer(Tracing tracing) {
return CurrentSpanCustomizer.create(tracing);
}
/**
* Decides how to name and tag spans. By default they are named the same as the http method
*/
HttpTracing httpTracing(Tracing tracing) {
return HttpTracing.create(tracing);
}
RpcTracing rpcTracing(Tracing tracing) {
return RpcTracing.create(tracing);
}
/**
* Creates server spans for http requests
*/
Filter tracingFilter(HttpTracing httpTracing) {
return TracingFilter.create(httpTracing);
}
SpanCustomizingAsyncHandlerInterceptor webMvcTracingCustomizer;
/**
* Decorates server spans with application-defined web tags
*/
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(webMvcTracingCustomizer);
}
}
前提:所有工程均依赖basic-common。 dubbo工程的provider和consumer均添加如下配置(请注意必须添加dubbo.consumer.filter和dubbo.provider.filter)
六、测试dubbo:
application:
name: foundationRpc #服务名称
consumer:
timeout: 3000 #消费超时时间
retries: 1 #重试次数
filter: tracing # 请注意必须添加该配置
check: false
provider:
filter: tracing # 请注意必须添加该配置
protocol:
name: dubbo
zipkin:
enable: true
base:
url: http://127.0.0.1:9411/api/v2/spans
设计如下调用关系: server定义http入口:
public String exec(){
log.info("come in tracing");
return rpcDemoService.tracing();
}
rpcDemoService为server中定义的service,实现如下:
package com.wd.basic.portal.server.modular.demo.service.impl;
import cn.hutool.core.util.StrUtil;
import com.wd.basic.foundation.rpc.test.service.DemoRpcService;
import com.wd.basic.portal.rpc.IDemoService;
import com.wd.basic.portal.rpc.ITraceDemoService;
import com.wd.basic.portal.server.modular.demo.service.RpcDemoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
/**
* rpc演示服务impl
*
* @author 小尘哥
* @date 2022/04/14
*/
public class RpcDemoServiceImpl implements RpcDemoService {
private IDemoService demoService2;
private IDemoService demoService1;
private ITraceDemoService traceDemoService;
private DemoRpcService demoRpcService;
public String tracing() {
log.info("come in exec service");
String rpc2 = demoService2.testRpc();
return StrUtil.join(",", rpc2);
}
}
IDemoService 定义在portal-api中,实现如下
package com.wd.basic.portal.rpc.impl;
import com.wd.basic.foundation.rpc.test.service.DemoRpcService;
import com.wd.basic.portal.rpc.IDemoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
/**
* 演示服务impl
*
* @author 小尘哥
* @date 2022/03/16
*/
@Slf4j
@DubboService(interfaceClass = IDemoService.class,version = "2.0")
public class DemoServiceV2Impl implements IDemoService {
@DubboReference(interfaceClass = DemoRpcService.class, version = "1.0")
private DemoRpcService demoRpcService;
@Override
public String testRpc() {
log.info("来自统一门户 RPC 测试");
demoRpcService.testRpc();
return "调用到了rpc服务 version 2.0.0";
}
}
DemoRpcService 定义在foundation-api中,实现如下:
package com.wd.basic.foundation.rpc.test.service.impl;
import com.wd.basic.foundation.rpc.test.service.DemoRpcService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
/**
* test Rpc服务
* @author 上官婉儿
*/
@Slf4j
@DubboService(interfaceClass = DemoRpcService.class,version = "1.0")
public class DemoRpcServiceImpl implements DemoRpcService {
@Override
public String testRpc() {
log.info("来自基础服务 RPC 测试");
return "RPC服务! from foundation";
}
}
七、日志配置
临门一脚了,以上都配置好了,但是哪里能看到效果呢?肯定是需要从日志来跟踪,我们采用springboot推荐的logback来记录日志,请注意日志记录格式,添加了部分内容“【%X{traceId},%X{spanId},%X{parentId}】”,完整配置如下:
八、验证<configuration scan="true" scanPeriod="10 seconds">
<contextName>Logback For Basic PlatformcontextName>
<springProperty scope="context" name="logDir" source="custom.log.dir" defaultValue="D:/logback"/>
<springProperty scope="context" name="logLevel" source="custom.log.level" defaultValue="info"/>
<springProperty scope="context" name="logMaxHistory" source="custom.log.max-history" defaultValue="180"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} 【%X{traceId},%X{spanId},%X{parentId}】 [%thread] %-5level %logger-%msg%n %ex{5}pattern>
<charset class="java.nio.charset.Charset">UTF-8charset>
encoder>
appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${logDir}/%d{yyyy-MM-dd}/pf.%i.log.zipfileNamePattern>
<maxFileSize>50MBmaxFileSize>
<maxHistory>${logMaxHistory}maxHistory>
<totalSizeCap>10GBtotalSizeCap>
rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} 【%X{traceId},%X{spanId},%X{parentId}】 [%thread] %-5level %logger-%msg%n %ex{5}pattern>
encoder>
appender>
<appender name="dayLogAsyncAppender" class="ch.qos.logback.classic.AsyncAppender">
<includeCallerData>trueincludeCallerData>
<discardingThreshold>0discardingThreshold>
<queueSize>512queueSize>
<appender-ref ref="FILE"/>
appender>
<logger name="org.springframework" level="${logLevel}"/>
<root level="${logLevel}">
<appender-ref ref="STDOUT" />
<appender-ref ref="dayLogAsyncAppender" />
root>
configuration>
来自server的日志
2022-04-26 16:44:25.817 【2926aa52265ad112,2926aa52265ad112,】 [http-nio-8080-exec-1] INFO com.wd.basic.portal.server.modular.demo.controller.RpcDemoController-come in tracing
2022-04-26 16:44:25.826 【2926aa52265ad112,2926aa52265ad112,】 [http-nio-8080-exec-1] INFO com.wd.basic.portal.server.modular.demo.service.impl.RpcDemoServiceImpl-come in exec service
来自portal-api的日志
2022-04-26 16:44:25.882 【2926aa52265ad112,6a4da6311934ab65,2926aa52265ad112】 [DubboServerHandler-10.1.252.224:20881-thread-5] INFO com.wd.basic.portal.rpc.impl.DemoServiceV2Impl-来自统一门户 RPC 测试
来自foundation-api的日志
2022-04-26 16:44:25.926 【2926aa52265ad112,53dbfb344223ef50,6a4da6311934ab65】 [DubboServerHandler-10.1.252.224:20880-thread-3] INFO com.wd.basic.foundation.rpc.test.service.impl.DemoRpcServiceImpl-来自基础服务 RPC 测试
九、日志分析提取三部分日志中括号中的内容:
统一门户:traceId = 2926aa52265ad112,spanId=2926aa52265ad112,parentId=’’;
统一门户RPC服务:traceId = 2926aa52265ad112,spanId=6a4da6311934ab65,parentId=2926aa52265ad112;
基础服务RPC服务:traceId = 2926aa52265ad112,spanId=53dbfb344223ef50,parentId=6a4da6311934ab65;
说明:
对于server,作为请求入口,因此traceId和spanId相同,无上游服务,因此parentId为空。
对于portal-api服务,parentId=统一门户请求入口的spanId,因此它是统一门户的下游服务。
对于foundation-api服务,parentId=同一门户RPC服务的spanId,因此它是统一门户RPC服务的下游服务。
综上,和测试开始时的设计逻辑一致,由traceId串联整个请求过程,由spanId和parentId构建上下游调用关系,完成dubbo服务之间调用的链路跟踪。