分布式-zipkin+springboot链路追踪
zipkin是一个用于分布式系统调用跟踪的系统,通过统一的 TraceId 将调用链路中的各种网络调用情况以日志的方式记录下来,以达到透视化网络调用的目的,这些链路数据可用于故障的快速发现,服务治理等
背景
- 在当下的技术架构实施中,统一采用面向服务的分布式架构,通过服务来支撑起一个个应用,而部署在应用中的各种服务通常都是用复杂大规模分布式集群来实现的。同时,这些应用又构建在不同的软件模块上,这些软件模块,有可能是由不同的团队开发,可能使用不同的编程语言来实现、有可能部署了几千台服务器。因此,就需要一些可以帮助理解各个应用的线上调用行为,并可以分析远程调用性能的组件。
- zipkin通过全局唯一的 TraceId 作为一次链路调用的唯一标识,在特定的一次链路调用中,这个 TraceId 会一直保持不变并传递给被调用的系统。有了 TraceId 我们能够唯一的标识和确定这次调用链路,但是却无法标识出这个链路的调用层次是如何的?那么如何解决这个问题呢,那就是 SpanId,SpanId 标示的一次链路调用所在的层次,SpanId 的不同即标示这次链路调用处在不同的层次上。
关于zipkin中的几个概念
- traceId:代表一次完整请求调用链中全局唯一的Id
- spanId:代表一次调用自整个调用链路树中的层次位置
- parentId:代表一次调用链路树中本层次的父级层次的spantId,通过spandId与parentId的结合,可以有效的分析出一次调用链中应用的层次和依赖关系
zipkin-server的安装与启动(源码方式)
- 首先下载zipkin源码,附上gitHub地址:https://github.com/openzipkin/zipkin
- 下载完成后,在根目录编译整个项目,可以使用maven进行编译:mvn clean package
- 编译完成后在zipkin-server的target目录下会生成可执行的jar文件(如):zipkin-server-2.11.9-SNAPSHOT-exec.jar
- 使用java -jar zipkin-server-2.11.9-SNAPSHOT-exec.jar 即可启动zipkinServer,启动完成后本地浏览器访问http://localhost:9411即可看到web管理台页面
本文概要
- 上面简单介绍了zipkin的相关知识以及zipkinServer的启动,当然也可以是用docker方式启动,现在就来说一下本文主要做的事情是什么
- 为了尽量的简化配置,本次基于SpringBoot架构进行实践,首先创建一个RpcServer后端应用A,以及一个Restful的应用B,并且B依赖调用A,最后通过http方式访问B应用,使用zipkin将整个请求调用的信息收集起来达到可视化分析的效果
创建一个RpcServer应用
- 关于pom文件的相关关键依赖,需要注意的是为了避免文章过于杂乱这里仅贴出关键依赖,另外还有dubbo、curator等依赖,以及有可能需要解决的版本依赖冲突
pom.xml
<!--************************spring boot *************************--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <version>2.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.0.4.RELEASE</version> </dependency> <!--************************ zipkin ***********************--> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-instrumentation-dubbo-rpc</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-sender-okhttp3</artifactId> <version>2.7.10</version> </dependency>
application.properties
spring.application.name=rpcServer server.port=8080 #############dubbo 配置############ registry.protocol=zookeeper registry.address=*** ################tracer 配置######## trace.brave.serviceName=rpcServer trace.brave.zipkin=http://localhost:9411/api/v2/spans trace.brave.rate=1.0
dubbo-service.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- dubbo 配置 --> <dubbo:application name="${spring.application.name}"/> <dubbo:registry protocol="${registry.protocol}" address="${registry.address}"/> <!-- 服务注册 --> <bean id="rpcService" class="com.rpcserver.serviceimpl.RpcServiceImpl"/> <dubbo:service interface="com.rpcserver.service.RpcService" ref="rpcService"/> <!--tracing--> <dubbo:provider filter="tracing"/> </beans>
创建一个Config对象TracerAutoConfiguration,用于加载Trace相关的Bean(服务埋点)
@Configuration public class TracerAutoConfiguration { @Value("${trace.brave.serviceName}") private String serviceName; @Value("${trace.brave.zipkin}") private String zipkin; @Value("${trace.brave.rate}") private float rate; @Bean public Sender sender(){ return OkHttpSender.create(zipkin); } @Bean public AsyncReporter<Span> spanAsyncReporter(){ return AsyncReporter.create(sender()); } @Bean public Tracing tracing(){ return Tracing.newBuilder() .localServiceName(serviceName) .spanReporter(spanAsyncReporter()) .sampler(Sampler.create(rate)).build(); } }
关于RpcServiceImpl的简单实现这里就不贴出了,无其他tracer代码入侵,按照正常的dubbo Service服务端实现公共接口即可,接着是SpringBoot的启动类,需要注意的是配置文件以及扫描包的路径配置成自己对应的包名以及路径,如下:
@SpringBootApplication @ComponentScan(basePackages = {"com.rpcserver"}) @ImportResource({"classpath:dubbo/*.xml"}) public class RpcServerApplication { public static void main(String[] args) { SpringApplication.run(RpcServerApplication.class,args); } }
创建一个Restful应用
- 由于该应用直接接受的是http请求,所以相对与上面的rpc后端应用来说需要多加一层http的服务埋点,这样才能追踪到完整的请求链路
pom.xml(同样只贴出部分关键依赖,依赖冲突处理也删除了)
<!-- ####################spring-boot##########################--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <version>2.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.0.4.RELEASE</version> </dependency> <!--**********************tracer begin***************************--> <dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-sender-okhttp3</artifactId> <version>2.7.10</version> </dependency> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-spring-web-servlet-interceptor</artifactId> <version>4.9.1</version> </dependency> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-instrumentation-spring-web</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-instrumentation-spring-webmvc</artifactId> <version>4.16.1</version> </dependency> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-instrumentation-dubbo-rpc</artifactId> <version>5.2.0</version> </dependency>
application.properties
spring.application.name=tracerMvc server.port=8081 #############dubbo 配置############ registry.protocol=zookeeper registry.address=*** #############zipkin############### trace.brave.serviceName=tracerMvc trace.brave.zipkin=http://localhost:9411/api/v2/spans trace.brave.rate=1.0
dubbo-consumer.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- dubbo 配置 --> <dubbo:application name="${spring.application.name}"/> <dubbo:registry protocol="${registry.protocol}" address="${registry.address}"/> <!-- 服务发现 --> <dubbo:reference interface="com.rpcserver.service.RpcService" id="rpcService"/> <!--rpc tracing--> <dubbo:consumer filter="tracing"/> </beans>
同样,创建一个服务埋点类TraceAutoConfiguration
@Configuration @Import({TracingHandlerInterceptor.class}) public class TraceAutoConfiguration extends WebMvcConfigurerAdapter{ @Value("${trace.brave.serviceName}") private String serviceName; @Value("${trace.brave.zipkin}") private String zipkin; @Value("${trace.brave.rate}") private float rate; @Autowired private TracingHandlerInterceptor tracingHandlerInterceptor; @Bean public Sender sender(){ return OkHttpSender.create(zipkin); } @Bean public AsyncReporter<Span> spanAsyncReporter(){ return AsyncReporter.create(sender()); } @Bean public Tracing tracing(){ return Tracing.newBuilder() .localServiceName(serviceName) .spanReporter(spanAsyncReporter()) .sampler(Sampler.create(rate)).build(); } @Bean HttpTracing httpTracing(Tracing tracing){ HttpTracing httpTracing = HttpTracing.create(tracing); return httpTracing.toBuilder() .serverParser(new HttpServerParser(){ @Override protected <Req> String spanName(HttpAdapter<Req, ?> adapter, Req req) { return adapter.path(req); } }).clientParser(new HttpClientParser(){ @Override protected <Req> String spanName(HttpAdapter<Req, ?> adapter, Req req) { return adapter.path(req); } }).build(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tracingHandlerInterceptor); } }
Controller,无trace代码入侵,正常的引入dubbo服务使用即可
@Controller @RequestMapping("/tracerMvc") public class TracerMvcController { @Autowired private RpcService rpcService; @RequestMapping("/getMsg") @ResponseBody public String getMsg(){ rpcService.RpcMethod("sb-dubbo-comsumer !"); return "hi zipkin"; } }
SpringBoot启动类,同样根据自己的路径配置
@SpringBootApplication @ComponentScan(basePackages = {"com.tracer"}) @ImportResource({"classpath:dubbo/*.xml"}) public class ApplicationMvc { public static void main(String[] args) { SpringApplication.run(ApplicationMvc.class,args); } }
启动服务
- 到这里,相关的服务已经创建完成,下面开始测试链路追踪
- 首先,启动zipkinServer
- 然后启动RpcServer后端服务,紧接着启动Restful消费服务,可以观察dubbo控制台,查看服务是否启动正常
- 当服务启动完成后,通过访问Controller的接口/tracerMvc/getMsg完成一次请求调用
请求链路分析
- 当上述步骤都成功完成以后,打开zipkin管理台可以看到如下数据,zipkin已经获取到了一次请求数据
- 继续点击这次请求的调用链,得到如下信息,可以看到请求在每个应用所消耗的时间,以及调用的接口信息
- 继续点击相关的调用链,可以查看到更加详细的信息,如客户端与服务端两边在一次请求中各个阶段的耗时,以及traceId等相关信息
- 在zipkin的web管理台点击依赖分析,还能查看出应该的依赖关系图,如下
总结
- 在分布式应用越来越广泛的今天,对于应用的请求链路追踪是非常有必要的,例如在应用出现异常的时候(如一次请求超时了,到底是在哪个环节超时了),为了快速的定位问题,就需要对一次请求链路信息的完整分析,zipkin可以帮助我们收集分析分布式系统的一次完整请求链路的相关信息,这需要我们在系统的关键位置进行服务埋点,其实zipkin的应用扩展非常使用,可以在很多场景下扩展,如在日志方面的应用,我们可以根据一次请求的唯一traceId来快速定位关键日志信息进行分析