目录
示例:Spring Boot 2.3.0配置Readiness和Liveness
Spring Boot团队最近发布了2.3.0版本,该版本具有许多增强功能,升级功能。(具体可以参考,Spring Boot 2.3.0 发行说明)。
在早些时候,Spring团队就已经宣布2.3版本将专注于Kubernetes。考虑到这一点,本文我们将尝试探索其中一些功能。
背景:容器被广泛使用
众所周知,容器是独立的单元,它是将应用程序及其依赖项打包为一个单元。因此,应用程序可以实现从一个环境快速可靠地迁移运行到另一个环境。*只要不同环境中存在兼容的容器运行时,就可以满足容器的可移植性,。
容器是操作系统虚拟化的一种,它包含运行应用程序所需的一切:可执行二进制文件,类库,配置文件,配置文件和系统库。与服务器虚拟化相比,容器不为每个虚拟环境提供单独的操作系统,容器使用主机上的操作系统。
由于对容器的前所未有的使用,对诸如Kubernetes和Docker Swarm之类的容器编排和管理工具的需求也在增加。这些工具通过许多功能简化了我们管理容器的工作,这些功能包括:
- 容器分组 ( Container grouping )
- 自我修复
- 自动扩展
- DNS管理
- 负载均衡
- 滚动更新或回滚
- 资源监控和记录
几乎所有功能都围绕应用程序(容器)提供以下方面的状态:
- 优雅停机( Gracefully Shutdown )
- 存活状态( Liveness )
- 就绪状态( Readiness )
让我们详细研究它们中的每一个,以及如何在启动Spring Boot 2.3.0中实现它们。
优雅停机( Gracefully Shutdown )
通常,优雅停机意味着在关闭应用程序之前,应设置超时期限,以允许仍在进行中的请求操作完成。在此超时期间,将不允许新请求。这将使你在应用请求处理方面保持一致,即没有未处理请求。
Spring Boot 2.3.0.RELEASE引入了Graceful Shutdown的功能。其中所有四个嵌入式Web服务器(Tomcat,Undertow,Netty和Jetty)都为响应式和基于Servlet的Web应用程序提供优雅停机功能。优雅停机是关闭应用程序上下文的一部分,并且在SmartLifecycle bean停止的最早阶段执行。
应用程序在宽限期内停止新请求的恰当方式,取决于所使用的服务器。根据官方文档Tomcat,Jetty和Reactor Netty将在网络层停止接受请求。Undertow将接受请求,但立即会以HTTP 503(服务不可用)来响应。
请注意,Tomcat 9.0.33或更高版本,才具备优雅停机功能。
在Spring Boot 2.3.0中,优雅停机非常容易实现,并且可以通过在应用程序配置文件中设置两个属性来进行管理。
- server.shutdown:此属性可以支持的值有
- immediate:这是默认值,将导致服务器立即关闭。
- graceful:启用优雅停机,并遵守spring.lifecycle.timeout-per-shutdown-phase属性中给出的超时。
- spring.lifecycle.timeout-per-shutdown-phase:采用java.time.Duration格式的值。
例如:
Properties 文件
# Enable gracefule shutdown server.shutdown=graceful # Allow grace timeout period for 20 seconds spring.lifecycle.timeout-per-shutdown-phase=20s # Force enable health probes. Would be enabled on kubernetes platform by default management.health.probes.enabled=true
现在,当我们配置了优雅停机时,可能会有两种可能性:
- 应用中没有正在进行的要求。在这种情况下,应用程序将会直接关闭,而无需等待宽限期结束后才关闭。
- 如果应用中有正在处理的请求,则应用程序将等待宽限期结束后才能关闭。如果应用在宽限期之后仍然有待处理的请求,应用程序将抛出异常并继续强制关闭。
存活状态( Liveness )
就应用程序而言,存活状态是指应用程序的状态是否正常。如果存活状态不正常,则意味着应用程序本身已损坏,无法恢复。在Kubernetes中,如果存活探针检测失败,则kubelet将杀死Container,并且Container将接受其重新启动策略。如果容器未提供存活探针,则默认状态为“ Success ”。
Spring Boot 2.3.0引入了org.springframework.boot.availability.LivenessState。可用状态为
- CORRECT :该应用程序正在运行,并且其内部状态正常。
- BROKEN:应用程序正在运行,但内部状态被打破。
就绪状态( Readiness )
就绪状态,指的是应用程序是否已准备好接受并处理客户端请求。出于任何原因,如果应用程序尚未准备好处理服务请求,则应将其声明为繁忙,直到能够正常响应请求为止。如果“Readiness”状态尚未就绪,则不应将流量路由到该实例。
例如,在Kubernetes中,如果就绪探针失败,则 Endpoints 控制器将从与Endpoints中删除Pod的IP地址。设置就绪状态为“Failure”。如果容器未提供就绪探针,则默认状态为“Success”。
Spring Boot在ReadinessState的帮助下引入了就绪状态。可以设置的值有:
- ACCEPTING_TRAFFIC:应用程序准备好接收流量。这是默认值。
- REFUSING_TRAFFIC:应用程序拒绝接收流量。
现在,当我们了解概念后,脑海中会有两个问题。
1.如何更新状态?
Spring Boot选择了Spring应用程序事件模型来更改可用性状态。Spring Boot还配置了ApplicationAvailabilityBean类型的Bean,它是ApplicationAvailability接口的实现。该bean监听这些事件并保持最新状态。因此,我们使用ApplicationAvailability来获取应用程序的状态
// Available as a component in the application context ApplicationAvailability availability; LivenessState livenessState = availability.getLivenessState(); ReadinessState readinessState = availability.getReadinessState();
我们还可以使用AvailabilityChangeEvent来更新状态。
2.如何获取应用状态?
最常见的用例是部署支持探针的Web应用程序。Spring boot Actuator是唯一需要的依赖项。因为Spring Boot Actuator已经公开了应用程序运行状况的端点( endpoint )。
启动Spring Boot 2.3.0 Actuator还将在Health指示器中公开可用性状态。这些指标将全部显示在“/actuator/health*”上。这些健康状况端点也可以作为单独的HTTP端点使用:“ /actuator/health/liveness ”和“ /actuator/health/readiness ”。
在Kubernetes平台上运行时,默认情况下这些指标( indicators)包括在actuator/health的endpoint中,而在另一个平台上则不可用。但是,你始终可以通过将management.health.probes.enabled属性设置为true来覆盖此行为。
Properties 文件
# Force enable health probes. Enabled on Kubernetes platform by default management.health.probes.enabled=true
请注意,所有与可用性相关的组件都org.springframework.boot.availability软件包的一部分。
示例:Spring Boot 2.3.0配置Readiness和Liveness
在我们编写代码之前,请记住
- 使用ApplicationAvailability获取应用程序的状态。
- 发布AvailabilityChangeEvent来更新状态。
出于演示的目的,我将在单个RestController中操作,并使用HTTP GET端点调用它们。在理想情况下,这些将由负责管理应用程序不同组件的bean完成。例如缓存,如果缓存失败,则你的缓存bean可以发布ReadinessState.REFUSING_TRAFFIC以拒绝请求到达此应用程序。
代码示例
下面的代码仅用于演示如何根据需要更改状态。不能在生产中使用。
package org.sk.ms.probes; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.LivenessState; import org.springframework.boot.availability.ReadinessState; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * This is sample code to display how to use probes available in spring boot 2.3.0. Not to be used in production. This must be updated by {@link Component} beans for example caching or connection revalidators */ @RestController public class ExampleController { private final Logger logger = org.slf4j.LoggerFactory.getLogger(ExampleController.class); @Autowired private ApplicationEventPublisher eventPublisher; public ExampleController(ApplicationEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } @GetMapping("/complete-normally") public String completeNormally() throws Exception { return "Hello from Controller"; } @GetMapping("/i-will-sleep-for-30sec") public String destroy() throws Exception { logger.info("------------------ Sleeping for 30 sec"); Thread.sleep(30000); return "sleep complete"; } @GetMapping("/readiness/accepting") public String markReadinesAcceptingTraffic() { AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC); return "Readiness marked as ACCEPTING_TRAFFIC"; } @GetMapping("/readiness/refuse") public String markReadinesRefusingTraffic() { AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.REFUSING_TRAFFIC); return "Readiness marked as REFUSING_TRAFFIC"; } @GetMapping("/liveness/correct") public String markLivenessCorrect() { AvailabilityChangeEvent.publish(eventPublisher, this, LivenessState.CORRECT); return "Liveness marked as CORRECT"; } @GetMapping("/liveness/broken") public String markLivenessBroken() { AvailabilityChangeEvent.publish(eventPublisher, this, LivenessState.BROKEN); return "Liveness marked as BROKEN"; } }
测试:Readiness和Liveness
1.检查可用端点
检查可用端点的初始值:
2.宽限期(没有正在进行中的请求)
我们将宽限期设置为20秒。而且没有正在进行中的请求。观察日志。
3.宽限期已过
我们有20秒的宽限期。让我们发出一个需要30秒才能完成的请求,然后尝试停止该应用程序。
在这种情况下,应用程序将因错误而关闭。注意观察日志。
4.将存活状态更新为“BROKEN”,然后将其设置为“CORRECT”
默认情况下,存活状态是由Spring应用程序上下文设置的。
存活状态,一旦标记为 correct ,它将反映在运行状况端点中,并且容器管理器将知道此实例。
例如,你的应用程序依赖于缓存,并且无法刷新缓存,应用程序的存活状态就会被标记为 broken ,容器管理器会基于配置采取适当的措施处理。
5.将就绪状态设置为REFUSING_TRAFFIC,并在恢复后返回ACCEPTING_TRAFFIC
一旦你的应用程序准备好处理请求,可以将就绪状态设置为REFUSING_TRAFFIC。
例如,在缓存更新时,应该将应用程序标记为REFUSING_TRAFFIC,否则可能会处理过时的数据。在这种情况下,容器管理器应停止向该实例发送流量。
但请记住,恢复后应将状态标记回ACCEPTING_TRAFFIC,以便可以将流量再次路由到该实例。
你可以在此GitHub仓库找到以上示例的完整代码。
译文链接: https://dzone.com/articles/configuring-graceful-shutdown-readiness-and-livene