[jaeger] 3、實現一個分佈式調用(OkHttp+SpringBoot)

不少狀況,trace是分佈在不一樣的應用中的,最經常使用的遠程調用方式就是Httpjava

在這種狀況下,咱們一般經過增長額外的Http Header傳遞Trace信息,而後將其組織起來。git

本部分經過構建一個目前最火的SpringBoot服務端,而後經過OkHttp3進行調用,來展現分佈式調用鏈的組織方式。github

更多連載關注小姐姐味道,本文相關代碼見:web

https://github.com/sayhiai/example-jaeger-opentracing-tutorial-003
複製代碼

須要的知識:spring

建立一個簡單的SpringBoot應用
使用OkHttp3發起一個Post請求
瞭解OpenTracing的inject和extract函數
複製代碼

inject & extract函數

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

Carrier 攜帶trace信息的載體,下文中將自定義一個
inject 將額外的信息`注入`到相應的載體中
extract 將額外的信息從載體中`提取`出來
複製代碼

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

在本文中,咱們就是用了text map,載體的底層就是http頭信息(也能夠經過request params進行傳遞)。curl

建立一個Server端

maven依賴

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

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已經生成了。

End

本文展現了建立分佈式調用鏈的通常方式。類比此法,能夠很容易的寫出基於HttpClient組件的客戶端組件。

接下來,咱們將使用Spring的拿手鐗Aop,來封裝經過Feign接口調用的SpringCloud服務。你會發現,實現一個相似Sleuth的客戶端收集器,仍是蠻簡單的。

相關文章
相關標籤/搜索