[jaeger] 三、實現一個分散式呼叫(OkHttp+SpringBoot)

小姐姐味道發表於2019-05-08

很多情況,trace是分佈在不同的應用中的,最常用的遠端呼叫方式就是Http

在這種情況下,我們通常通過增加額外的Http Header傳遞Trace資訊,然後將其組織起來。

本部分通過構建一個目前最火的SpringBoot服務端,然後通過OkHttp3進行呼叫,來展示分散式呼叫鏈的組織方式。

需要的知識:

  • 建立一個簡單的SpringBoot應用
  • 使用OkHttp3發起一個Post請求
  • 瞭解OpenTracing的inject和extract函式

inject & extract函式

這是兩個為了跨程式追蹤而生的兩個函式,力求尋找一種通用的trace傳輸方式。這是兩個強大的函式,它進行了一系列抽象,使得OpenTracing協議不用和特定的實現進行耦合。

  • Carrier 攜帶trace資訊的載體,下文中將自定義一個
  • inject 將額外的資訊注入到相應的載體中
  • extract 將額外的資訊從載體中提取出來

其實,這個載體大多數都是用一個Map(具體是text map)來實現;或者是其他二進位制方式實現。

在本文中,我們就是用了text map,載體的底層就是http頭資訊(也可以通過request params進行傳遞)。

建立一個Server端

maven依賴

首先,通過bom方式import進spring boot的相關配置。

  • spring-boot-dependencies 2.1.3.RELEASE

然後,引入其他依賴

  • opentracing-util 0.32.0
  • jaeger-client 0.35.0
  • logback-classic 1.2.3
  • spring-boot-starter-web 2.1.3.RELEASE
  • okhttp 3.14.1

SpringBoot應用

建立一個SpringBoot應用,埠指定為8888,並初始化預設的Tracer

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan(basePackages = {  "com.sayhiai.example.jaeger.totorial03.controller",
})
public class App extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
    @Bean
    public JaegerTracer getJaegerTracer() {
        return JaegerTracerHelper.initTracer("LoveYou");
    }
}
複製程式碼

在controller目錄下建立一個簡單的服務/hello,通過request body傳遞引數。

關鍵程式碼如下:

@PostMapping("/hello")
@ResponseBody
public String hello(@RequestBody String name,HttpServletRequest request) {
        Map<String, String> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String header = headerNames.nextElement();
            headers.put(header, request.getHeader(header));
        }

        System.out.println(headers);

        Tracer.SpanBuilder builder = null;
        SpanContext parentSpanContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(headers));
        if (null == parentSpanContext) {
            builder = tracer.buildSpan("hello");
        } else {
            builder = tracer.buildSpan("hello").asChildOf(parentSpanContext);
        }

        Span span = builder.start();
複製程式碼

首先拿到頭資訊,並進行extract,如果得到的SpanContext不為空,則代表當前的請求是另外一個應用發起的。在這種情況下,我們把請求的來源,作為當前請求的parent

使用Curl進行呼叫,確保服務能正常執行。

curl -XPOST http://localhost:8888/hello  -H "Content-Type:text/plain;charset=utf-8"   -d "小姐姐味道"
複製程式碼

建立OkHttp3客戶端呼叫

建立載體

OkHttp3是一個非常輕量級的類庫,它的header資訊可以通過以下程式碼設定。

Request.Builder builder;
builder.addHeader(key, value);
複製程式碼

我們在上面提到,將要建立一個自定義的Carrier,這裡通過繼承TextMap,來實現一個。

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);
    }
}
複製程式碼

發起呼叫

使用OkHttp3發起一個簡單的Post請求即可。

public static void main(String[] args) {
    Tracer tracer = JaegerTracerHelper.initTracer("Main");

    String url = "http://localhost:8888/hello";
    OkHttpClient client = new OkHttpClient();
    Request.Builder request = new Request.Builder()
            .url(url)
            .post(RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), "小姐姐味道"));


    Span span = tracer.buildSpan("okHttpMainCall").start();
    Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT);
    Tags.HTTP_METHOD.set(span, "POST");
    Tags.HTTP_URL.set(span, url);
    tracer.activateSpan(span);

    tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, new RequestBuilderCarrier(request));

    client.newCall(request.build()).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            e.printStackTrace();
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            System.out.println(response.body().string());
        }
    });

    span.finish();
}
複製程式碼

注意,在方法中間,我們使用inject函式,將trace資訊附著在RequestBuilderCarrier上進行傳遞。

這兩個函式,使用的就是jaeger的實現。見:

io.jaegertracing.internal.propagation.TextMapCodec
複製程式碼

執行Main方法,檢視Jaeger的後臺,可以看到,我們的分散式Trace已經生成了。

[jaeger] 三、實現一個分散式呼叫(OkHttp+SpringBoot)

End

本文展示了建立分散式呼叫鏈的一般方式。類比此法,可以很容易的寫出基於HttpClient元件的客戶端元件。

接下來,我們將使用Spring的拿手鐗Aop,來封裝通過Feign介面呼叫的SpringCloud服務。你會發現,實現一個類似Sleuth的客戶端收集器,還是蠻簡單的。

[jaeger] 三、實現一個分散式呼叫(OkHttp+SpringBoot)

相關文章