指標和追蹤:相輔相成

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

這篇博文是由我們自己的、對 Spring 的一切都充滿熱情的 Josh Long 共同撰寫的。

你決定將你的才能奉獻給人類——在這個大流行時代,除了軟體之外別無其他技能可言——你將要構建一個網路服務,人們可以在你的新網站 www.ps5ownersarebetterpeople.com.net 上查詢備受推崇的 Playstation 5 影片遊戲機的供貨情況。

一切都開始得如此吉利...

前往值得信賴的Spring Initializr,使用最新版本的 Java( 自然! )生成一個新專案(名為service),並將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 返回遊戲機的可用性(大概來自當地電子產品商店)。除了出於某種原因,對於我們的演示來說,這將不得不是 deus ex machina ——沒有 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://: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 starter 反過來又引入了Micrometer,它為最流行的監控系統的儀表客戶端提供了簡單的外觀,允許您對基於 JVM 的應用程式程式碼進行儀表化,而無需供應商鎖定。想想 SLF4J,但用於指標。

Micrometer 最簡單的用法是捕獲指標並將其儲存在記憶體中,Spring Boot Actuator 會這樣做。您可以配置您的應用程式以在 Actuator 管理端點 - /actuator/metrics/ 下顯示這些指標。然而,更常見的情況是,您希望將這些指標傳送到時間序列資料庫,例如 Graphite、Prometheus、Netflix Atlas、Datadog 或 InfluxDB。時間序列資料庫儲存指標隨時間演變的值,因此您可以檢視其變化情況。

跟蹤資料

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

我們還希望對單個請求和跟蹤進行詳細的細分,以提供有關特定失敗請求的上下文。Sleuth starter 引入了Spring Cloud Sleuth分散式跟蹤抽象,它為 OpenZipkin、Google Cloud Stackdriver Trace 和 Wavefront 等分散式跟蹤系統提供了簡單的外觀。

Micrometer 和 Sleuth 讓您在指標和跟蹤後端方面擁有選擇權。我們 可以 使用這兩種不同的抽象,並分別為我們的跟蹤和指標聚合系統建立一個專門的叢集。人們確實這樣做了。更瘋狂的事情也發生過。我們堅信您不應該執行無法收費的東西,所以讓我們使用一個簡單、開箱即用、託管的軟體即服務 (SaaS) 產品,讓其他人來完成這項工作。我們不羨慕擁有如此高度相關的資料生活在兩個不同、不相關的後端系統所帶來的整合任務。

去可觀察性洞穴,統計員!

我們將使用 VMware Tanzu 的優秀的 Wavefront 可觀察性平臺,它瞭解指標和跟蹤,並可以將它們連結在一起。我們已經將Wavefront starter 新增到我們的構建中。

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

該 URL 會將您帶到 Spring Boot 的 Wavefront 儀表板。這裡有很多內容,我們將重點關注幾個關鍵點。

您可以看到 Wavefront 在螢幕頂部的Dashboards選單中預裝了Spring Boot儀表板。儀表板頂部顯示Sourcemy-cloud-server,這來自配置屬性management. .export.wavefront.source(或使用預設值,即機器的主機名)。我們感興趣的Applicationconsole-availability,這來自配置屬性wavefront.application.nameApplication 指的是 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 自動為您收集的元資料。跟蹤由稱為 span 的單個段組成,這些段描述了請求旅程中的一次跳躍。

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

我們從預設配置中獲得了大量資訊。除了新增 Spring Boot Actuator starter、Wavefront starter 和Sleuth starter 並啟動應用程式之外,我們實際上沒有對程式碼做任何事情來獲得剛剛看到的結果。看到了嗎?這很簡單!非常簡單。就像從一個以日誌為中心的系統掉到真正的可觀察性平臺一樣容易。我們獲得了跟蹤資訊、指標和可以查閱詳細資訊的儀表板。我們對 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 的服務高延遲的警報通知(基於指標),您可以連結到與該警報匹配的 span 搜尋(跟蹤資料)。您將搜尋具有相同服務、HTTP 方法、HTTP URI 且持續時間超過閾值的 span,以快速獲取與該警報匹配的跟蹤樣本。

總而言之,有資料總比沒有資料好,整合資料總比非整合資料好。Micrometer 和 Spring Cloud Sleuth 開箱即用,提供了堅實的可觀測性基礎,但可以根據您的業務/領域上下文進行配置和調整。最後,雖然您 可以 將 Micrometer 或 Spring Cloud Sleuth 與其他任意數量的後端一起使用,但我們發現 Wavefront 是一個方便而強大的選擇。示例中顯示的程式碼可從此 GitHub 儲存庫獲取。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有