前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Zipkin实现分布式链路追踪

Zipkin实现分布式链路追踪

作者头像
每天学Java
发布2020-06-02 10:17:29
1.4K0
发布2020-06-02 10:17:29
举报
文章被收录于专栏:每天学Java每天学Java
前言

如今越来越多的互联网公司在架构上开始走向分布式,比如微服务,分布式数据库,分布式缓存等等。好处很明显,高扩展性,高可用,高性能。缺点也有比如分布式事务,分布式锁,数据一致性等等,而这些缺点的解决方案也是我们面试过程中很容易被问到的。

今天我们来谈一下在分布式架构中另一个问题,如何进行链路追踪。为什么需要实现这个功能?在业务繁杂的分布式中,服务间的调用可能是比较复杂的,如果前台应用调用服务失败,我们如何快速的定位是哪个服务造成的。寻常的日志排查就非常费时了。所以分布式调用链的作用就显现出来了。

分布式调用链其实就是将一次分布式请求还原成调用链路。显式的在后端查看一次分布式请求的调用情况,比如各个节点上的耗时、请求具体打到了哪台机器上、每个服务节点的请求状态等等。

正文

许多大型互联网公司都拥有自己的分布式链路追踪系统,比如阿里的鹰眼,谷歌的Drapper,Twitter的ZipKin等等。

这里我们通过ZipKin来了解链路追踪。集成ZipKin应该是较为简单的,我们大概的看一下,然后通过分析slueth和zipkin的源码,了解其实现原理。

首先是下载ZipKin的服务应用,之前的版本可以通过SpringBoot集成ZipKin自己开启一个Zipkin的应用,但是现在可以直接下载jar包启动。(链接:https://pan.baidu.com/s/1XcZfnI3nF7X8tLVNz9ELwQ 密码:5ki9)

启动方式非常简单 java -jar 即可。

启动服务之后,http://127.0.0.1:9411可看到ZipKin的UI界面。这个时候界面是空的,因为还没有链路请求发送到这里。

现在我们要做的就是在我们微服务中集成Zipkin,将请求信息发送到这个服务上。集成很简单,我们只要导入下面依赖就OK。

代码语言:javascript
复制
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

接下来就是服务调用了,这里我使用Eureka作为注册中心,服务间通讯使用Feign(公众号有专门的微服务SpringCloud模块,这里就不在赘叙了)。

代码语言:javascript
复制
@Autowired

为了测试成功和失败,我在homeWorkServer的getHomeWork2打了断点,使其调用超时,而getHomeWork正常。调用之后我们进入ZipKin的UI界面。上面两个是错误,最后一个是正常的。

我们点进去看一下:可以看到如下信息,服务耗时,持续时间,深度,Span总数(Span可以理解为用来描述一次RPC调用)

我们继续点开,会发现error的信息,描述了超时的问题是由于http://server-homework/getHomeWork导致的,也就是我们断点导致超时的异常。

到这里我们就完成了简单的集成,ZipKin服务的数据是存在内存当中的,我们也可以通过配置,与ES或者MySQL做数据的集成,这里我就不多说了,因为我也没有集成过。

如何实现

ZipKin的原理是什么呢?这里我就自己集成方案来进行解析(自己研究的,可能不是很正确和完善)。

这篇文章的集成方案中,不仅仅是ZipKin,还有slueth,但是slueth是有ZipKin去引入的,slueth本身也是SpringCloud提供的服务治理组件之一,其功能就是生成分布式链路调用日志,但是目前仍以日志的形式输出,Zipkin结合了slueth使我们能以界面形式展现。

图:zipkin引入slueth

说到这里我们并没有说到链路追踪的实现原理,在我看源码之前我就在想方案很有可能就是:拦截器(Interceptor),过滤器(Filter),切面织入(AOP),实际上三个都用到了。下图中我们可以看到instrment这个包下就是拦截组件(Spring Cloud Sleuth可以追踪10种类型的组件对应图上10个包名),由于个人精力问题,这里我目前只是看了web下的部分源码。

图:slueth支持的组件

是不是文件有点多,我一开始点开的时候,头皮发麻,脑海里想的是:这不是玩我吗。但是不要怂,看目录我们先找到我们能猜出来是干嘛的文件(大佬教我的方案:看别人的源码,首先从文件名找到感觉自己能看懂的,然后继续追踪)。那下图中哪些我们能猜到是干嘛的?首先Filter后缀的,十有八九就是过滤器,Interceptor那应该就是拦截器了,AutoConfiguration后缀的配置文件可能性是跑不了的,Aspect后缀绝对是AOP的应用。Processsor,Enable这些我们就扔一边吧。。。。

既然知道实现离不开过滤器,拦截器,AOP这些,参考我们自己实现这些功能的过程,我们可以想到有些必须的组件,比如Springboot中实现拦截器肯定要有WebMvcConfigure的实现类,或者WebMvcConfigurationSupport的子类,于是我就盯上了TraceWebMvcConfigurer,看着最像,打开源码(如下),果然是拦截器的配置。

代码语言:javascript
复制
package org.springframework.cloud.sleuth.instrument.web;

import brave.spring.webmvc.SpanCustomizingAsyncHandlerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@Import({SpanCustomizingAsyncHandlerInterceptor.class})
class TraceWebMvcConfigurer implements WebMvcConfigurer {
    @Autowired
    ApplicationContext applicationContext;

    TraceWebMvcConfigurer() {
    }

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor((HandlerInterceptor)this.applicationContext.getBean(SpanCustomizingAsyncHandlerInterceptor.class));
    }
}

知道配置了拦截器,那么我就继续往下追踪SpanCustomizingAsyncHandlerInterceptor这个继承HandlerInterceptorAdapter的类了,我们可以发现getAttribute这个方法来获取SpanCustomizer。如果获取到就会填充SpanCustomizer的tag参数。

代码语言:javascript
复制
package brave.spring.webmvc;

import brave.SpanCustomizer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public final class SpanCustomizingAsyncHandlerInterceptor extends HandlerInterceptorAdapter {
    @Autowired(
        required = false
    )
    HandlerParser handlerParser = new HandlerParser();

    SpanCustomizingAsyncHandlerInterceptor() {
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) {
        SpanCustomizer span = (SpanCustomizer)request.getAttribute(SpanCustomizer.class.getName());
        if (span != null) {
            this.handlerParser.preHandle(request, o, span);
        }

        return true;
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        SpanCustomizer span = (SpanCustomizer)request.getAttribute(SpanCustomizer.class.getName());
        if (span != null) {
            SpanCustomizingHandlerInterceptor.setHttpRouteAttribute(request);
        }

    }
}

其次就是Filter,如TraceWebFilter。个人觉得Filter的作用应该在拦截器之上,因为SpingBoot项目中,如果我们自定义了拦截器,那么导入拦截器将不会生效,这个本人经过验证,在该项目中,当我实现自定义的拦截器的时候,slueth拦截器断点并不会进入,而放弃自定义拦截器或者采用继承WebMvcConfigure(我通常采用继承WebMvcConfigurationSupport实现拦截器)才会走进slueth的断点,但是无论slueth断点是否生效,ZipKin的数据仍然是全面的。

然后就是切面了,我们看下图,它拦截了RestController,Controller等注解,实现调用织入。

最后就是数据如何发送到ZipKin了。LoadBalancerClientZipkinLoadBalancer下有一个instance方法其作用就是发送数据到服务端,而getBaseUrl方法返回的就是服务的URL了,默认:http://localhost:9411

关于ZipKin与slueth本人目前也是学习到这里,自己给自己的定位是,大概理解实现原理的外壳,但是其核心并没有完全搞懂,很多困惑仍然没有得到答案,所以分享只能到这里结束,在后续将会继续分享通过源码学习到相关知识,也欢迎大家加群讨论。


关于分布式链路追踪的设计一般由如下几点(如果自己实现的话):

1.埋点日志:埋点即系统在当前节点的上下文信息,埋点日志通常要包含:

TraceId、RPCId、调用的开始时间,调用类型,协议类型,调用方ip和端口,请求的服务名等信息; 调用耗时,调用结果,异常信息,消息报文等; 预留可扩展字段,为下一步扩展做准备

2.然后是代码的侵入性要低,服务透明,低损耗,扩展性。

3.抓取和存储日志

4.分析和统计调用链数据

5.计算和展示

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
  • 如何实现
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档