SpringBoot:定製 Actuator

無敵天驕發表於2021-03-25

雖然 Actuator提供了很多執行中 Spring Boot應用程式的內部工作細節,但難免和你的需求有所偏差。也許你並不需要它提供的所有功能,想要關閉一些也說不定。或者,你需要對 Actuator 稍作擴充套件,增加一些自定義的度量資訊,以滿足你對應用程式的需求。

實際上, Actuator有多種定製方式,包括以下五項。

  • 重新命名端點。
  • 啟用和禁用端點。
  • 自定義度量資訊。
  • 建立自定義倉庫來儲存跟蹤資料。
  • 插入自定義的健康指示器。

接下來,我們會了解如何定製 Actuator,以滿足我們的需要。先來看一個最簡單的定製:重新命名 Actuator端點。

一、修改端點 ID


每個  Actuator 端點都有一個ID用來決定端點的路徑,比方說, /beans端點的預設ID就是 beans

如果端點的路徑是由ID決定的,那麼可以透過修改ID來改變端點的路徑。你要做的就是設定一個屬性,屬性名是 endpoints.endpoint-id.id

我們用 /shutdown端點來做個演示,它會響應發往 /shutdownPOST請求。假設你想讓它處理發往 /kill的POST請求,可以透過如下 YAML/shutdown賦予一個新的ID,也就是新的路徑:

endpoints: 
 shutdown:
  id: kill

重新命名端點、修改其路徑的理由很多。最明顯的理由就是,端點的命名要和團隊的術語保持一致。你也可能想重新命名端點,讓那些熟悉預設名稱的人找不到它,藉此增加一些安全感。

遺憾的是,重新命名端點並不能真的起到保護作用,頂多是讓駭客慢點找到它們。現在先讓我們來看看如何禁用某個(或全部)不希望別人訪問的端點。

二、啟用和禁用端點


雖然 Actuator的端點都很有用,但你不一定需要全部這些端點。預設情況下,所有端點(除了 /shutdown)都啟用。這裡就不詳細介紹如何設定 endpoints.shutdown.enabled為true,以此開啟 /shutdown端點。用同樣的方式,你可以禁用其他的端點,將 endpoints. endpoint-id.enabled設定為false。

例如,要禁用 /metrics端點,你要做的就是將 endpoints.metrics.enabled屬性設定為false。在 application.yml裡做如下設定:

endpoints: 
 metrics: 
 enabled: false

如果你只想開啟一兩個端點,那就先禁用全部端點,然後啟用那幾個你要的,這樣更方便。例如,考慮如下 application.yml片段:

endpoints: 
 enabled: false 
 metrics: 
 enabled: true

正如以上片段所示, endpoints.enabled設定為false就能禁用 Actuator的全部端點,然後將 endpoints.metrics.enabled設定為true重新啟用 /metrics端點。

三、新增自定義度量資訊

你可能還想定義自己的度量,用來捕獲應用程式中的特定資訊。

比方說,我們想要知道使用者往閱讀列表裡儲存了多少次圖書,最簡單的方法就是在每次呼叫 ReadingListController的addToReadingList()方法時增加計數器值。計數器很容易實現,但這個不斷變化的總計值如何同 /metrics端點發布的度量資訊一起釋出出來呢?

再假設我們想要獲得最後儲存圖書的時間戳。時間戳可以透過呼叫 System.currentTime-Millis()來獲取,但如何在 /metrics端點裡報告該時間戳呢?

實際上,自動配置允許 Actuator建立 CounterService的例項,並將其註冊為 Spring的應用程式上下文中的 BeanCounterService這個介面裡定義了三個方法,分別用來增加、減少或重置 特定名稱的度量值,程式碼如下:

package org.springframework.boot.actuate.metrics; 
public interface CounterService { 
 void increment(String metricName); 
 void decrement(String metricName); 
 void reset(String metricName); 
}

Actuator的自動配置還會配置一個 GaugeService型別的 Bean。該介面與 CounterService類似,能將某個值記錄到特定名稱的度量值裡。 GaugeService看起來是這樣的:

package org.springframework.boot.actuate.metrics; 
public interface GaugeService { 
 void submit(String metricName, double value); 
}

你無需實現這些介面。 Spring Boot已經提供了兩者的實現。我們所要做的就是把它們的例項注入所需的 Bean,在適當的時候呼叫其中的方法,更新想要的度量值。

針對上文提到的需求,我們需要把 CounterServiceGaugeService Bean注入 Reading-ListController,然後在 addToReadingList()方法裡呼叫其中的方法。

使用注入的 CounterServiceGaugeService

@Controller 
@RequestMapping("/") 
@ConfigurationProperties("amazon") 
public class ReadingListController { 
 ... 
 private CounterService counterService; 
 @Autowired 
 public ReadingListController( 
 ReadingListRepository readingListRepository, 
 AmazonProperties amazonProperties, 
 CounterService counterService, 
 GaugeService gaugeService) { 
 this.readingListRepository = readingListRepository; 
 this.amazonProperties = amazonProperties; 
 this.counterService = counterService; 
 this.gaugeService = gaugeService; 
 } 
 ... 
 @RequestMapping(method=RequestMethod.POST) 
 public String addToReadingList(Reader reader, Book book) { 
 book.setReader(reader); 
 readingListRepository.save(book); 
 counterService.increment("books.saved");
 gaugeService.submit( 
 "books.last.saved", System.currentTimeMillis()); 
 return "redirect:/"; 
 }
}

修改後的 ReadingListController使用了自動織入機制,透過控制器的構造方法注入 CounterServiceGaugeService,隨後把它們儲存在例項變數裡。此後, addToReading-List()方法每次處理請求時都會呼叫 counterService.increment ("books.saved")gaugeService.submit("books. last.saved")來調整度量值。

儘管 CounterServiceGaugeService用起來很簡單,但還是有一些度量值很難透過增加計數器或記錄指標值來捕獲。對於那些情況,我們可以實現 PublicMetrics介面,提供自己需要的度量資訊。該介面定義了一個 metrics()方法,返回一個 Metric物件的集合:

package org.springframework.boot.actuate.endpoint; 
public interface PublicMetrics { 
 Collection<Metric<?>> metrics(); 
}

為了解 PublicMetrics的使用方法,這裡假設我們想報告一些源自 Spring應用程式上下文的度量值——應用程式上下文啟動的時間、Bean及Bean定義的數量,這些都包含進來會很有意思。 順便再報告一下新增了  @Controller 註解的  Bean 的數量。

釋出自定義度量資訊

package readinglist; 
import java.util.ArrayList; 
import java.util.Collection; 
import java.util.List; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.boot.actuate.endpoint.PublicMetrics; 
import org.springframework.boot.actuate.metrics.Metric; 
import org.springframework.context.ApplicationContext; 
import org.springframework.stereotype.Component; 
import org.springframework.stereotype.Controller; 
@Component 
public class ApplicationContextMetrics implements PublicMetrics { 
 private ApplicationContext context;
 @Autowired 
 public ApplicationContextMetrics(ApplicationContext context) { 
 this.context = context; 
 } 
 @Override 
 public Collection<Metric<?>> metrics() { 
 List<Metric<?>> metrics = new ArrayList<Metric<?>>(); 
 metrics.add(new Metric<Long>("spring.context.startup-date",
 context.getStartupDate())); 
 metrics.add(new Metric<Integer>("spring.beans.definitions",
 context.getBeanDefinitionCount())); 
 metrics.add(new Metric<Integer>("spring.beans", 
 context.getBeanNamesForType(Object.class).length));
 metrics.add(new Metric<Integer>("spring.controllers", 
 context.getBeanNamesForAnnotation(Controller.class).length));
 return metrics; 
 } 
}

Actuator會呼叫 metrics()方法,收集 ApplicationContextMetrics提供的度量資訊。該方法呼叫了所注入的 ApplicationContext上的方法,獲取我們想要報告為度量的數量。每個度量值都會建立一個 Metrics例項,指定度量的名稱和值,將其加入要返回的列表。

建立 ApplicationContextMetrics,並在 ReadingListController裡使用 Counter-ServiceGaugeService之後,我們可以在 /metrics端點的響應中找到如下條目:

{ 
 ... 
 spring.context.startup-date: 1429398980443, 
 spring.beans.definitions: 261, 
 spring.beans: 272, 
 spring.controllers: 2, 
 books.count: 1, 
 gauge.books.save.time: 1429399793260, 
 ... 
 }

當然,這些度量的實際值會根據新增了多少書、何時啟動應用程式及何時儲存最後一本書而發生變化。在這個例子裡,你一定會好奇為什麼 spring.controllers是2。因為這裡算上了 ReadingListController以及 Spring Boot提供的 BasicErrorController

四、建立自定義跟蹤倉庫

預設情況下, /trace端點報告的跟蹤資訊都儲存在記憶體倉庫裡,100個條目封頂。一旦倉庫滿了,就開始移除老的條目,給新的條目騰出空間。在開發階段這沒什麼問題,但在生產環境中,大流量會造成跟蹤資訊還沒來得及看就被丟棄。

為了避免這個問題,你可以宣告自己的 InMemoryTraceRepository Bean,將它的容量調整至100以上。如下配置類可以將容量調整至1000個條目:

package readinglist; 
import org.springframework.boot.actuate.trace.InMemoryTraceRepository; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
@Configuration 
public class ActuatorConfig { 
 @Bean 
 public InMemoryTraceRepository traceRepository() { 
 InMemoryTraceRepository traceRepo = new InMemoryTraceRepository(); 
 traceRepo.setCapacity(1000); 
 return traceRepo; 
 } 
}

倉庫容量翻了10倍,跟蹤資訊的儲存時間應該會更久。不過,繁忙到一定程度,應用程式還是可能在你檢視這些資訊前將其丟棄。這是一個記憶體儲存的倉庫,還要避免容量增長太多,影響應用程式的記憶體使用。

除了上述方法,我們還可以將那些跟蹤條目儲存在其他地方——既不消耗記憶體,又能長久儲存的地方。只需實現 Spring BootTraceRepository介面即可:

package org.springframework.boot.actuate.trace; 
import java.util.List; 
import java.util.Map; 
public interface TraceRepository { 
 List<Trace> findAll(); 
 void add(Map<String, Object> traceInfo); 
}

如你所見, TraceRepository只要求我們實現兩個方法:一個方法查詢所有儲存的 Trace物件,另一個儲存了一個 Trace,包含跟蹤資訊的 Map物件。

作為演示,假設我們建立了一個使用 MongoDB資料庫儲存跟蹤資訊的 TraceRepository例項。

MongoDB儲存跟蹤資料

package readinglist; import java.util.Date; 
import java.util.List; import java.util.Map; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.boot.actuate.trace.Trace; 
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.data.mongodb.core.MongoOperations; 
import org.springframework.stereotype.Service; 
@Service 
public class MongoTraceRepository implements TraceRepository { 
 private MongoOperations mongoOps; 
 @Autowired 
 public MongoTraceRepository(MongoOperations mongoOps) {
 this.mongoOps = mongoOps; 
 } 
 @Override 
 public List<Trace> findAll() { 
 return mongoOps.findAll(Trace.class);
 } 
 @Override 
 public void add(Map<String, Object> traceInfo) { 
 mongoOps.save(new Trace(new Date(), traceInfo));
 } }

findAll()方法很直白,用注入的 MongoOperations來查詢全部 Trace物件。 add()方法稍微有趣一點,用當前時間和含有跟蹤資訊的 Map建立了一個 Trace物件,然後透過  MongoOperations.save()將其儲存下來。唯一的問題是, MongoOperations是哪裡來的?

為了使用 MongoTraceRepository,我們需要保證 Spring應用程式上下文裡先有一個  MongoOperations Bean。得益於 Spring Boot的起步依賴和自動配置,做到這一點只需新增  MongoDB起步依賴即可。你需要如下 Gradle依賴:

compile("org.springframework.boot:spring-boot-starter-data-mongodb")

如果你用的是 Maven,則需要如下依賴:

<dependency> 
 <groupId>org.springframework.boot</groupId> 
 <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>

新增了這個起步依賴後, Spring Data MongoDB和所依賴的庫會新增到應用程式的 Classpath 裡。 Spring Boot會自動配置所需的 Bean,以便使用 MongoDB資料庫。這些 Bean裡就包括  MongoOperations。另外,你需要確保和 MongoOperations通訊的 MongoDB伺服器正常執行。

參考資料:《SpringBoot實戰筆記》

需要這份筆記的朋友可以加助理VX:C18173184271,免費獲取!

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69964492/viewspace-2764905/,如需轉載,請註明出處,否則將追究法律責任。

相關文章