指標與追蹤:相輔相成

工程 | Tommy Ludwig | 2021 年 2 月 10 日 | ...

這篇部落格文章是由我們自己人,總是對所有 Spring 事物感到興奮的 Josh Long 共同撰寫。

您已決定發揮您的才能為人類服務,並且在這個疫情時代,除了軟體之外沒有其他真正的技能可言,您將建立一個網站服務,讓人們可以在您的新網站 www.ps5ownersarebetterpeople.com.net 上查詢備受讚譽的 Playstation 5 遊戲機的供貨情況。

一切美好的開始...

前往可靠的 Spring Initializr 並產生一個新的專案(名為 service),使用最新版本的 Java(當然!),並將 Reactive WebWavefrontLombokSleuthActuator 依賴項新增至專案。點擊 Generate 按鈕下載一個 .zip 檔案,其中包含您應該在您最喜歡的 IDE 中開啟的專案程式碼。

將以下配置值新增至 application.properties。我們將在它們變得相關時回顧它們。目前,要記住的關鍵是我們在埠 8083 上執行 service,並且我們使用 spring.application.name 屬性 service 為此服務命名。

spring.application.name=service
server.port=8083
wavefront.application.name=console-availability
management.metrics.export.wavefront.source=my-cloud-server

這是 Java 程式碼

@Slf4j
@SpringBootApplication
public class ServiceApplication {

    public static void main(String[] args) {
        log.info("starting server");
        SpringApplication.run(ServiceApplication.class, args);
    }
}

@RestController
class AvailabilityController {

    private boolean validate(String console) {
        return StringUtils.hasText(console) &&
               Set.of("ps5", "ps4", "switch", "xbox").contains(console);
    }

    @GetMapping("/availability/{console}")
    Map<String, Object> getAvailability(@PathVariable String console) {
        return Map.of("console", console,
                "available", checkAvailability(console));
    }

    private boolean checkAvailability(String console) {
        Assert.state(validate(console), () -> "the console specified, " + console + ", is not valid.");
        return switch (console) {
            case "ps5" -> throw new RuntimeException("Service exception");
            case "xbox" -> true;
            default -> false;
        };
    }
}

對於特定類型遊戲機(ps5nintendoxboxps4)的請求,API 會傳回遊戲機的供貨情況(據推測來自當地電子產品零售店)。但由於某些原因,為了我們的演示,這將不得不成為機械降神——PlayStation 5 完全沒有供貨。更糟的是,該服務本身正在發生錯誤,並且每次有人膽敢詢問 Playstation 5 時都會崩潰!我們將使用這個特定的程式碼路徑——特別是詢問 Playstation 5 的供貨情況——來模擬我們系統中的錯誤。別評判。您可能也在某個時候犯過錯誤。可能吧。

我們希望盡可能多地了解個別微服務及其互動,並且當我們試圖找出系統中的錯誤時,我們最需要這些資訊。讓我們看看追蹤和指標如何協同工作,以提供優於單獨使用指標或追蹤的可觀察性態勢。

我們需要一個客戶端來與服務對話並驅動一些流量到該服務。返回 Spring Initializr 並產生另一個與 service 完全相同的專案,但將此專案的 spring.application.name 值設定為 client

這是 配置檔案

spring.application.name=client
wavefront.application.name=console-availability
management.metrics.export.wavefront.source=my-cloud-server

該程式碼使用反應式、非阻塞的 WebClient 向服務發出請求。整個應用程式——clientservice——都使用反應式、非阻塞的 HTTP。您也可以輕鬆地使用基於傳統 Servlet 的 Spring MVC。或者您可以完全避免使用 HTTP,而改用訊息傳遞技術。或者您可以兩者都使用。這是 Java 程式碼

@Slf4j
@SpringBootApplication
public class ClientApplication {

    public static void main(String[] args) {
        log.info("starting client");
        SpringApplication.run(ClientApplication.class, args);
    }

    @Bean
    WebClient webClient(WebClient.Builder builder) {
        return builder.build();
    }

    @Bean
    ApplicationListener<ApplicationReadyEvent> ready(AvailabilityClient client) {
        return applicationReadyEvent -> {
            for (var console : "ps5,xbox,ps4,switch".split(",")) {
                Flux.range(0, 20).delayElements(Duration.ofMillis(100)).subscribe(i ->
                        client
                                .checkAvailability(console)
                                .subscribe(availability ->
                                        log.info("console: {}, availability: {} ", console, availability.isAvailable())));
            }
        };
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Availability {
    private boolean available;
    private String console;
}

@Component
@RequiredArgsConstructor
class AvailabilityClient {

    private final WebClient webClient;
    private static final String URI = "https://127.0.0.1:8083/availability/{console}";

    Mono<Availability> checkAvailability(String console) {
        return this.webClient
                .get()
                .uri(URI, console)
                .retrieve()
                .bodyToMono(Availability.class)
                .onErrorReturn(new Availability(false, console));
    }

}

啟動 service 應用程式,然後啟動 client 應用程式。client 應用程式產生大量針對服務的需求,其中一些需求導致請求失敗。我們想要擷取所有這些資訊。

換湯不換藥

首先,我們希望有一個所有資料的彙總視圖,指標,它為我們提供有關所有請求的統計資訊。指標是數字,彙總。指標可以涵蓋諸如記憶體/執行緒使用率、垃圾收集、程序指標等內容。它們通常還包含業務可能設定的關鍵績效指標,例如履行了多少訂單、驗證了多少使用者等等。

Actuator 啟動器反過來引入了 Micrometer,它為最流行的監控系統的檢測客戶端提供了一個簡單的外觀模式,讓您可以檢測基於 JVM 的應用程式碼,而無需供應商鎖定。可以將其視為 SLF4J,但用於指標。

Micrometer 最直接的用法是擷取指標並將它們保存在記憶體中,這也是 Spring Boot Actuator 將要執行的操作。您可以配置您的應用程式以在 Actuator 管理端點 /actuator/metrics/ 下顯示這些指標。但是,更常見的是,您會希望將這些指標發送到時間序列資料庫,例如 Graphite、Prometheus、Netflix Atlas、Datadog 或 InfluxDB。時間序列資料庫儲存指標隨時間演變的值,因此您可以看到它是如何變化的。

追蹤資料

危險是真正偵探的零食。 -Mac Barnett

我們也希望詳細分解個別請求和追蹤,以便為我們提供有關特定失敗請求的上下文。Sleuth 啟動器引入了 Spring Cloud Sleuth 分散式追蹤抽象層,它為分散式追蹤系統(例如 OpenZipkin 和 Google Cloud Stackdriver Trace 以及 Wavefront)提供了一個簡單的外觀模式。

Micrometer 和 Sleuth 為您提供了在指標和追蹤後端中進行選擇的能力。我們可以使用這兩個不同的抽象層,並單獨建立一個專用於我們的追蹤和指標彙總系統的叢集。有人這樣做。更瘋狂的事情也發生過。我們都認為您不應該運行您無法收費的東西,因此讓我們使用一個簡單、交鑰匙、託管的軟體即服務 (SaaS) 產品,讓其他人來完成這項工作。我們並不羨慕在兩個不同的、不相關的後端系統中擁有如此高度相關的資料所暗示的整合任務。

前往可觀察性洞穴,Statsman!

我們將使用 VMware Tanzu 的 出色的 Wavefront 可觀察性平台,它了解指標和追蹤,並且可以將它們連結在一起。我們已經將 Wavefront 啟動器新增到我們的建置中。

啟動 service,然後啟動 clientclient 將產生大量流量。嗯,不是大量。請記住,Reddit 在他們的全球規模上成功地使用了 Wavefront。因此,在所有條件相同的情況下,我們的資料微不足道。但這足以看到一些核心概念的實際應用。當我們的 Spring Boot 應用程式啟動時,會列印出 Wavefront URL。這是存取 freemium Wavefront 叢集的 URL。您已經擁有有效的 Wavefront 配置,甚至不必註冊帳戶!指標發布到 Wavefront 需要一分鐘。等待一分鐘,然後訪問瀏覽器中控制台中列印的 URL。

該 URL 會將您轉儲到 Spring Boot 的 Wavefront 儀表板中。這裡有很多內容,因此我們將特別關注一些關鍵事項。

您可以看到 Wavefront 在螢幕頂部的 Dashboards 選單中完全載入了 Spring Boot 儀表板。儀表板的頂部顯示 Sourcemy-cloud-server,這來自配置屬性 management. .export.wavefront.source(或使用預設值,即機器的主機名稱)。我們感興趣的 Applicationconsole-availability,這來自配置屬性 wavefront.application.name應用程式指的是 Spring Boot 微服務的邏輯群組,而不是任何特定的微服務。

點擊它,您將一目了然地看到有關您的應用程式的所有資訊。您可以選擇查看有關 clientservice 模組的資訊。點擊 Jump To 以導航到一組特定的圖表。我們對 HTTP 部分中的資料感興趣。

您可以看到有用的資訊,例如 Top RequestsTop Failed Requests 和程式碼中遇到的 Top Exceptions——將滑鼠懸停在特定類型的請求上以取得與每個條目相關的一些詳細資訊。您可以取得與失敗請求相關的資訊,例如 HTTP 方法 (GET)、服務 (service)、狀態碼 (500) 和 URI (/availability/{console})。

這些一目了然的數字是指標。指標不是基於抽樣資料;它們是每個請求的彙總。您應該將指標用於警報,因為它們確保您看到所有請求(以及所有錯誤、慢速請求等)。另一方面,追蹤資料通常需要在高流量時進行抽樣,因為資料量與流量成比例增加。

我們可以發現在區分請求時,指標集合忽略了 {console} 路徑變數的值,這意味著——就我們的資料而言——只有一個 URI (/availability/{console})。這是設計使然。{console} 是我們用來指定遊戲機的路徑變數,但它也可能很容易成為使用者 ID、訂單 ID 或其他可能存在許多(可能無界)值的東西。指標系統預設記錄高基數指標是很危險的。有界基數指標很便宜!成本不會隨著流量增加而增加。注意指標中的基數。

這有點遺憾,因為即使我們知道 {console} 是一個低基數變數——可能值的集合是有限的——我們也無法進一步深入研究資料,以便一目了然地看到哪些路徑失敗。指標表示彙總統計資訊,因此即使我們根據 {console} 變數分解指標,指標仍然缺乏圍繞個別請求的上下文。

總是有追蹤資料!點擊 Top Failed Requests 字樣右側的小麵包屑/三明治圖示,然後透過前往 Traces > console-availability 找到服務。

這是為應用程式收集的所有追蹤:好的、壞的或其他。

讓我們透過新增 Error 篩選器到搜尋中,僅深入研究錯誤請求。然後點擊 Search。現在我們可以仔細檢查個別錯誤請求。您可以看到每個服務呼叫花費了多長時間、服務之間的關係以及錯誤的來源。

點擊螢幕右下角標記為 client: GET 的面板的 Expand 圖示。您可以看到請求旅程中的每個躍點:花費的時間、追蹤 ID、URL 和路徑。

展開追蹤的特定區段下的 Tags 分支,您可以看到 Spring Cloud Sleuth 自動為您收集的中繼資料。追蹤由稱為跨度的個別區段組成,這些區段描述了請求旅程中的一個躍點。

使用業務/網域上下文豐富資料

我們從預設配置中獲得了很多。除了新增 Spring Boot Actuator 啟動器、Wavefront 啟動器和 Sleuth 啟動器並啟動應用程式之外,我們實際上沒有對程式碼做任何事情來獲得我們剛才看到的結果。看到了嗎?這很容易!非常容易。就像從一個日誌中心系統轉到一個真正的可觀察性平台一樣容易。我們獲得了追蹤資訊和指標,以及一個我們可以查閱詳細資訊的儀表板。我們對我們的 Java 程式碼進行了完全零次的更改,以支援所有這些。

讓我們更進一步,自訂 Spring Cloud Sleuth 和 Micrometer 擷取的中繼資料,以更輕鬆地依網域特定的概念(即請求的遊戲機類型)向下鑽研。我們可以透過 {console} 路徑變數來做到這一點。程式碼已經驗證遊戲機的值是否在眾所周知的遊戲機集合內。重要的是,我們在使用輸入之前驗證輸入,這確保了遊戲機的類型是低基數的。您不應將可能具有高基數的任意輸入(例如路徑變數或查詢參數)用作指標標籤——儘管您可以將高基數資料用作追蹤標籤。現在,我們可以改為使用指標和追蹤上的標籤,而不是從追蹤資料中的 HTTP 路徑中收集遊戲機的類型。

我們將更新服務以注入 SpanCustomizer 以自訂追蹤資訊。我們還將更新服務以配置 WebFluxTagsContributor 以自訂 Spring Boot 擷取並提供給 Micrometer 的標籤。這是 新的和更新的程式碼

@Slf4j
@SpringBootApplication
public class ServiceApplication {

    @Bean
    WebFluxTagsContributor consoleTagContributor() {
        return (exchange, ex) -> {
            var console = "UNKNOWN";
            var consolePathVariable = ((Map<String,String>) exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)).get("console");
            if (AvailabilityController.validateConsole(consolePathVariable)) {
                console = consolePathVariable;
            }
            return Tags.of("console", console);
        };
    }

    public static void main(String[] args) {
        log.info("starting server");
        SpringApplication.run(ServiceApplication.class, args);
    }
}

@RestController
@AllArgsConstructor
class AvailabilityController {

    private final SpanCustomizer spanCustomizer;

    @GetMapping("/availability/{console}")
    Map<String, Object> getAvailability(@PathVariable String console) {
        Assert.state(validateConsole(console), () -> "the console specified, " + console + ", is not valid.");
        this.spanCustomizer.tag("console", console);
        return Map.of("console", console, "available", checkAvailability(console));
    }

    private boolean checkAvailability(String console) {
        return switch (console) {
            case "ps5" -> throw new RuntimeException("Service exception");
            case "xbox" -> true;
            default -> false;
        };
    }

    static boolean validateConsole(String console) {
        return StringUtils.hasText(console) &&
               Set.of("ps5", "ps4", "switch", "xbox").contains(console);
    }

}

使用上述變更重新執行服務,然後重新執行客戶端(與之前相同),並等待一分鐘以發布指標。然後再次開啟 Wavefront 控制台;使用控制台輸出中列印的方便連結!

您現在可以看到按遊戲機類型分類的不同指標。點擊 Dashboards > Spring Boot Dashboard,您會注意到 Top RequestsTop Failed Requests 有更多條目。這次,您可以根據每個遊戲機類型分解結果。將滑鼠懸停在它們上方,您將看到詳細資訊。

這是成功的請求。

這是失敗的請求。

在我們看來,ps5 遊戲機與失敗的請求高度相關。讓我們看看追蹤資訊。點擊 Applications > Traces,以查看更新的資料。

點擊關鍵路徑分解並展開面板。點擊特定區段,如此處所示,並展開 Tags 分支。您將看到與特定請求關聯的所有標籤,包括 console 標籤。描繪的失敗請求是在有人請求 ps5 遊戲機的供貨情況之後發生的。如果可以根據遊戲機類型進行篩選,那不是很好嗎?點擊標籤 console 旁邊的 + 圖示,Wavefront 會將其新增到搜尋條件中。點擊 Search 以查看所有錯誤的追蹤並找到罪魁禍首。

我們的資料根據遊戲機類型(我們的網域特定概念)分解了追蹤和指標。

指標與追蹤就像炸雞與醃汁一樣相得益彰

什麼?您從未嘗試過炸雞和醃汁?很好吃。它真的很好吃。您可以想像,一旦您標準化了 Spring 和 Wavefront,並且不必自己維護那麼多無差異化的基礎架構,您將擁有多少更多的空閒時間?那將會很棒。您將有很多時間。您將有足夠的時間來嘗試炸雞和醃汁。

您已經看到了一個將指標和追蹤結合使用的具體範例。讓我們回顧一下指標和追蹤的一些用途和反模式。這有望清楚地說明為什麼您需要指標和追蹤,以及如何針對不同的目的使用它們。考慮 Peter Bourgon 的指標、追蹤和日誌記錄 部落格文章中建立的框架可能會很有用。

追蹤和指標在提供有關我們服務中請求範圍互動的洞察力方面有所重疊。但是,指標和追蹤提供的一些資訊是不相交的。追蹤擅長顯示服務之間的關係以及有關特定請求的高基數資料,例如與請求關聯的使用者 ID。分散式追蹤可協助您快速查明分散式系統中問題的來源。權衡取捨是,在高流量和嚴格的效能要求下,需要對追蹤進行抽樣以控制成本。這意味著您感興趣的特定請求可能不在抽樣追蹤資料中。

另一方面,指標彙總所有測量值,並在時間間隔匯出彙總值以定義時間序列資料。所有資料都包含在此彙總中,並且只要遵循有關標籤基數的最佳實務,成本就不會隨著流量增加而增加。因此,測量某事物最大延遲的指標將包含最慢的請求,並且錯誤率的計算將是準確的,而與追蹤資料上的任何抽樣無關。

指標可以用於請求範圍之外,以監控記憶體、CPU 使用率、垃圾收集和快取等等。您將希望將指標用於您的警報、SLO(服務等級目標)和儀表板。在 console-availability 範例中,它將是關於 SLO 違規的警報,該警報通知我們有關我們服務的高錯誤率。(您不想 24/7 盯著儀表板來檢測問題,對嗎?)

然後,透過指標和追蹤,我們可以透過每個指標和追蹤中可用的通用中繼資料從一個跳到另一個。指標和追蹤資訊都支援使用稱為標籤的資料擷取任意鍵值對。例如,給定有關基於 HTTP 的服務的高延遲的警報通知(基於指標),您可以連結到符合警報的跨度(追蹤資料)的搜尋。您將搜尋具有相同服務、HTTP 方法、HTTP URI 且持續時間高於閾值的跨度,以快速取得符合警報的追蹤樣本。

總之,資料勝過沒有資料,整合資料勝過非整合資料。Micrometer 和 Spring Cloud Sleuth 提供了可靠的開箱即用可觀察性態勢,但可以配置和調整以適應您的業務/網域的上下文。最後,雖然您可以將 Micrometer 或 Spring Cloud Sleuth 與許多其他後端一起使用,但我們發現 Wavefront 是一個方便且功能強大的選項。範例中顯示的程式碼可從 此 GitHub 儲存庫 取得。

取得 Spring 電子報

與 Spring 電子報保持聯繫

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉辦的活動

查看 Spring 社群中所有即將舉辦的活動。

查看全部