【问题标题】:Spring boot - strange long response time for the first request of several onesSpring boot - 几个请求的第一个请求奇怪的长响应时间
【发布时间】:2019-01-15 03:20:00
【问题描述】:

我的队友正在使用 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 只需要 4 毫秒。所以我更怀疑外部的东西。

如果您需要查看有关 Spring Boot 应用程序的配置。这里是pom.xmlapplication.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 执行之间插入一些延迟,猜测“上次请求和当前请求之间的间隔会影响当前请求的响应时间”。 (比如......如果长时间没有人要求他做某事,人类工人可能会睡着。当有人要求他时,他需要更多时间醒来。如果其他人要求他在他醒着的时候,他可以更快地完成第二份工作。)

当我插入超过 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 应用程序没有出现这种症状。

【问题讨论】:

  • 你能发布你的代码相关的作业调度属性吗?
  • @JonathanJohx 很抱歉,“作业调度程序属性”是什么意思?如果您的意思是我的监控调度程序,我只是使用了 crontab "* * * * * check.sh" 并且 check.sh 执行 curl 以向每个端点发送请求。或者,如果您的意思是 Java 应用程序......代码不是我的,由于我公司的政策,我不能发布它。但它是一个常规的(?)Spring boot REST 应用程序,它接收请求,向数据库发送查询,向客户端返回 Json 响应......
  • spring boot 默认配置的池中的 JDBC 连接通常有一个最大空闲时间,这意味着如果它们未使用,它们将被关闭。我对redis不熟悉,但是由于您为它指定了网络地址,所以我想它是相同的。您可以查看添加一些简单的仪器日志记录,查看baeldung.com/spring-performance-logging这可能会给您一些线索时间都花在了。 Spring boot 使用github.com/brettwooldridge/HikariCP 进行数据库连接池。请注意,所有这些技术都有自己的缓存,所以这不足为奇。
  • @Taylor 感谢您的评论。我不擅长 Java 开发,但我会尝试你告诉我的。当我在帖子中添加一些新信息时(“Edit(3)”),我将嵌入式服务器从 Tomcat 更改为 Jetty,它似乎可以工作。
  • 如果从tomcat换成jetty有效果,可能跟他们管理线程池的方式有关。同样,最好的办法是为 tomcat 增加日志以了解它在做什么。增加日志记录显然会减慢速度,但会给你一些洞察力。您还可以查看 jprofiler 之类的分析工具来了解所花费的时间。

标签: spring-boot redis apache-kafka grafana


【解决方案1】:

这是一个非常有趣的测试。您的堆栈很复杂,我理解在这种情况下对性能的需求。

关于 Tomcat/Jetty 之间的性能差异。我可以建议您尝试使用 MockMvc 进行集成测试(请参阅get started

我知道在 OPS 基础架构和 DEV 框架之间拆分测试有多困难,但是使用 SpringBoot,您可以通过单元测试来测试和显示执行性能。

您可以随时在您的 API 上迭代一个调用,并在执行过程中添加一个 StopWatch。

@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());
  }
}

这个测试应该揭示性能问题应该是其余控制器的初始化。

在本地,我在第一次通话时也有延迟。

StopWatch“测试 REST API 性能”:运行时间(毫秒)= 806

  • 00510ms 063 % 测试迭代 1
  • 00072ms 009 % 测试迭代 2
  • 00080ms 010 % 测试迭代 3
  • 00071ms 009 % 测试迭代 4
  • 00073ms 009 % 测试迭代 5

如您所见,第一次调用需要很长时间。

希望我的回答能帮助您达到预期的性能并帮助您做出选择。

【讨论】:

    猜你喜欢
    • 2016-08-01
    • 1970-01-01
    • 2020-03-23
    • 2017-09-17
    • 2017-09-04
    • 2022-01-21
    • 2017-03-07
    • 2020-08-13
    • 2018-05-03
    相关资源
    最近更新 更多