迁移到 Grails 5.1.1 后运行缓慢 - 花在 GrailsControllerUrlMappingInfo 上的时间

Slowness after migrating to Grails 5.1.1 - time spent on GrailsControllerUrlMappingInfo

最近,我们将后端 API 从 Grails 3 迁移到 Grails 5.1.1 与此同时,我们还将 java 版本升级到 11。一切都是 Docker 上的 运行。否则,其他都没有改变。

迁移后,我们现在面临性能问题。但这是一个奇怪的。

首先,我们从 NewRelic 得到了一些结果:

NewRelic 表明 org.grails.web.mapping.mvc.GrailsControllerUrlMappingInfo 是罪魁祸首。它下面没有其他东西是缓慢的。 更深入地挖掘,我们发现了一个 article(不久前)声称 NewRelic 没有很好地检测 Grails。

此时,我们试图在本地重现问题,我们做到了。创建了一个简单的函数,用计时器包装我们需要的任何东西,以测量执行所需的时间:

def withExecutionTimeLog(Closure closure) {
        Long start = System.currentTimeMillis()
        def result = closure()
        log.warn "Execution time -> ${System.currentTimeMillis() - start} ms"

        result
    }

例如,将它用于最慢的端点之一:

这是一个控制器(我们控制的最高点)

def create(Long workspaceId, Long projectId) {
        withExecutionTimeLog {
            canUpdateProject(projectId) {
                withExceptionsHandling {
                    CecUtilization utilization =
                            cecUtilizationService.create(workspaceId, projectId, getCurrentUser())

                    [utilization: utilization]
                }
            }
        }
    }

因此,在这种情况下,timer 函数包装了所有内容,包括数据库调用,一直到视图渲染。

结果如下:

完整的请求周期需要 524ms(其中 432ms 是服务器端:

从那个小小的执行时间记录器我得到了 161ms

综上所述,NewRelic 似乎是对的。其他东西正在浪费周期。最大的问题是WHAT?

我如何描述它?也许有人遇到过类似的情况。 我该如何解决?

另一方面,由于我们拥有所有可能的监控工具,我可以肯定地说服务器没有做太多事情(包括数据库)。分配的堆大小最大为 3G。 通常,对同一个端点的第一个请求需要更长的时间,连续的请求要好得多但仍然很慢。

更新

决定深入挖掘。连接上 Async Profiler,我认为我最初的假设被证明是正确的。

class 加载器似乎是导致性能问题的原因。这也解释了第一个请求(任何类型)的缓慢,而随后的请求开始工作得更快。但是,如果我在另外 4-5 分钟内执行相同的调用,它会再次加载 classes 并且速度很慢。

所以,除非我弄错了,这里的问题是 class 加载程序。我为此责怪 TomcatEmbeddedWebappClassLoader。

现在,最大的问题是如何在 Grails 中解决这个问题?我是 运行 产品中的一个 war 文件 -> java -Dgrails.env=prod -Xms1500m -Xmx3000m -jar app.war

我发现 this post 方向有点相同,但我不确定如何在 Grails 中连接它。

更新 #2

寻找 class 加载器解决方案让我找到了 this issue。建议的解决方案适用于 Spring。我想知道用 grails 解决这个问题的方法是什么。

运行 siege 命令查看多个用户同时发出请求的总时间

我创建了一个存储库,试图按照您所说的去做

https://github.com/fernando88to/slowness5

在linkhttps://github.com/spring-projects/spring-boot/issues/16471中有如下报道:

“它的优先级很低,因为可以通过使用 .jar 包装而不是 .war 包装来避免这个问题,或者,我相信,通过切换到另一个嵌入式容器。就目前情况而言,我们没有计划在 2.x 时间内解决这个问题。"

所以我运行命令'./gradlew bootJar'。

而且我用siege做了测试,第一个请求从1.62秒到0.47秒