迁移到 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 命令查看多个用户同时发出请求的总时间
我创建了一个存储库,试图按照您所说的去做
在linkhttps://github.com/spring-projects/spring-boot/issues/16471中有如下报道:
“它的优先级很低,因为可以通过使用 .jar 包装而不是 .war 包装来避免这个问题,或者,我相信,通过切换到另一个嵌入式容器。就目前情况而言,我们没有计划在 2.x 时间内解决这个问题。"
所以我运行命令'./gradlew bootJar'。
而且我用siege做了测试,第一个请求从1.62秒到0.47秒
最近,我们将后端 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 命令查看多个用户同时发出请求的总时间
我创建了一个存储库,试图按照您所说的去做
在linkhttps://github.com/spring-projects/spring-boot/issues/16471中有如下报道:
“它的优先级很低,因为可以通过使用 .jar 包装而不是 .war 包装来避免这个问题,或者,我相信,通过切换到另一个嵌入式容器。就目前情况而言,我们没有计划在 2.x 时间内解决这个问题。"
所以我运行命令'./gradlew bootJar'。
而且我用siege做了测试,第一个请求从1.62秒到0.47秒