Spring Boot 在微服务中的最佳实践
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
要启用 Spring Boot Actuator 的功能,我们必须将 management.endpoint.metrics 设置为 true。现在,你可以通过调用 GET /actuator/metrics 来检查度量指标的完整列表。对我们来说,最重要的指标之一是 http.server.requests,它展示了请求数量和响应时间的统计信息,并会自动记录下请求类型(POST、GET 等)、HTTP 状态码和 uri。
度量指标必须被存储在某个地方。最流行的工具是 InfluxDB 和 Prometheus。它们代表了两种不同的数据收集模型。Prometheus 定期从应用程序的公开端点拉取数据,而 InfluxDB 提供了 REST API,由应用程序来调用。Micrometer 库提供了与 InfluxDB、Prometheus 和其他一些工具的集成方案。要启用对 InfluxDB 的支持,我们需要添加以下依赖项。
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-influx</artifactId>
</dependency>
我们还需要在 Spring Boot 的 application.yml 中配置 InfluxDB 的 URL 和数据库名。
management:
metrics:
export:
influx:
db:?springboot
uri:?http://192.168.99.100:8086
要启用对 Prometheus 的支持,我们首先需要添加对应的 Micrometer 库,然后设置属性 management.endpoint.metrics 为 true。
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
默认情况下,Prometheus 每分钟尝试从定义的目标端点收集一次数据。可以在 Prometheus 的配置文件中设置这些参数,scrape_config 部分负责指定一组目标和参数,以及如何与它们连接。
scrape_configs:
-?job_name:?'springboot'
metrics_path:?'/actuator/prometheus'
static_configs:
-?targets:?['person-service:2222']
有时候,为指标数据打上标签是非常有用的,特别是当我们在单个微服务系统中存在多个应用程序,并将指标数据发送给单个 Influx 数据库时。下面是代码示例。
@Configuration
class?ConfigurationMetrics?{
@Value("${spring.application.name}")
lateinit?var?appName:?String
@Value("${NAMESPACE:default}")
lateinit?var?namespace:?String
@Value("${HOSTNAME:default}")
lateinit?var?hostname:?String
@Bean
fun?tags():?MeterRegistryCustomizer<InfluxMeterRegistry>?{
return?MeterRegistryCustomizer?{?registry?->
registry.config().commonTags("appName",?appName).commonTags("namespace",?namespace).commonTags("pod",?hostname)
}
}
}?
这是一张从 Grafana 中截取的监控视图,表示单个应用的 http.server.requests 指标。
ess=image/format,png)
不要忘记日志
======
日志记录在开发过程中不是很重要,但在维护过程中却非常重要。值得记住的是,在组织中,通过日志的质量可以间接判断应用程序的质量。通常,应用程序由支持团队维护,因此日志应该是非常重要的。不要试图把所有的事情都放在日志里,只有最重要的事件才应该被记录下来。
为微服务中的所有应用程序使用相同的日志格式也很重要。例如,如果要以 JSON 格式记录日志,那么对每个应用程序都应该用 JSON 格式。如果你使用标记 appName 来指示应用程序的名称或用 instanceId 来区分同一应用程序的不同实例,那么在任何地方都要这样做。为什么?你通常希望将微服务中收集的所有日志存储在一个单独的中央数据库中。最流行的工具(或者说是收集日志的工具)是 Elastic Stack(ELK)。将中央数据库的优势发挥到最大,你应该确保查询条件和响应结构对于所有应用程序都是相同的,尤其是将不同微服务之间的日志关联起来。怎么做呢?当然是通过使用第三方库。我推荐你使用 Spring Boot logging,要使用它,你需要添加如下依赖。
<dependency>
<groupId>com.github.piomin</groupId>
<artifactId>logstash-logging-spring-boot-starter</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
这个库将迫使你使用一些良好的日志记录实践,并自动与 Logstash 集成(ELK 中负责收集日志的工具)。它的主要特点是:
能够记录完整的 HTTP 请求和 HTTP 响应,并使用适当的标签将这些日志发送到 Logstash,这些标签标明了 HTTP 的请求类型和 HTTP 响应码
计算和存储每个请求的执行时间
当使用 Spring RestTemplate 调用的下游服务时,生成和传递 correlationId
为了能够向 Logstash 发送日志,我们至少需要如下配置信息。
logging.logstash:
enabled:?true
url:?192.168.99.100:5000
在添加了 logstash-logging-spring-boot-starter 之后,你就可以使用 Logstash 中的日志标记功能。下图是来自 Kibana 中的单条日志记录的截图。
我们还可以将库 Spring Cloud Sleuth 添加到我们的依赖项中。
<dependency>?
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
Spring Cloud Sleuth 传递与 Zipkin(一种流行的分布式跟踪工具)兼容的标头。它的主要特点是:
它将跟踪(相关请求)和 span IDs 添加到 Slf4J MDC
它记录时间信息以帮助进行延迟分析
它修改了日志的格式,以添加一些信息,比如附加的 MDC 字段
它提供与其他 Spring 组件的集成,如 OpenFeign、RestTemplate 或 Spring Cloud Netflix Zuul
让你的 API 易用
========
在大多数情况下,其他应用程序将通过 REST API 调用你的应用程序。因此我们需要小心的维护一份 API 文档。文档应该由代码生成。当然有一些工具可以做到这一点。其中最受欢迎的是 Swagger。你可以使用 SpringFox 项目轻松地将 Swagger 2 集成到你的 Spring Boot 应用程序中。为了使用 Swagger,我们需要添加如下依赖。第一个库负责从 Spring MVC 控制器代码中生成 Swagger descriptor,而第二个库负责解析 Swagger descriptor 并在你的浏览器中展示页面。
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
我们还必须提供一些 Bean 来修改 Swagger 的默认行为。它应该只展示我们自己提供的 REST API,而不展示 Spring Boot Actuator 的 API。我们也可以通过定义 UiConfiguration bean 来定制 UI 外观。
@Configuration
@EnableSwagger2
public?class?ConfigurationSwagger?{
@Autowired
Optional<BuildProperties>?build;
@Bean
public?Docket?api()?{
String?version?=?"1.0.0";
if?(build.isPresent())
version?=?build.get().getVersion();
return?new?Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo(version))
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("(/components.*)"))
.build()
.useDefaultResponseMessages(false)
.forCodeGeneration(true);
}
@Bean
public?UiConfiguration?uiConfig()?{
return?UiConfigurationBuilder.builder().docExpansion(DocExpansion.LIST).build();
}
private?ApiInfo?apiInfo(String?version)?{
return?new?ApiInfoBuilder()
.title("API?-?Components?Service")
.description("Managing?Components.")
.version(version)
.build();
}
}?
下图是 Swagger 2 UI 的例子。
接下来是为所有微服务定义相同的 REST API 准则。如果你始终如一地为你的微服务构建 API,那么对于外部和内部客户端,集成微服务要简单得多。指南应该包含如何构建 API 的说明,需要在请求和响应上设置哪些 HTTP header,如何生成错误码等。这些准则应该与组织中的所有开发人员和供应商共享。有关为 Spring Boot 微服务生成 Swagger 文档(包括为 API 网关上的所有应用程序公开它)的更详细说明,您可以参考我的文章《Microservices API Documentation with Swagger2》。
不要害怕使用断路器
=========
如果你使用 Spring cloud 在微服务之间进行通信,你可以利用 Spring Cloud Netflix Hystrix 或 Spring Cloud 断路器来实现断路。然而,由于 Netflix 不再开发 Hystrix,Pivotal 团队已经将第一个解决方案转移到了维护模式。推荐的解决方案是构建在 resilience4j 项目之上的新的 Spring Cloud 断路器。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
然后,我们需要为断路器设置所需的配置,方法是定义一个 Customizer bean,该 bean 被传递给 Resilience4JCircuitBreakerFactory。以下示例使用默认值。
@Bean
public?Customizer<Resilience4JCircuitBreakerFactory>?defaultCustomizer()?{
return?factory?->?factory.configureDefault(id?->?new?Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(5)).build())
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.build());
}?
有关将 Hystrix 断路器与 Spring Boot 应用程序集成的详细信息,请参阅我的另一篇文章:《Part 3: Creating Microservices: Circuit Breaker, Fallback and Load Balancing with Spring Cloud》。
使应用程序透明
=======
我们不应该忘记,迁移到微服务的最重要原因之一是持续交付的需求。今天,快速交付变更的能力在市场上具有优势。你甚至应该能够在一天内多次交付更改。因此,重要的是当前是什么版本,它在哪里发布,以及它包括哪些更改。
在使用 Spring Boot 和 Maven 时,我们可以很容易地发布诸如最后更改日期、Git 的 commit id 或应用程序的多个版本等信息。要实现这一点,我们只需要在我们的 pom.xml 中包含以下 Maven 插件。
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<configuration>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
</configuration>
</plugin>
</plugins>
假设你已经启用了 Spring Boot Actuator(参见第 1 节),你必须启用/info 端点来显示所有有趣的数据。
management.endpoint.info.enabled:?true
当然,我们有许多微服务组成的一个大系统,并且每个微服务都有一些正在运行的实例。最好是在一个应用程序中监视我们所有的实例——就像收集度量指标和收集日志一样。幸运的是,有一个专门用于 Spring Boot 应用程序的工具,它能够从所有 Actuator 端点收集数据并在 UI 中显示它们。它是由 Codecentric 开发的 Spring Boot Admin。使用 Spring Boot Admin 最方便的方式是创建一个专门的 Spring Boot 应用程序,该程序需要添加 Spring Boot Admin 的依赖项和服务发现的依赖项,例如 Spring Cloud Netflix Eureka。
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
然后,我们应该通过使用 @EnableAdminServer 注解来启用 Spring Boot Admin。
@SpringBootApplication
@EnableDiscoveryClient
@EnableAdminServer
@EnableAutoConfiguration
public?class?Application?{
public?static?void?main(String[]?args)?{
SpringApplication.run(Application.class,?args);
}
}?
评论