ITPub博客

首页 > 架构设计 > 软件结构 > SpringBoot:定制 Actuator

SpringBoot:定制 Actuator

原创 软件结构 作者:无敌天骄 时间:2021-03-25 16:01:39 0 删除 编辑

虽然 Actuator提供了很多运行中 Spring Boot应用程序的内部工作细节,但难免和你的需求有所偏差。也许你并不需要它提供的所有功能,想要关闭一些也说不定。或者,你需要对 Actuator 稍作扩展,增加一些自定义的度量信息,以满足你对应用程序的需求。

实际上, Actuator有多种定制方式,包括以下五项。

  • 重命名端点。
  • 启用和禁用端点。
  • 自定义度量信息。
  • 创建自定义仓库来存储跟踪数据。
  • 插入自定义的健康指示器。

接下来,我们会了解如何定制 Actuator,以满足我们的需要。先来看一个最简单的定制:重命名 Actuator端点。

一、修改端点 ID


每个  Actuator 端点都有一个ID用来决定端点的路径,比方说, /beans端点的默认ID就是 beans。

如果端点的路径是由ID决定的,那么可以通过修改ID来改变端点的路径。你要做的就是设置一个属性,属性名是 endpoints.endpoint-id.id。

我们用 /shutdown端点来做个演示,它会响应发往 /shutdown的 POST请求。假设你想让它处理发往 /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的应用程序上下文中的 Bean。 CounterService这个接口里定义了三个方法,分别用来增加、减少或重置 特定名称的度量值,代码如下:

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,在适当的时候调用其中的方法,更新想要的度量值。

针对上文提到的需求,我们需要把 CounterService和 GaugeService Bean注入 Reading-ListController,然后在 addToReadingList()方法里调用其中的方法。

使用注入的 CounterService和 GaugeService

@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使用了自动织入机制,通过控制器的构造方法注入 CounterService和 GaugeService,随后把它们保存在实例变量里。此后, addToReading-List()方法每次处理请求时都会调用 counterService.increment ("books.saved")和 gaugeService.submit("books. last.saved")来调整度量值。

尽管 CounterService和 GaugeService用起来很简单,但还是有一些度量值很难通过增加计数器或记录指标值来捕获。对于那些情况,我们可以实现 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-Service和 GaugeService之后,我们可以在 /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 Boot的 TraceRepository接口即可:

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博客 ” ,链接:https://blog.itpub.net/69964492/viewspace-2764905/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
公众号"慕容千语" //免费领取面试PDF,架构学习视频

注册时间:2020-02-26

  • 博文量
    79
  • 访问量
    58483