OpenTracing Java Library教程(3)——跨服务传递SpanContext

栏目: IT技术 · 发布时间: 4年前

内容简介:本文内容主要翻译(意译)自Yurishkuro大神的学习如何:为了演示跨服务做链路跟踪,我们先来构建几个服务:

本文内容主要翻译(意译)自Yurishkuro大神的 opentracing-tutorial java ,加了一些补充说明,方便理解,习惯看英文的也可以看原文。总共4篇,本文是第3篇。如果你还没接触过OpenTracing,建议先读这篇文章《 OpenTracing概念术语介绍 》和 官方文档

  1. 第1篇:单span的trace创建
  2. 第2篇:多span的trace创建(进程内SpanContext传递 )。
  3. 第3篇:跨服务(进程)传递SpanContext本文 )。
  4. 第4篇:Baggage介绍

学习如何:

  • 跨服务做链路跟踪
  • 使用 InjectExtract 方法在服务间传递span上下文(SpanContext)信息
  • 使用一些OpenTracing推荐的tags

开发流程

构建Hello-World微服务

为了演示跨服务做链路跟踪,我们先来构建几个服务:

  • Hello.java :基于上一节的代码,修改了部分代码,增加了HTTP请求代码。
  • Formatter.java :基于Dropwizard-based的HTTP服务器,提供这样的一个接口:发送 GET 'http://localhost:8081/format?helloTo=Bryan' ,返回 "Hello, Bryan!" 字符串。
  • Publisher.java :类似 Formatter.java ,提供这样一个接口:发送 GET 'http://localhost:8082/publish?helloStr=hi%20there' 请求,就往标准输出打印一个 "hi there" 字符串。

先把后面两个HTTP Server运行起来:

// terminal tab 1
$ ./run.sh lesson03.exercise.Formatter server
// terminal tab 2
$ ./run.sh lesson03.exercise.Publisher server

然后发送一个HTTP请求:

$ curl 'http://localhost:8081/format?helloTo=Bryan'
Hello, Bryan!

如果出现上面打印,说明我们的服务已经OK了。

最后我们像前一篇文章一样,继续运行Hello服务:

./run.sh lesson03.solution.Hello Bryan

进程/服务间链路信息传递

虽然我们的Hello中做了两个RPC请求(HTTP也是RPC的一种),但运行之后会发现链路图和之前的一样:产生了一个包含三个span的链路,都是 hello-world 这个服务产生的。我们当然希望链路可以展示出这个调用中的涉及的所有服务,这个时候就需要实现在服务间(即跨进程)传递链路信息。链路信息一般包装在上下文中,这个上下文称之为SpanContext:一般至少包含链路的状态信息(比如traceID、spanID等)和Baggage信息。Baggage信息下篇文章介绍。所以链路信息的传递就是传递这个SpanContext。OpenTracing提供了一个抽象,在Tracer接口中定义了两个接口:

inject(spanContext, format, carrier)
extract(format, carrier)

按照OpenTracing API定义, format 参数表示SpanContext的编码格式(或者说传递方式吧),需要为以下三个编码之一:

TEXT_MAP
BINARY
HTTP_HEADERS

carrier是基于底层RPC框架做的一层抽象,用于传递SpanContext。比如 TEXT_MAP 格式对应的carrier接口允许tracer实例通过 put(key, value) 方法将key-value格式的数据写入到请求中。同理,BINARY格式的就是 ByteBuffer

下面我们看如何通过inject和extract来实现进程间的链路上下文信息传递。

客户端增强

首先需要在客户端发送HTTP请求前将SpanContext注入进去,发送给服务端。现在的HTTP请求是封装在 Hello#getHttp() 中的,所以在这里加:

import io.opentracing.propagation.Format;
import io.opentracing.tag.Tags;

Tags.SPAN_KIND.set(tracer.activeSpan(), Tags.SPAN_KIND_CLIENT);
Tags.HTTP_METHOD.set(tracer.activeSpan(), "GET");
Tags.HTTP_URL.set(tracer.activeSpan(), url.toString());
tracer.inject(tracer.activeSpan().context(), Format.Builtin.HTTP_HEADERS, new RequestBuilderCarrier(requestBuilder));

这里是以TEXT_MAP(HTTP_HEADERS)编码SpanContext的,所以需要实现TextMap类:

import java.util.Iterator;
import java.util.Map;

import okhttp3.Request;

public class RequestBuilderCarrier implements io.opentracing.propagation.TextMap {
    private final Request.Builder builder;

    RequestBuilderCarrier(Request.Builder builder) {
        this.builder = builder;
    }

    @Override
    public Iterator<Map.Entry<String, String>> iterator() {
        throw new UnsupportedOperationException("carrier is write-only");
    }

    @Override
    public void put(String key, String value) {
        builder.addHeader(key, value);
    }
}

tracer会调用put方法将SpanContext中的信息以key-value的形式加到HTTP头中。这里的信息主要是我们写的一些跟请求想关的Tags信息。

这样,客户端已经通过inject将SpanContext加入到请求中了。接下来看服务端收到请求后,如何使用extract取出这些信息。

服务端增强

服务端增强和客户端类似,先参照客户端创建一个Tracer实例。这部分一样,就略过了,重点看如何取出SpanContext信息。

这里封装一个 startServerSpan 函数,这个函数实现的功能如下:

extract
public static Span startServerSpan(Tracer tracer, javax.ws.rs.core.HttpHeaders httpHeaders, String operationName) {
    // format the headers for extraction
    MultivaluedMap<String, String> rawHeaders = httpHeaders.getRequestHeaders();
    final HashMap<String, String> headers = new HashMap<String, String>();
    for (String key : rawHeaders.keySet()) {
        headers.put(key, rawHeaders.get(key).get(0));
    }

    Tracer.SpanBuilder spanBuilder;
    try {
        SpanContext parentSpanCtx = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(headers));
        if (parentSpanCtx == null) {
            spanBuilder = tracer.buildSpan(operationName);
        } else {
            spanBuilder = tracer.buildSpan(operationName).asChildOf(parentSpanCtx);
        }
    } catch (IllegalArgumentException e) {
        spanBuilder = tracer.buildSpan(operationName);
    }
    return spanBuilder.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER).start();
}

Formatter和Publisher两个服务都需要做这个事情。有了这个span,就可以使用了,这里展示一下Formatter代码的代码:

@GET
public String format(@QueryParam("helloTo") String helloTo, @Context HttpHeaders httpHeaders) {
    // 调用封装的startServerSpan函数,基于客户端传递过来SpanContext的创建一个新的span,并在tags中加入服务端的一些信息
    Span span = startServerSpan(tracer, httpHeaders, "format");
    try (Scope scope = tracer.scopeManager.activate(span)) {
        String helloStr = String.format("Hello, %s!", helloTo);
        span.log(ImmutableMap.of("event", "string-format", "value", helloStr));
        return helloStr;
    } finally {
      span.finish();
    }
}

至此,服务端的增强也实现好了,是时候见证奇迹了。

见证奇迹

重新运行Formatter、 Publisher和Hello服务(我没有改时区,所以日志中的时间差了8小时,实际现在是周日早晨8点):

// terminal tab 1:启动Formatter服务
$ ./run.sh lesson03.exercise.Formatter server
// 省略了部分日志
INFO  [2020-07-12 00:57:48,181] io.jaegertracing.internal.reporters.LoggingReporter: Span reported: 2b20ca6e8ddc6547:2eb6a1fbef6e9789:8a92a88a65fb4776:1 - format
127.0.0.1 - - [12/Jul/2020:00:57:48 +0000] "GET /format?helloTo=Bryan HTTP/1.1" 200 13 "-" "okhttp/3.9.0" 4

// terminal tab 2:启动Publisher服务
$ ./run.sh lesson03.exercise.Publisher server
// 省略了部分日志
Hello, Bryan!
INFO  [2020-07-12 00:57:48,440] io.jaegertracing.internal.reporters.LoggingReporter: Span reported: 2b20ca6e8ddc6547:93916ee579078535:75065f170bf15bff:1 - publish
127.0.0.1 - - [12/Jul/2020:00:57:48 +0000] "GET /publish?helloStr=Hello,%20Bryan! HTTP/1.1" 200 9 "-" "okhttp/3.9.0" 137

// terminal tab 3:启动Hello,启动后会分别调用Formatter服务和Publisher服务
-> % ./run.sh lesson03.solution.Hello Bryan 
// 省略了部分日志
08:57:48.206 [main] INFO io.jaegertracing.internal.reporters.LoggingReporter - Span reported: 2b20ca6e8ddc6547:8a92a88a65fb4776:2b20ca6e8ddc6547:1 - formatString
08:57:48.468 [main] INFO io.jaegertracing.internal.reporters.LoggingReporter - Span reported: 2b20ca6e8ddc6547:75065f170bf15bff:2b20ca6e8ddc6547:1 - printHello
08:57:48.468 [main] INFO io.jaegertracing.internal.reporters.LoggingReporter - Span reported: 2b20ca6e8ddc6547:2b20ca6e8ddc6547:0:1 - say-hello

然后看下生成的链路图:

OpenTracing Java Library教程(3)——跨服务传递SpanContext

可以看到新生成的链路包含了3个服务,共5个span。点击查看链路详情:

OpenTracing Java Library教程(3)——跨服务传递SpanContext

从右侧可以清楚的看出调用关系,左侧可以看出耗时。然后再看下每个服务的一些详细信息:

OpenTracing Java Library教程(3)——跨服务传递SpanContext

Tags包含了各个span的一些关键信息。

本文主要展示了如何跨进程/服务传递SpanContext,下一篇介绍另外一种传递信息的方式,也是SpanContext中非常重要的一部分:Baggage。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Everything Store

The Everything Store

Brad Stone / Little, Brown and Company / 2013-10-22 / USD 28.00

The definitive story of Amazon.com, one of the most successful companies in the world, and of its driven, brilliant founder, Jeff Bezos. Amazon.com started off delivering books through the mail. Bu......一起来看看 《The Everything Store》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具