Spring Webflux 未计算的时间损失

Spring Webflux unaccounted time loss

我们最近切换到基于 Spring Webflux 的 ExpediaGroups GraphQL library

自从切换我们的 Jaeger Traces 以来,在创建最后一个数据库查询/跨度之前和之后显示了差距: 除了琐碎的实体 -> DTO 映射之外,在执行上述数据库查询之前或之后没有完成任何计算繁重的工作。通过 VisualVM 进行的初步调查显示没有明显的热点,但我们在本地环境中总共损失了 3-6 毫秒(开发、生产遭受的损失更多),这使整体响应时间增加了 100%。

我们不知所措:这是 WebFlux 问题吗?这是关于链接库的问题吗?我们在一个未饱和的单独线程池中执行我们所有的逻辑(这些结果出现在一个客户端非并发调用我们的 GraphQL 端点)所以我们不应该阻塞 Netty 的事件循环(即使我们这样做了:它应该如果我对 Webflux 的理解是正确的,就不要制造那些“差距”。

我正在寻找进一步调查此问题或任何配置旋钮的方法。

数据库调用之间的“差距”已被确定为与框架相关,可以通过重构我们的代码来规避,头部和尾部的“差距”无法在探查器中解决/解决。此外,我们不会丢失任何跨线程边界的跟踪相关信息,这已被考虑在内。

附加信息:

它有许多不同的原因,例如:

  • 电脑处理速度慢
  • spring-webflux 故障
  • 长/笨拙的代码
  • 本地环境问题/无法运行

尝试 运行在另一台计算机或 public 环境中运行代码

经过大量调试,我们找到了以下答案:

  • 每个请求前面和结尾的“间隙”只是传入请求和相应响应的纯 I/O。这也解释了当被请求者慢慢消耗响应/发送请求时生产中的更大差距,这对本地主机请求不成立
  • 两者之间较大的差距可归因于缺乏预热 (JIT),这对于 WebFlux 堆栈来说似乎是必需的
  • 总体“糟糕”的性能也与预热相关 (JIT)

此外,建议的 PreparsedDocumentProvider GraphQL 缓存(谢谢 Korashen)似乎提供了不错的性能提升:

    @Bean
    fun preparsedDocumentProvider(): PreparsedDocumentProvider = object : PreparsedDocumentProvider {
        @Suppress("MagicNumber")
        private val cache: Cache<String, PreparsedDocumentEntry> = Cache2kBuilder
            .of(String::class.java, PreparsedDocumentEntry::class.java)
            // avoid cache attacks
            .entryCapacity(1024)
            .eternal(true)
            .build()

        override fun getDocument(
            executionInput: ExecutionInput,
            parseAndValidateFunction: Function<ExecutionInput, PreparsedDocumentEntry>
        ) = cache.computeIfAbsent(executionInput.query) { parseAndValidateFunction.apply(executionInput) }
    }