🧑 博主简介:历代文学网(PC端可以访问:历代文学,移动端可微信小程序搜索“历代文学”)总架构师,
15年
工作经验,精通
Java编程
,
高并发设计
,
Springboot和微服务
,熟悉
Linux
,
ESXI虚拟化
以及
云原生Docker和K8s
,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
在本文中,我将提出构建Spring Boot应用程序的“黄金法则”列表,这些应用程序是基于微服务的系统的一部分。我基于我将在 JEE 服务器上运行的单片 SOAP 应用程序迁移到基于 Spring Boot 构建的基于 REST 的小型应用程序的经验。此最佳实践列表假设您在巨大的传入流量下在生产环境中运行许多微服务。让我们开始吧。
1. 收集指标
指标可视化可以改变组织中的系统监控方法,这真是太神奇了。在 Grafana 中设置监控后,我们能够在客户向我们的支持团队报告之前识别出系统中 90% 以上的大问题。由于这两个带有大量图表和警报的监视器,我们可以比以前更快地做出反应。如果您拥有基于微服务的架构,那么指标就变得比整体架构更重要。
对我们来说,好消息是 Spring Boot 带有内置机制来收集最重要的指标。事实上,我们只需要设置一些配置属性来公开 Spring Boot Actuator 提供的预定义指标集。要使用它,我们需要将 Actuator starter 作为依赖项包含在内:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
要启用指标端点,我们必须将属性设置
management.endpoint.metrics.enabled
为
true
。现在,您可以通过调用端点来查看生成的指标的完整列表
GET /actuator/metrics
。对我们来说,最重要的指标之一是
http.server.requests
,它提供了传入请求数和响应时间的统计数据。它会自动标记方法类型(POST、GET 等)、HTTP 状态和 uri。
指标必须存储在某个地方。最流行的工具是InfluxDB和Prometheus。它们代表了两种不同的数据收集模型。Prometheus 定期从应用程序公开的端点检索数据,而 InfluxDB 提供必须由应用程序调用的 REST API。这两个工具和其他几个工具的集成是使用Micrometer库实现的。要启用对 InfluxDB 的支持,我们必须包含以下依赖项。
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-influx</artifactId>
</dependency>
我们还必须在
application.yml
文件中至少提供 URL 和 Influx 数据库名称。
management:
metrics:
export:
influx:
db: springboot
uri: http://192.168.99.100:8086
要启用 Prometheus HTTP 端点,我们首先需要包含适当的 Micrometer 模块并将属性设置
management.endpoint.prometheus.enabled
为
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 数据库时。以下是在 Kubernetes 上运行的应用程序的标记示例。
@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
单个应用程序指标创建的图表。
2. ELK日志记录
日志记录在开发过程中并不是很重要,但在维护过程中却是关键点。值得记住的是,组织将通过日志质量来查看您的应用程序。通常,应用程序由支持团队维护,因此您的日志应该很重要。不要试图把所有东西都放在那里,只记录最重要的事件。
对所有微服务使用相同的日志记录标准也很重要。例如,如果您以 JSON 格式记录信息,请对每个应用程序执行相同的操作。如果您使用标签
appName
来指示应用程序名称或
instanceId
区分同一应用程序的不同实例,请在所有地方都这样做。为什么?您通常希望将从所有微服务收集的日志存储在一个中心位置。最流行的工具(或者更确切地说是工具集合)是Elastic Stack (ELK)。要利用将日志存储在中心位置的优势,您应该确保查询条件和响应结构对于所有应用程序都是相同的,尤其是您将关联不同微服务之间的日志。如何做到这一点?当然是通过使用外部库。我可以推荐我的 Spring Boot 日志记录库。要使用它,您应该将其包含在您的依赖项中。
<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 状态)
- 能够计算并存储每个请求的执行时间
- 能够为使用 Spring 调用的下游服务生成和传播 correlationId
RestTemplate
为了能够将日志发送到 Logstash,我们至少应该向提供它的地址和
logging.logstash.enabled
属性
true
。
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 兼容的标头 - Zipkin 是一种流行的分布式跟踪工具。其主要特点是:
- 它将跟踪(关联请求)和跨度 ID 添加到 Slf4J MDC
- 它记录时间信息以帮助延迟分析
- 它修改了日志条目的模式以添加一些信息,例如额外的 MDC 字段
- 它提供与其他 Spring 组件(如 OpenFeign、RestTemplate 或 Spring Cloud Netflix Zuul)的集成
3. 集成Swagger2 构建 API
在大多数情况下,您的应用程序将通过基于 REST 的 API 被其他应用程序调用。因此,值得注意正确且清晰的文档。文档应与代码一起生成。当然有一些工具可以做到这一点。其中最受欢迎的一种是Swagger 。您可以使用****SpringFox项目轻松地将 Swagger 2 与您的 Spring Boot 应用程序集成。为了公开带有 API 文档的 Swagger HTML 站点,我们需要包含以下依赖项。第一个库负责从 Spring MVC 控制器代码生成 Swagger 描述符,而第二个库嵌入 Swagger UI 以在您的 Web 浏览器中显示 Swagger YAML 描述符的表示。
<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 生成行为。它应该只记录我们控制器内部实现的方法,例如,不是 Spring Boot 自动提供的方法(如
/actuator/*
端点)。我们还可以通过定义 bean 来自定义 UI 外观
UiConfiguration
。
@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 的说明、请求和响应中需要设置哪些标头、如何生成错误代码等。此类指南应与组织中的所有开发人员和供应商共享。
4. 使用断路器
如果您使用 Spring Cloud 进行微服务之间的通信,则可以利用 Spring Cloud Netflix Hystrix 或 Spring Cloud Circuit Breaker 来实现断路。但是,由于 Netflix 不再开发 Hystrix,因此 Pivotal 团队已将第一种解决方案移至维护模式。推荐的解决方案是基于resilience4j项目构建的新 Spring Cloud Circuit Breaker。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
Customizer
然后我们需要通过定义传递的 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());
}
5. 让你的申请透明化
我们不应忘记,迁移到微服务架构的最重要原因之一是持续交付的需求。如今,快速交付变更的能力在市场上具有优势。您甚至应该能够在一天内多次交付变更。因此,了解当前版本是什么、发布在哪里以及包含哪些变更非常重要。
使用 Spring Boot 和 Maven 时,我们可以轻松发布此类信息,例如上次更改的日期、Git 提交 ID 或应用程序的多个版本。要实现这一点,我们只需将以下 Maven 插件包含到我们的插件中即可
pom.xml
。
<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 应用程序,其中包含 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>
然后我们应该通过使用 注释主类来为 Spring Boot 应用程序启用它
@EnableAdminServer
。
@SpringBootApplication
@EnableDiscoveryClient
@EnableAdminServer
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
使用 Spring Boot Admin,我们可以轻松浏览在发现服务器中注册的应用程序列表,并查看每个应用程序的版本或提交信息。
我们可以扩展详细信息以查看从
/info
端点检索的所有元素以及从其他执行器端点收集的更多数据。
6. 编写契约测试
消费者驱动契约 ( CDC ) 测试是一种允许您验证系统内应用程序之间集成的方法。此类交互的数量可能非常大,尤其是在您维护基于微服务的架构时。得益于Spring Cloud Contract项目,在 Spring Boot 中开始契约测试相对容易。还有一些专门为 CDC 设计的框架,如 Pact,但 Spring Cloud Contract 可能是首选,因为我们使用的是 Spring Boot。
要在生产者端使用它,我们需要包含 Spring Cloud Contract Verifier。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency>
在消费者方面,我们应该包括 Spring Cloud Contract Stub Runner。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
第一步是定义合同。编写合同的一种选择是使用 Groovy 语言。合同应在生产者和消费者双方进行验证。以下是:
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
method 'GET'
urlPath('/persons/1')
}
response {
status OK()
body([
id: 1,
firstName: 'John',
lastName: 'Smith',
address: ([
city: $(regex(alphaNumeric())),
country: $(regex(alphaNumeric())),
postalCode: $(regex('[0-9]{2}-[0-9]{3}')),
houseNo: $(regex(positiveInt())),
street: $(regex(nonEmpty()))
])
])
headers {
contentType(applicationJson())
}
}
}
契约与存根一起打包在 JAR 中。它可以发布到 Artifactory 或 Nexus 等存储库管理器,然后消费者可以在 JUnit 测试期间从那里下载它。生成的 JAR 文件以 为后缀
stubs
。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
@AutoConfigureStubRunner(ids = {"pl.piomin.services:person-service:+:stubs:8090"}, consumerName = "letter-consumer", stubsPerConsumer = true, stubsMode = StubsMode.REMOTE, repositoryRoot = "http://192.168.99.100:8081/artifactory/libs-snapshot-local")
@DirtiesContext
public class PersonConsumerContractTest {
@Autowired
private PersonClient personClient;
@Test
public void verifyPerson() {
Person p = personClient.findPersonById(1);
Assert.assertNotNull(p);
Assert.assertEquals(1, p.getId().intValue());
Assert.assertNotNull(p.getFirstName());
Assert.assertNotNull(p.getLastName());
Assert.assertNotNull(p.getAddress());
Assert.assertNotNull(p.getAddress().getCity());
Assert.assertNotNull(p.getAddress().getCountry());
Assert.assertNotNull(p.getAddress().getPostalCode());
Assert.assertNotNull(p.getAddress().getStreet());
Assert.assertNotEquals(0, p.getAddress().getHouseNo());
}
}
契约测试不会验证基于微服务的系统中的复杂用例。但是,它是测试微服务之间交互的第一阶段。一旦确保应用程序之间的 API 契约有效,您就可以进行更高级的集成或端到端测试。
7. 保持最新状态
Spring Boot 和 Spring Cloud 相对频繁地发布其框架的新版本。假设您的微服务有一个小型代码库,那么很容易升级所用库的版本。Spring Cloud 使用发布列车模式发布项目的新版本,以简化依赖项管理并避免库版本不兼容之间发生冲突的问题。
此外,Spring Boot 系统地改善了应用程序的启动时间和内存占用,因此值得仅凭这一点就对其进行更新。这是 Spring Boot 和 Spring Cloud 的当前稳定版本。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
8. 结论
本文向您展示了使用 Spring Boot 功能和 Spring Cloud 中的一些附加库来遵循最佳实践并不难。这些最佳实践将使您更容易迁移到基于微服务的架构,并在容器中运行您的应用程序。
版权归原作者 月下独码 所有, 如有侵权,请联系我们删除。