微服務呼叫鏈追蹤中心搭建

CodeSheep發表於2019-02-28

概述

一個完整的微服務系統包含多個微服務單元,各個微服務子系統存在互相呼叫的情況,形成一個 呼叫鏈。一個客戶端請求從發出到被響應 經歷了哪些元件哪些微服務請求總時長每個元件所花時長 等資訊我們有必要了解和收集,以幫助我們定位效能瓶頸、進行效能調優,因此監控整個微服務架構的呼叫鏈十分有必要,本文將闡述如何使用 Zipkin 搭建微服務呼叫鏈追蹤中心。

注: 本文首發於 My 公眾號 CodeSheep ,可 長按掃描 下面的 小心心 來訂閱 ↓ ↓ ↓

CodeSheep · 程式羊


Zipkin初摸

正如 Ziplin官網 所描述,Zipkin是一款分散式的追蹤系統,其可以幫助我們收集微服務架構中用於解決延時問題的時序資料,更直白地講就是可以幫我們追蹤呼叫的軌跡。

Zipkin的設計架構如下圖所示:

Zipkin設計架構

要理解這張圖,需要了解一下Zipkin的幾個核心概念:

  • Reporter

在某個應用中安插的用於傳送資料給Zipkin的元件稱為Report,目的就是用於追蹤資料收集

  • Span

微服務中呼叫一個元件時,從發出請求開始到被響應的過程會持續一段時間,將這段跨度稱為Span

  • Trace

從Client發出請求到完成請求處理,中間會經歷一個呼叫鏈,將這一個整個過程稱為一個追蹤(Trace)。一個Trace可能包含多個Span,反之每個Span都有一個上級的Trace。

  • Transport

一種資料傳輸的方式,比如最簡單的HTTP方式,當然在高併發時可以換成Kafka等訊息佇列


看了一下基本概念後,再結合上面的架構圖,可以試著理解一下,只有裝配有Report元件的Client才能通過Transport來向Zipkin傳送追蹤資料。追蹤資料由Collector收集器進行手機然後持久化到Storage之中。最後需要資料的一方,可以通過UI介面呼叫API介面,從而最終取到Storage中的資料。可見整體流程不復雜。

Zipkin官網給出了各種常見語言支援的OpenZipkin libraries:

OpenZipkin libraries

本文接下來將 構造微服務追蹤的實驗場景 並使用 Brave 來輔助完成微服務呼叫鏈追蹤中心搭建!


部署Zipkin服務

利用Docker來部署Zipkin服務再簡單不過了:

docker run -d -p 9411:9411 \
--name zipkin \
docker.io/openzipkin/zipkin
複製程式碼

完成之後瀏覽器開啟:localhost:9411可以看到Zipkin的視覺化介面:

Zipkin視覺化介面


模擬微服務呼叫鏈

我們來構造一個如下圖所示的呼叫鏈:

微服務呼叫鏈

圖中包含 一個客戶端 + 三個微服務

  • Client:使用/servicea介面消費ServiceA提供的服務

  • ServiceA:使用/serviceb介面消費ServiceB提供的服務,埠8881

  • ServiceB:使用/servicec介面消費ServiceC提供的服務,埠8882

  • ServiceC:提供終極服務,埠8883

為了模擬明顯的延時效果,準備在每個介面的響應中用程式碼加入3s的延時。

簡單起見,我們用SpringBt來實現三個微服務。

ServiceA的控制器程式碼如下:

@RestController
public class ServiceAContorller {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/servicea”)
    public String servicea() {
        try {
            Thread.sleep( 3000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return restTemplate.getForObject("http://localhost:8882/serviceb", String.class);
    }
}
複製程式碼

ServiceB的程式碼如下:

@RestController
public class ServiceBContorller {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/serviceb”)
    public String serviceb() {
        try {
            Thread.sleep( 3000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return restTemplate.getForObject("http://localhost:8883/servicec", String.class);
    }
}
複製程式碼

ServiceC的程式碼如下:

@RestController
public class ServiceCContorller {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/servicec”)
    public String servicec() {
        try {
            Thread.sleep( 3000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Now, we reach the terminal call: servicec !”;
    }
}
複製程式碼

我們將三個微服務都啟動起來,然後瀏覽器中輸入localhost:8881/servicea來發出請求,過了9s之後,將取到ServiceC中提供的微服務介面所返回的內容,如下圖所示:

微服務鏈式呼叫結果

很明顯,呼叫鏈可以正常work了!

那麼接下來我們就要引入Zipkin來追蹤這個呼叫鏈的資訊!

編寫與Zipkin通訊的工具元件

從Zipkin官網我們可以知道,藉助OpenZipkin庫Brave,我們可以開發一個封裝Brave的公共元件,讓其能十分方便地嵌入到ServiceA,ServiceB,ServiceC服務之中,完成與Zipkin的通訊。

為此我們需要建立一個新的基於Maven的Java專案:ZipkinTool

  • pom.xml中加入如下依賴:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hansonwang99</groupId>
    <artifactId>ZipkinTool</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <version>2.0.1.RELEASE</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.7.RELEASE</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-spring-web-servlet-interceptor</artifactId>
            <version>4.0.6</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-spring-resttemplate-interceptors</artifactId>
            <version>4.0.6</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.reporter</groupId>
            <artifactId>zipkin-sender-okhttp3</artifactId>
            <version>0.6.12</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

</project>
複製程式碼
  • 編寫ZipkinProperties類

其包含endpoint和service兩個屬性,我們最後是需要將該兩個引數提供給ServiceA、ServiceB、ServiceC微服務作為其application.properties中的Zipkin配置

@Data
@Component
@ConfigurationProperties("zipkin")
public class ZipkinProperties {
    private String endpoint;
    private String service;
}
複製程式碼

用了lombok之後,這個類異常簡單!

【注意:關於lombok的用法,可以看這裡

  • 編寫ZipkinConfiguration類

這個類很重要,在裡面我們將Brave的BraveClientHttpRequestInterceptor攔截器註冊到RestTemplate的攔截器呼叫鏈中來收集請求資料到Zipkin中;同時還將Brave的ServletHandlerInterceptor攔截器註冊到呼叫鏈中來收集響應資料到Zipkin中

上程式碼吧:

@Configuration
@Import({RestTemplate.class, BraveClientHttpRequestInterceptor.class, ServletHandlerInterceptor.class})
public class ZipkinConfiguration extends WebMvcConfigurerAdapter {

    @Autowired
    private ZipkinProperties zipkinProperties;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private BraveClientHttpRequestInterceptor clientInterceptor;

    @Autowired
    private ServletHandlerInterceptor serverInterceptor;

    @Bean
    public Sender sender() {
        return OkHttpSender.create( zipkinProperties.getEndpoint() );
    }

    @Bean
    public Reporter<Span> reporter() {
        return AsyncReporter.builder(sender()).build();
    }

    @Bean
    public Brave brave() {
        return new Brave.Builder(zipkinProperties.getService()).reporter(reporter()).build();
    }

    @Bean
    public SpanNameProvider spanNameProvider() {
        return new SpanNameProvider() {
            @Override
            public String spanName(HttpRequest httpRequest) {
                return String.format(
                        "%s %s",
                        httpRequest.getHttpMethod(),
                        httpRequest.getUri().getPath()
                );
            }
        };
    }

    @PostConstruct
    public void init() {
        List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
        interceptors.add(clientInterceptor);
        restTemplate.setInterceptors(interceptors);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(serverInterceptor);
    }
}

複製程式碼

ZipkinTool完成以後,我們需要在ServiceA、ServiceB、ServiceC三個SpringBt專案的application.properties中加入Zipkin的配置:

以ServiceA為例:

server.port=8881
zipkin.endpoint=http://你Zipkin服務所在機器的IP:9411/api/v1/spans
zipkin.service=servicea
複製程式碼

我們最後依次啟動ServiceA、ServiceB、和ServiceC三個微服務,並開始實驗來收集鏈路追蹤資料 !


## 實際實驗

1. 依賴分析

瀏覽器開啟Zipkin的UI介面,可以檢視 依賴分析

點選依賴分析

圖中十分清晰地展示了ServiceA、ServiceB和ServiceC三個服務之間的呼叫關係! 注意,該圖可縮放,並且每一個元素均可以點選,例如點選 ServiceB這個微服務,可以看到其呼叫鏈的上下游!

點選ServiceB微服務


2. 查詢呼叫鏈

接下來我們看一下呼叫鏈相關,點選 服務名,可以看到Zipkin監控到個所有服務:

查詢呼叫鏈

同時可以檢視Span,如以ServiceA為例,其所有REST介面都再下拉選單中:

檢視Span

以ServiceA為例,點選 Find Traces,可以看到其所有追蹤資訊:

Find Traces

點選某個具體Trace,還能看到詳細的每個Span的資訊,如下圖中,可以看到 A → B → C 呼叫過程中每個REST介面的詳細時間戳:

某一個具體Trace

點選某一個REST介面進去還能看到更詳細的資訊,如檢視/servicec這個REST介面,可以看到從傳送請求到收到響應資訊的所有詳細步驟:

某一個具體Span詳細資訊

後記

作者更多的SpringBt實踐文章在此:


如果有興趣,也可以抽點時間看看作者一些關於容器化、微服務化方面的文章:


CodeSheep · 程式羊


相關文章