javascript
SpringBoot - 构建监控体系02_定义度量指标和 Actuator 端点
文章目錄
- Pre
- Actuator 中的度量指標(biāo)
- Micrometer 度量庫
- Meter接口
- 計(jì)量器類型
- 如何創(chuàng)建這些計(jì)量器
- 擴(kuò)展 Metrics 端點(diǎn)
- 自定義 Metrics 指標(biāo)
- 使用 MeterRegistry
- 自定義 Actuator 端點(diǎn)
- 小結(jié)
Pre
SpringBoot - 構(gòu)建監(jiān)控體系01_使用 Actuator 組件實(shí)現(xiàn)及擴(kuò)展系統(tǒng)監(jiān)控 我們引入了 Spring Boot Actuator 組件來滿足 Spring Boot 應(yīng)用程序的系統(tǒng)監(jiān)控功能,并重點(diǎn)介紹了如何擴(kuò)展常見的 Info 和 Health 監(jiān)控端點(diǎn)的實(shí)現(xiàn)方法。
這一講我們繼續(xù)討論如何擴(kuò)展 Actuator 端點(diǎn),但更多關(guān)注與度量指標(biāo)相關(guān)的內(nèi)容。同時(shí),我們還將給出如何創(chuàng)建自定義 Actuator 的實(shí)現(xiàn)方法,以便應(yīng)對(duì)默認(rèn)端點(diǎn)無法滿足需求的應(yīng)用場景。
Actuator 中的度量指標(biāo)
對(duì)于系統(tǒng)監(jiān)控而言,度量是一個(gè)很重要的維度。
在 Spring Boot 2.X 版本中,Actuator 組件主要使用內(nèi)置的 Micrometer 庫實(shí)現(xiàn)度量指標(biāo)的收集和分析。
Micrometer 度量庫
Micrometer 是一款監(jiān)控指標(biāo)的度量類庫,為 Java 平臺(tái)上的性能數(shù)據(jù)收集提供了一套通用的 API。在應(yīng)用程序中,我們只使用 Micrometer 提供的通用 API 即可收集度量指標(biāo)。
下面我們先來簡要介紹 Micrometer 中包含的幾個(gè)核心概念。
Meter接口
首先我們需要介紹的是計(jì)量器 Meter,它是一個(gè)接口,代表的是需要收集的性能指標(biāo)數(shù)據(jù)。關(guān)于 Meter 的定義如下:
public interface Meter extends AutoCloseable {//Meter 的唯一標(biāo)識(shí),是名稱和標(biāo)簽的一種組合Id getId();//一組測量結(jié)果Iterable<Measurement> measure();//Meter 的類型枚舉值enum Type {COUNTER,GAUGE,LONG_TASK_TIMER,TIMER,DISTRIBUTION_SUMMARY,OTHER} }通過上述代碼,我們注意到 Meter 中存在一個(gè) Id 對(duì)象,該對(duì)象的作用是定義 Meter 的名稱和標(biāo)簽。從 Type 的枚舉值中,我們不難看出 Micrometer 中包含的所有計(jì)量器類型。
接下來我們先說明兩個(gè)概念。
- Meter 的名稱:對(duì)于計(jì)量器來說,每個(gè)計(jì)量器都有自己的名稱,而且在創(chuàng)建時(shí)它們都可以指定一系列標(biāo)簽。
- Meter 的標(biāo)簽:標(biāo)簽的作用在于監(jiān)控系統(tǒng)可以通過這些標(biāo)簽對(duì)度量進(jìn)行分類過濾。
計(jì)量器類型
在日常開發(fā)過程中,常用的計(jì)量器類型主要分為計(jì)數(shù)器 Counter、計(jì)量儀 Gauge 和計(jì)時(shí)器 Timer 這三種。
-
Counter:這個(gè)計(jì)量器的作用和它的名稱一樣,就是一個(gè)不斷遞增的累加器,我們可以通過它的 increment 方法實(shí)現(xiàn)累加邏輯。
-
Gauge:與 Counter 不同,Gauge 所度量的值并不一定是累加的,我們可以通過它的 gauge 方法指定數(shù)值。
-
Timer:這個(gè)計(jì)量器比較簡單,就是用來記錄事件的持續(xù)時(shí)間。
如何創(chuàng)建這些計(jì)量器
既然我們已經(jīng)明確了常用的計(jì)量器及其使用場景,那么如何創(chuàng)建這些計(jì)量器呢?
在 Micrometer 中,我們提供了一個(gè)計(jì)量器注冊表 MeterRegistry,它主要負(fù)責(zé)創(chuàng)建和維護(hù)各種計(jì)量器。關(guān)于 MeterRegistry 的創(chuàng)建方法如下代碼所示:
public abstract class MeterRegistry implements AutoCloseable {protected abstract <T> Gauge newGauge(Meter.Id id, @Nullable T obj, ToDoubleFunction<T> valueFunction);protected abstract Counter newCounter(Meter.Id id);protected abstract Timer newTimer(Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, PauseDetector pauseDetector);…}以上代碼只是創(chuàng)建 Meter 的一種途徑,從中我們可以看到 MeterRegistry 針對(duì)不同的 Meter 提供了對(duì)應(yīng)的創(chuàng)建方法。
而創(chuàng)建 Meter 的另一種途徑是使用某個(gè) Meter 的具體 builder 方法。以 Counter 為例,它的定義中包含了一個(gè) builder 方法和一個(gè) register 方法,如下代碼所示:
public interface Counter extends Meter {static Builder builder(String name) {return new Builder(name);}default void increment() {increment(1.0);}void increment(double amount);double count();@Overridedefault Iterable<Measurement> measure() {return Collections.singletonList(new Measurement(this::count, Statistic.COUNT));}…public Counter register(MeterRegistry registry) {return registry.counter(new Meter.Id(name, tags, baseUnit, description, Type.COUNTER));}}注意到最后的 register 方法就是將當(dāng)前的 Counter 注冊到 MeterRegistry 中,因此我們需要?jiǎng)?chuàng)建一個(gè) Counter。通常,我們會(huì)采用如下所示代碼進(jìn)行創(chuàng)建。
Counter counter = Counter.builder("counter1").tag("tag1", "value1").register(registry);擴(kuò)展 Metrics 端點(diǎn)
了解了 Micrometer 框架的基本概念后,接下來我們回到 Spring Boot Actuator,一起來看看它提供的專門針對(duì)度量指標(biāo)管理的 Metrics 端點(diǎn)。
在 Spring Boot 中,它為我們提供了一個(gè) Metrics 端點(diǎn)用于實(shí)現(xiàn)生產(chǎn)級(jí)的度量工具。訪問 actuator/metrics 端點(diǎn)后,我們將得到如下所示的一系列度量指標(biāo)。
以上代碼中涉及的指標(biāo)包括常規(guī)的系統(tǒng)內(nèi)存總量、空閑內(nèi)存數(shù)量、處理器數(shù)量、系統(tǒng)正常運(yùn)行時(shí)間、堆信息等,如果引用了數(shù)據(jù)庫,也也包含我們引入 JDBC 和 HikariCP 數(shù)據(jù)源組件之后的數(shù)據(jù)庫連接信息等。
此時(shí),如果我們想了解某項(xiàng)指標(biāo)的詳細(xì)信息,在 actuator/metrics 端點(diǎn)后添加對(duì)應(yīng)指標(biāo)的名稱即可。
例如我們想了解當(dāng)前內(nèi)存的使用情況,就可以通過 actuator/metrics/jvm.memory.used 端點(diǎn)進(jìn)行獲取,如下代碼所示。
自定義 Metrics 指標(biāo)
前面介紹 Micrometer 時(shí),我們已經(jīng)提到 Metrics 指標(biāo)體系中包含支持 Counter 和 Gauge 這兩種級(jí)別的度量指標(biāo)。
通過將 Counter 或 Gauge 注入業(yè)務(wù)代碼中,我們就可以記錄自己想要的度量指標(biāo)。其中,Counter 用來暴露 increment() 方法,而 Gauge 用來提供一個(gè) value() 方法。
下面我們以 Counter 為例介紹在業(yè)務(wù)代碼中嵌入自定義 Metrics 指標(biāo)的方法,如下代碼所示:
package com.artisan.admin;import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.springframework.stereotype.Component;/*** @author 小工匠* @version 1.0* @description: * @date 2021/5/30 19:16* @mark: show me the code , change the world*/ @Component public class CounterService {public CounterService() {Metrics.addRegistry(new SimpleMeterRegistry());}public void counter(String name, String... tags) {Counter counter = Metrics.counter(name, tags);counter.increment();} }在這段代碼中,我們構(gòu)建了一個(gè)公共服務(wù) CounterService,并開放了一個(gè) Counter 方法供業(yè)務(wù)系統(tǒng)進(jìn)行使用。當(dāng)然,你也可以自己實(shí)現(xiàn)類似的工具類完成對(duì)各種計(jì)量器的封裝。
另外,Micrometer 還提供了一個(gè) MeterRegistry 工具類供我們創(chuàng)建度量指標(biāo)。因此,我們也十分推薦使用 MeterRegistry 對(duì)各種自定義度量指標(biāo)的創(chuàng)建過程進(jìn)行簡化。
使用 MeterRegistry
比如我們希望系統(tǒng)每創(chuàng)建一個(gè)客服工單,就對(duì)所創(chuàng)建的工單進(jìn)行計(jì)數(shù),并作為系統(tǒng)運(yùn)行時(shí)的一項(xiàng)度量指標(biāo),該效果的實(shí)現(xiàn)方式如下代碼所示:
@Service public class CustomerTicketService {@Autowiredprivate MeterRegistry meterRegistry;public CustomerTicket generateCustomerTicket(Long accountId, String orderNumber) {CustomerTicket customerTicket = new CustomerTicket();…meterRegistry.summary("customerTickets.generated.count").record(1);return customerTicket;} }在上述 generateCustomerTicket 方法中,通過 MeterRegistry 我們實(shí)現(xiàn)了每次創(chuàng)建 CustomerTicket 時(shí)自動(dòng)添加一個(gè)計(jì)數(shù)的功能。
而且,MeterRegistry 還提供了一些類工具方法用于創(chuàng)建自定義度量指標(biāo)。這些類工具方法除了常規(guī)的 counter、gauge、timer 等對(duì)應(yīng)具體 Meter 的工具方法之外,還包括上述代碼中的 summary 方法,且 Summary 方法返回的是一個(gè) DistributionSummary 對(duì)象,關(guān)于它的定義如下代碼所示:
public interface DistributionSummary extends Meter, HistogramSupport {static Builder builder(String name) {return new Builder(name);}//記錄數(shù)據(jù)void record(double amount);//記錄操作執(zhí)行的次數(shù)long count();//記錄數(shù)據(jù)的數(shù)量double totalAmount();//記錄數(shù)據(jù)的平均值default double mean() {return count() == 0 ? 0 : totalAmount() / count();}//記錄數(shù)據(jù)的最大值double max();…}因?yàn)?DistributionSummary 的作用是記錄一系列的事件并對(duì)這些事件進(jìn)行處理,所以在 CustomerTicketService 中添加的meterRegistry.summary("customertickets.generated.count").record(1) 這行代碼相當(dāng)于每次調(diào)用 generateCustomerTicket 方法時(shí),我們都會(huì)對(duì)這次調(diào)用進(jìn)行記錄。
現(xiàn)在訪問 actuator/metrics/customertickets.generated.count 端點(diǎn),我們就能看到如下所示的隨著服務(wù)調(diào)用不斷遞增的度量信息。
{"name":"customertickets.generated.count","measurements":[{"statistic":"Count","value":1},{"statistic":"Total","value":19}] }顯然,通過 MeterRegistry 實(shí)現(xiàn)自定義度量指標(biāo)的使用方法更加簡單。這里,你也可以結(jié)合業(yè)務(wù)需求嘗試該類的不同功能。
接下來我們再來看一個(gè)相對(duì)比較復(fù)雜的使用方式。在 customer-service 中,我們同樣希望系統(tǒng)存在一個(gè)度量值,該度量值用于記錄所有新增的 CustomerTicket 個(gè)數(shù),這次的示例代碼如下所示:
@Componentpublic class CustomerTicketMetrics extends AbstractRepositoryEventListener<CustomerTicket> {private MeterRegistry meterRegistry;public CustomerTicketMetrics(MeterRegistry meterRegistry) {this.meterRegistry = meterRegistry;}@Overrideprotected void onAfterCreate(CustomerTicket customerTicket) { meterRegistry.counter("customerTicket.created.count").increment(); }}首先,這里我們使用了 MeterRegistry 的 Counter 方法初始化一個(gè) counter,然后調(diào)用它的 increment 方法增加度量計(jì)數(shù)(這部分內(nèi)容我們已經(jīng)很熟悉了)。
注意到這里,我們同時(shí)還引入了一個(gè) AbstractRepositoryEventListener 抽象類,這個(gè)抽象類能夠監(jiān)控 Spring Data 中 Repository 層操作所觸發(fā)的事件 RepositoryEvent,例如實(shí)體創(chuàng)建前后的 BeforeCreateEvent 和 AfterCreateEvent 事件、實(shí)體保存前后的 BeforeSaveEvent 和 AfterSaveEvent 事件等。
針對(duì)這些事件,AbstractRepositoryEventListener 能捕捉并調(diào)用對(duì)應(yīng)的回調(diào)函數(shù)。關(guān)于 AbstractRepositoryEventListener 類的部分實(shí)現(xiàn)如下代碼所示:
public abstract class AbstractRepositoryEventListener<T> implements ApplicationListener<RepositoryEvent> {public final void onApplicationEvent(RepositoryEvent event) {…Class<?> srcType = event.getSource().getClass();if (event instanceof BeforeSaveEvent) {onBeforeSave((T) event.getSource());} else if (event instanceof BeforeCreateEvent) {onBeforeCreate((T) event.getSource());} else if (event instanceof AfterCreateEvent) {onAfterCreate((T) event.getSource());} else if (event instanceof AfterSaveEvent) {onAfterSave((T) event.getSource());}…} }在這段代碼中,我們可以看到 AbstractRepositoryEventListener 直接實(shí)現(xiàn)了 Spring 容器中的 ApplicationListener 監(jiān)聽器接口,并在 onApplicationEvent 方法中根據(jù)所傳入的事件類型觸發(fā)了回調(diào)函數(shù)。
以案例中的需求場景為例,我們可以在創(chuàng)建 Account 實(shí)體之后執(zhí)行度量操作。也就是說,可以把度量操作的代碼放在 onAfterCreate 回調(diào)函數(shù)中,正如案例代碼中所展示那樣。
現(xiàn)在我們執(zhí)行生成客戶工單操作,并訪問對(duì)應(yīng)的 Actuator 端點(diǎn),同樣可以看到度量數(shù)據(jù)在不斷上升。
自定義 Actuator 端點(diǎn)
在日常開發(fā)過程中,擴(kuò)展現(xiàn)有端點(diǎn)有時(shí)并不一定能滿足業(yè)務(wù)需求,而自定義 Spring Boot Actuator 監(jiān)控端點(diǎn)算是一種更靈活的方法。
假設(shè)我們需要提供一個(gè)監(jiān)控端點(diǎn)以獲取當(dāng)前系統(tǒng)的用戶信息和計(jì)算機(jī)名稱,就可以通過一個(gè)獨(dú)立的 MySystemEndPoint 進(jìn)行實(shí)現(xiàn),如下代碼所示
@Configuration @Endpoint(id = "mysystem", enableByDefault=true) public class MySystemEndpoint { @ReadOperationpublic Map<String, Object> getMySystemInfo() {Map<String,Object> result= new HashMap<>();Map<String, String> map = System.getenv();result.put("username",map.get("USERNAME"));result.put("computername",map.get("COMPUTERNAME"));return result;} }在這段代碼中我們可以看到,MySystemEndpoint 主要通過系統(tǒng)環(huán)境變量獲取所需監(jiān)控信息。
注意,這里我們引入了一個(gè)新的注解 @Endpoint,該注解定義如下代碼所示:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Endpoint {//端點(diǎn) idString id() default "";//是否默認(rèn)啟動(dòng)標(biāo)志位boolean enableByDefault() default true;}這段代碼中的 @Endpoint 注解主要用于設(shè)置端點(diǎn) id 及是否默認(rèn)啟動(dòng)的標(biāo)志位。且在案例中,我們指定了 id 為“mysystem”,enableByDefault 標(biāo)志為 true。
事實(shí)上,在 Actuator 中也存在一批類似 @Endpoint 的端點(diǎn)注解。其中被 @Endpoint 注解的端點(diǎn)可以通過 JMX 和 Web 訪問應(yīng)用程序,對(duì)應(yīng)的被 @JmxEndpoint 注解的端點(diǎn)只能通過 JMX 訪問,而被 @WebEndpoint 注解的端點(diǎn)只能通過 Web 訪問。
在示例代碼中,我們還看到了一個(gè) @ReadOperation 注解,該注解作用于方法,用于標(biāo)識(shí)讀取數(shù)據(jù)操作
在 Actuator 中,除了提供 @ReadOperation 注解之外,還提供 @WriteOperation 和 @DeleteOperation 注解,它們分別對(duì)應(yīng)寫入操作和刪除操作。
現(xiàn)在,通過訪問 http://localhost:8080/actuator/mysystem,我們就能獲取如下所示監(jiān)控信息。
有時(shí)為了獲取特定的度量信息,我們需要對(duì)某個(gè)端點(diǎn)傳遞參數(shù),而 Actuator 專門提供了一個(gè) @Selector 注解標(biāo)識(shí)輸入?yún)?shù),示例代碼如下所示:
@Configuration @Endpoint(id = "account", enableByDefault = true) public class AccountEndpoint {@Autowiredprivate AccountRepository accountRepository; @ReadOperationpublic Map<String, Object> getMySystemInfo(@Selector String arg0) {Map<String, Object> result = new HashMap<>();result.put(accountName, accountRepository.findAccountByAccountName(arg0));return result;} }這段代碼的邏輯非常簡單,就是根據(jù)傳入的 accountName 獲取用戶賬戶信息。
這里請(qǐng)注意,通過 @Selector 注解,我們就可以使用http://localhost:8080/actuator/ account/account1 這樣的端口地址觸發(fā)度量操作了。
小結(jié)
度量是我們觀測一個(gè)應(yīng)用程序運(yùn)行時(shí)狀態(tài)的核心手段。我們介紹了 Spring Boot 中新引入的 Micrometer 度量庫,以及該庫中提供的各種度量組件。
同時(shí)還基于 Micrometer 中的核心工具類 MeterRegistry 完成了在業(yè)務(wù)系統(tǒng)中嵌入度量指標(biāo)的實(shí)現(xiàn)過程。最后,我們還簡要介紹了如何自定義一個(gè) Actuator 端點(diǎn)的開發(fā)方法。
總結(jié)
以上是生活随笔為你收集整理的SpringBoot - 构建监控体系02_定义度量指标和 Actuator 端点的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot - 探究Sprin
- 下一篇: SpringBoot - 构建监控体系0