[英]Spring boot cold start
我有一个 spring 引导应用程序,我在 openshift 集群中的 docker 容器中运行。 在稳定的 state 中,有 N 个应用程序实例(假设 N=5)并且请求被负载均衡到这 N 个实例。 一切运行良好,响应时间很短(~5ms,总吞吐量为~60k)。
每当我添加一个新实例时,响应时间都会短暂增加(最多约 70 毫秒),然后恢复正常。
我能做些什么来避免这种类型的冷启动吗? 我尝试通过在发送流量之前连续调用 ~100 curl 来预热应用程序,但这没有帮助吗?
我是否需要更好的高并发预热脚本? 有没有更好的方法来处理这个问题?
谢谢
如果您的应用程序在向其提供请求时运行良好,但仍然存在响应缓慢的问题,则应尝试启用分层编译
-XX:CompileThreshold -XX:TieredCompilation
通常,VM 使用解释器来收集有关提供给编译器的方法的分析信息。 在分层方案中,除了解释器之外,客户端编译器还用于生成方法的编译版本,这些方法收集有关自身的分析信息。
由于编译代码比解释代码快得多,程序在分析阶段以更好的性能执行。
我们的微服务遇到了类似的问题,为了预热,我们添加了一个组件
ApplicationStartup implements ApplicationListener<ApplicationReadyEvent>
在应用程序启动后立即调用服务,这对我们有用。 使用此解决方案,保证将在您的有效负载中使用的所有类将在您启动的每个实例中的实例启动后立即加载,并且您不需要外部脚本来进行调用。 外部脚本也有问题,我们不能肯定地说新实例处理的调用在哪里。
@Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
@Autowired
YourService yourService;
@Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
System.out.println("ApplicationReadyEvent: application is up");
try {
// some code to call yourservice with property driven or constant inputs
} catch (Exception e) {
e.printStackTrace();
}
}
}
首先,我会尝试启用JIT 编译并比较结果。 Baeldung 中有一篇很好的文章将 Graal 性能与默认的 C1 和 C2 JIT 编译器进行了比较——您可能想要针对您的工作负载运行一些测试。 基本上,您需要在运行 Java 应用程序时设置以下选项:
-XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler
此外,请确保您已使用 Spring Boot 的执行器健康检查 URL ( /actuator/health ) 在 OpenShift 中配置就绪探针。 否则,您的容器可能会在准备好服务之前收到流量。
就绪探针确定容器是否已准备好为请求提供服务。 如果就绪探针使容器失败,端点控制器将确保容器的 IP 地址从所有服务的端点中删除。 准备就绪探针可用于向端点控制器发出信号,即使容器正在运行,它也不应接收来自代理的任何流量。 通过配置 pod 配置的template.spec.containers.readinessprobe节来设置就绪检查。
最后,让 NGINX 或其他一些反向代理缓存您的响应也有帮助。
在我的场景中,我模拟了 100 多个 curl 请求来初始化这些客户端池、预加载缓存或其他延迟加载的人。
我在WarmupHealthIndicator implements HealthIndicator
,它实现了一个弹簧执行器健康检查端点。
最终它起作用了,在预热结束之前,来自 Nginx(或其他负载均衡器)的任何修复检查都将获得 5xx 状态代码,并在正文消息下方。 在状态上升后,没有流量会花费应用初始化时间。
{
"status": "DOWN",
"details": {
"warmup": {
"status": "DOWN"
},
"diskSpace": {
"status": "UP",
"details": {
"total": 536608768000,
"free": 395195826176,
"threshold": 10485760
}
}
}
}
此外, NGINX Plus
有一个付费功能slow_start ,它可以为你的兴趣做同样的事情。
这个问题可以从两个方面来解决。 第一种方法是上菜前先热身。 第二种方法是一开始就少给外部的请求,这样可以预留更多的计算资源来完成JVM的一些初始化(比如类加载)。 无论哪种方式,都是因为 JVM 需要预热才能启动。 这是由JVM的运行原理决定的。 特别是HotSpot虚拟机,其执行引擎由解释执行引擎和实时编译执行(JIT)两部分组成。 对于 JIT,需要 CPU 资源来实时编译字节码。 此外,类的延迟加载在第一次运行时也会需要更多时间。
JVM预热主要是解决类加载和实时编译两个问题。
对于以上两个方向,类加载本身会消耗更多的时间。 热身这部分会得到更大的投入产出比。
从网络层面,给定一定的预热流量,可以是特定的预热流量,也可以是普通的用户请求。
这通常可以在 nginx 层进行流量控制。 当一个新启动的节点加入上游时,可以给新节点一个非常低的权重。 这样,在初始阶段只进入了少量的流量。 因此,预留了足够的计算资源。 做代码预热,即类加载和即时编译。 如果服务只提供 RPC 服务,不提供 HTTP 服务,则 RPC 框架层可以做流量预热。 例如,Dubbo 等 RPC 框架已经具备服务预热功能。 同样,预热意味着启动初期的节点只给少量的流量。
预热所需的计算资源在上述方法中都有提及。 也就是CPU。 如果您的服务主机有足够的计算资源,您可以为每个节点分配更多的 CPU 资源,以加快预热过程。 减少预热处理时间。
如果上述网络层和硬件资源和RPC框架不能改变。 我们可以在 SpringBoot 微服务中热身。 上面的答案已经提到了ApplicationReadyEvent
,实际更好的实现是监听ContextRefreshedEvent
事件。 因为 HTTP 端口会在 ApplicationReadyEvent 发生时被初始化和暴露。 在预热完成之前可能会有意外的请求进来。
@Component
public class StartWarmUpListener implements ApplicationListener<ContextRefreshedEvent> {
/**
* Handle an application event.
*
* @param event the event to respond to
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// do something about warm-up here.....
}
}
注意:上面的预热代码并没有预热所有代码。 因为Controller层的请求有一些代码路径,直到HTTP服务器没有准备好才能真正执行。 我们只能在服务层进行代码覆盖。 简而言之,这可能是一种妥协。
Spring Boot应用程序启动时,JVM需要加载各种类进行初始化,导致HTTP请求响应时间较长。
如果想预热http个组件,可以参考:
@Component
public class WarmUpListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// Warm up
}
}
或者试试这个 spring boot starter: warmup-spring-boot-starter ,可以在应用对外提供服务之前预热 HTTP 相关组件,从而减少 HTTP 请求响应时间。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.