我的队友正在使用 Spring Boot + PostgreSQL + Redis + Kafka 等开发 REST API,而我正在使用 Prometheus、Grafana 等制作监控工具来监控该 API。
此 API 提供四个端点。我们称它们为 A、B、C 和 D。
我想收集“每个 API 端点的大致响应时间”的指标。所以我编写了简单的脚本(Bash 和 Perl)来测量使用 curl
调用每个端点所花费的时间。然后我使用 crontab 注册脚本,每分钟执行一次。
# get response time
curl -XGET -s -w "\\n%{http_code}\\n%{time_total}\\n" "http://for.example/A" | tail -n 1 >> log_A
curl -XGET -s -w "\\n%{http_code}\\n%{time_total}\\n" "http://for.example/B" | tail -n 1 >> log_B
...
如下所示,结果有些奇怪:
调用一个端点大约需要 100 毫秒,调用其他端点只需几毫秒。
我调查了一下,发现每个 cron 作业的第一次调用花费了更长的时间。也就是说,如果我测量 A、B、C,然后测量 D,A 需要 100 毫秒。如果我测量 B、C、D,那么 A、B 需要 100 毫秒。接下来的三个端点响应很快。在图中,我修改了序列,我可以看到它立即影响了结果。
我怀疑 Spring boot 应用程序和数据库(或 redis,或 kafka?)之间的连接因过期而断开连接,因此需要先重新连接。但我认为一分钟对于任何配置都无法使任何连接过期来说太短了。无论如何,我必须从什么时候开始?
如有任何建议,我们将不胜感激。
编辑:
写完这篇文章后,我启动了一个非常简单的 Spring Boot REST API 应用程序,其代码来自 Spring boot 指南文档(https://spring.io/guides/gs/rest-service),没有使用 DB 或任何外部东西。 curl
这个 API 总是只需要 4ms。所以我更怀疑外在的东西。
如果你需要查看有关Spring boot 应用程序的配置。这是 pom.xml
和 application.yaml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.company</groupId>
<artifactId>sylphid</artifactId>
<version>0.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>personal</artifactId>
<version>0.3.0</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>${spring-kafka.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-spi -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-spi</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-spring-web -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-spring-web</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
<finalName>personal</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.21.0</version>
<configuration>
<images>
<image>
<name>sylphid/${project.build.finalName}</name>
<build>
<from>openjdk:8u162-jdk</from>
<entryPoint>java -Dspring.profiles.active=docker -jar /application/${project.build.finalName}.jar</entryPoint>
<assembly>
<basedir>/application</basedir>
<descriptorRef>artifact</descriptorRef>
<inline>
<id>assembly</id>
<files>
<file>
<source>target/${project.build.finalName}.jar</source>
</file>
</files>
</inline>
</assembly>
<tags>
<tag>latest</tag>
<tag>${project.version}</tag>
</tags>
<ports>
<port>8080</port>
</ports>
</build>
<run>
<namingStrategy>alias</namingStrategy>
</run>
<alias>${project.build.finalName}</alias>
</image>
</images>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype Nexus Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>
spring:
profiles: allnative
application:
name: personal
jpa:
database-platform: org.hibernate.dialect.PostgreSQL9Dialect
properties:
hibernate:
temp.use_jdbc_metadata_defaults: false
show_sql: false
format_sql: false
use_sql_comments: false
hibernate:
ddl-auto: update
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://127.0.0.1:5432/sylphid
username: postgres
batch:
initialize-schema: always
cache:
type: redis
redis:
key-prefix: sylphid_
time-to-live: 60m
redis:
host: 127.0.0.1
port: 6379
kafka:
bootstrap-servers: 127.0.0.1:9092
consumer:
auto-offset-reset: earliest
group-id: bookclub
server:
port: 13480
app:
topic:
selection: bookclub.selection
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
logging:
file: allnative.log
编辑(2):
我正在为每个端点调用 curl
,没有任何延迟:
curl endpointA; curl endpointB; curl endpointC; curl endpointD
( almost 60 seconds interval before next cronjob )
curl endpointA; curl endpointB; curl endpointC; curl endpointD
...
昨天我试图在每次 curl
执行之间插入一些延迟,猜测“最后一个请求和当前请求之间的间隔会影响当前请求的响应时间”。 (就像......如果长时间没有人要求他做某事,人类 worker 可能会睡着。当有人要求他时,他需要更多时间醒来。如果有人要求他做当他醒着的时候,他可以更快地完成第二份工作。)
当我插入超过 2 秒的延迟时,我可以看到另一个端点开始变慢。当我插入 10 秒延迟时:
curl endpointA; sleep 10; curl endpointB; sleep 10;...
( about 20 seconds interval, because crontab still executes every minutes )
curl endpointA; sleep 10; curl endpointB; sleep 10;...
这是结果。大约 100 毫秒后,每个端点开始响应。
编辑(3)
作为一个新的尝试,我尝试使用Jetty作为嵌入式服务器而不是Tomcat。结果有些令人印象深刻。使用 Jetty 的应用程序几乎在每次测量中都显示出非常稳定的响应时间。甚至 Jetty 有时也会出现较长的响应时间(大约 300 毫秒左右),但这种情况非常罕见。
我已经观察了两个测试集(Tomcat 和 Jetty)几个小时了,我打算观察一天或几天。如果继续这样下去,我打算把这件事告诉我的队友,并建议将嵌入式服务器更改为 Jetty。
但是,如果 Tomcat 是问题的根源,我不知道为什么官方指南中的简单 Spring Boot 应用程序没有出现这种症状。
最佳答案
这是一个非常有趣的测试。您的堆栈很复杂,我理解在这种情况下对性能的需求。
关于Tomcat/Jetty的性能差异。我可以建议您尝试使用 MockMvc 进行集成测试(请参阅 get started )
我知道在 OPS 基础设施和 DEV 框架之间进行拆分测试有多么困难,但是使用 SpringBoot,您可以通过单元测试来测试和显示执行性能。
随时在您的 API 上重复调用一个调用,并在执行过程中添加一个秒表。
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ApplicationTest {
private final static Logger LOGGER = LoggerFactory.getLogger(ApplicationTest .class);
@Autowired
private MockMvc mockMvc;
@Test
public void shouldReturnDefaultMessage() throws Exception {
StopWatch stopWatch = new StopWatch("Testing REST API performances");
for(int i=1; i<=5; i<++) {
stopWatch.start("Test iteration " + i);
this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("Hello World")));
stopWatch.stop();
}
// Print the result
LOGGER.info(stopWatch.prettyPrint());
}
}
这个测试应该揭示性能问题应该是其余 Controller 的初始化。
本地我在第一次调用时也有延迟。
秒表“测试 REST API 性能”:运行时间(毫秒)= 806
- 00510ms 063 % 测试迭代 1
- 00072ms 009 % 测试迭代 2
- 00080ms 010 % 测试迭代 3
- 00071ms 009 % 测试迭代 4
- 00073ms 009 % 测试迭代 5
如您所见,第一个调用需要很长时间。
希望我的回答能帮助您达到预期的性能并帮助您做出选择。
关于spring-boot - Spring boot - 几个请求的第一个请求的响应时间很长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54192265/