Zipkin Brave源码解读-Tracing(全链路跟踪埋点)(一)

最近在研究全链路跟踪,因某些原因选用了 ZipKin 的 Brave 作为埋点工具,ZipKin不使用,本系列仅做 Brave(版本=5.6.0) 部分源码解读,其他内容不涉及。

对全链路不熟悉的,先学习 opentracing 的标准规范:https://opentracing-contrib.github.io/opentracing-specification-zh/specification.html

先来看一个简单埋点Demo:

import brave.Span;
import brave.Tracer;
import brave.Tracer.SpanInScope;
import brave.Tracing;
import brave.propagation.B3Propagation;
import brave.propagation.ExtraFieldPropagation;

public class Atest {
    public static void main(String[] args) {
        Tracing tracing = Tracing.newBuilder().localServiceName("test")
                .propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
                .build();

        Tracer tracer = tracing.tracer();
        Span root = tracer.nextSpan().name("root").start();//创建跨度 root
        SpanInScope scope = null;
        try {
            scope = tracer.withSpanInScope(root);//设置 root 跨度的作用域
//开始新的跨度 s1。此处使用 currentTraceContext().get() 获取到当前作用域中的 TraceContext
//TraceContext 中包含着链路中的关键信息,如 TraceId, parentId, spanId 等
            Span s1 = tracer.newChild(tracing.currentTraceContext().get()).name("s1").start();
            System.out.println("被跟踪的业务代码...");
            s1.finish();//结束跨度 s1
        } catch (Exception e) {
            root.error(e);//报错处理
        } finally {
            scope.close();//结束作用域
        }
        root.finish();//结束跨度 root
    }
}

执行代码,使用的是 Brave 默认的 LoggerReporter,输出的日志如下,默认使用的是V2版本,比起V1版本的日志,节省了很多日志内容,更加简洁易懂且提升性能。
可见“被跟踪的业务代码...”先被打印出来,接着打印的是跨度 s1,它的父节点是“b236c6f732bca43f”,spanId是“a41bf8dd8fba3f16”,耗时 138 微秒,serviceName是在Tracing设置的“test”,ip地址是“192.168.1.6”
最后打印的是跨度 root,一个完整简单的链路跟踪就完成了。

被跟踪的业务代码...
一月 24, 2019 10:20:11 下午 brave.Tracing$LoggingReporter report
信息: {"traceId":"b236c6f732bca43f","parentId":"b236c6f732bca43f","id":"a41bf8dd8fba3f16","name":"s1","timestamp":1548339611584476,"duration":138,"localEndpoint":{"serviceName":"test","ipv4":"192.168.1.6"}}
一月 24, 2019 10:20:11 下午 brave.Tracing$LoggingReporter report
信息: {"traceId":"b236c6f732bca43f","id":"b236c6f732bca43f","name":"root","timestamp":1548339611569087,"duration":53153,"localEndpoint":{"serviceName":"test","ipv4":"192.168.1.6"}}

本节先看 Tracing 的代码节选,Tracing 主要用于初始化链路跟踪所需的各组件,使用了 Builder 的模式,可根据需求自由去创建合适的链路跟踪特性。
譬如上面所使用的默认的 LoggingReporter,可修改为上送到ZipKin的Reporter。服务名,传播模式等都可以通过Builder定制创建。

public abstract class Tracing implements Closeable {
        //Tracing 内部类 Builder见下方,许多组件都给了缺省定义
    public static final class Builder {
        //服务名与服务器Ip
        String localServiceName = "unknown", localIp;
        int localPort; // 服务端口
        // reporter,用于处理(常见上报给ZipKin或打印到本地)链路信息
        Reporter<zipkin2.Span> spanReporter;
        Clock clock;//用于计时
       //采样器,用于定义采样规则,默认全样采集
        Sampler sampler = Sampler.ALWAYS_SAMPLE; 
        //用于获取当前 TraceContext ,默认使用了 InheritableThreadLocal,支持复制到异步线程
        CurrentTraceContext currentTraceContext = CurrentTraceContext.Default.inheritable();
        //顾名思义,traceId是否128bit,是否支持Join一个跨度
        boolean traceId128Bit = false, supportsJoin = true;
        //传播工厂,用于定义传播规则,如何注入与提取等。
        Propagation.Factory propagationFactory = B3Propagation.FACTORY;
        //错误处理器
        ErrorParser errorParser = new ErrorParser();
        //span结束回调器
        List<FinishedSpanHandler> finishedSpanHandlers = new ArrayList<>();

   //执行build方法创建 Tracing
    public Tracing build() {
      //根据不同的jdk获取clock(Brave支持Jdk1.6+)
      if (clock == null) clock = Platform.get().clock();
      //根据不同的jdk获取ip
      if (localIp == null) localIp = Platform.get().linkLocalIp();
      //默认reporter就是在此处定义了
      if (spanReporter == null) spanReporter = new LoggingReporter();
      //将 Builder 传入创建 Tracing,Default 是 Tracing一个内部类,见下方代码
      return new Default(this);
     }
    }

static final class Default extends Tracing {
//代码太长,见下方代码块,单独说Default
    }
}
static final class Default extends Tracing {
    final Tracer tracer;//Tracer 可以理解为链路对象,用于操作span
    final Propagation.Factory propagationFactory;
    final Propagation<String> stringPropagation;
    final CurrentTraceContext currentTraceContext;
    final Sampler sampler;
    final Clock clock;
    final ErrorParser errorParser;
    final AtomicBoolean noop;

    Default(Builder builder) {
      //初始化过程先默认从builder中获取所需对象
      this.clock = builder.clock;
      this.errorParser = builder.errorParser;
      this.propagationFactory = builder.propagationFactory;
      this.stringPropagation = builder.propagationFactory.create(Propagation.KeyFactory.STRING);
      this.currentTraceContext = builder.currentTraceContext;
      this.sampler = builder.sampler;
      this.noop = new AtomicBoolean();

      List<FinishedSpanHandler> finishedSpanHandlers = builder.finishedSpanHandlers;

      // If a Zipkin reporter is present, it is invoked after the user-supplied finished span handlers.
      FinishedSpanHandler zipkinFirehose = FinishedSpanHandler.NOOP;
      if (builder.spanReporter != Reporter.NOOP) {//若 reporter 不是空操作
        zipkinFirehose = new ZipkinFinishedSpanHandler(builder.spanReporter, errorParser,
            builder.localServiceName, builder.localIp, builder.localPort);
        finishedSpanHandlers = new ArrayList<>(finishedSpanHandlers);
        finishedSpanHandlers.add(zipkinFirehose);
      }

      // 将所有的 FinishedSpanHandler 归集成一个 finishedSpanHandler 
      FinishedSpanHandler finishedSpanHandler =
          FinishedSpanHandlers.noopAware(FinishedSpanHandlers.compose(finishedSpanHandlers), noop);
      //创建一个 Tracer,差不多也是将各种组件初始化到 Tracer 中
      this.tracer = new Tracer(
          builder.clock,
          builder.propagationFactory,
          finishedSpanHandler,
          new PendingSpans(clock, zipkinFirehose, noop),
          builder.sampler,
          builder.currentTraceContext,
          builder.traceId128Bit || propagationFactory.requires128BitTraceId(),
          builder.supportsJoin && propagationFactory.supportsJoin(),
          finishedSpanHandler.alwaysSampleLocal(),
          noop
      );
      maybeSetCurrent();//确保Tracing 唯一
    }
    private void maybeSetCurrent() {
      if (current != null) return;
      synchronized (Tracing.class) {
        if (current == null) current = this;
      }
    }
}

Tracing 的解读基本完成,看Tracing的定义,基本清楚知道整个链路跟踪流程中会使用到的组件有哪些,他们的作用也大概能得知。
下一章继续讲 Tracer。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341