前言
SOFABoot 是蚂蚁金服的开源框架, 在原有 Spring Boot 的基础上增强了不少能力, 例如 Readiness Check, 类隔离, 日志空间隔离等能力. 除此之外, SOFABoot 还可以方便的整合 SOFA 技术栈所包含的各类中间件. 如果想要对 SOFABoot 有体感, 可以参考这里快速构建一个 SOFABoot 的应用.
本文来聊聊 SOFABoot 新增的 Readiness 健康检查机制. 主要内容有以下几点:
liveness 和 readiness 的含义和区别
SOFABoot 项目如何使用 readiness 的能力
SOFABoot 是如何实现 readiness 的
一 Liveness 和 Readiness
服务的健康检查, 是微服务的基础能力, 在微服务的运行时期定时地检查服务健康状态, 为熔断降级等提供决策依据. 那么说到健康检查, 这里提出两个概念: liveness 和 readiness. 这两个概念什么意思呢? 有何区别呢? 我们先看看在容器编排领域, k8s 官网是在什么场景下提到这两个词的.
The kubelet uses liveness probes to know when to restart a Container. For example, liveness probes could catch a deadlock, where an application is running, but unable to make progress. Restarting a Container in such a state can help to make the application more available despite bugs.
The kubelet uses readiness probes to know when a Container is ready to start accepting traffic. A Pod is considered ready when all of its Containers are ready. One use of this signal is to control which Pods are used as backends for Services. When a Pod is not ready, it is removed from Service load balancers.
kubelet 用 liveness 探针来检测应用在运行的过程中何时该重启一个容器. 例如, liveness 探针检测到一个应用在运行中陷入死锁状态, 毫无进展, 那么这个时候会重启容器暂时避免这种无解的运行状态, 保持应用的正常运行.
kuelet 用 readiness 探针来检测何时一个容器可以接受业务流量. 当一个 Pod 中的所有容器都准备就绪了, 这个 Pod 才被认为是准备就绪的, 这个时候才会将容器放入到 Service 的负载均衡池中, 对外提供服务.
所以, liveness 的职责是在服务运行期, 已经在跑业务时, 定时检查服务是否正常; 而 readiness 的职责则是在应用服务运行之前, 判断该服务是否准备就绪, 如果服务就绪了, 负载均衡就可以将业务流量引入到该服务了. 服务就绪往往有很多需要判断的, 例如: 各项配置是否加载完毕. 如果这些提供服务前的准备工作未就绪, 这个时候把流量放进来, 就会有大量报错.
二 SOFABoot 项目中使用 Readiness Check
Readiness Check 在 SOFABoot 中是个可选能力, 通过 starter 的方式提供, 如果需要使用, 引入下方依赖即可:
- <dependency>
- <groupId>com.alipay.sofa</groupId>
- <artifactId>healthcheck-sofa-boot-starter</artifactId>
- </dependency>
该 starter 包含了 SpringBoot 的健康检查 spring-boot-starter-actuator.
在应用启动时, 即可启动 Readiness 检查.
三 SOFABoot 如何实现 Readiness Check
我们到 healthcheck-sofa-boot-starter 对应的 spring.factories 文件看看有哪些自定义 bean, 其配置如下:
- org.springframework.context.ApplicationContextInitializer=\
- com.alipay.sofa.healthcheck.initializer.SofaBootHealthCheckInitializer
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- com.alipay.sofa.healthcheck.configuration.SofaBootHealthCheckAutoConfiguration
配置了两个 SOFABoot 的实现类, 一个是用应用初始化组件, 一个是 SOFABoot 健康检查需要的配置类.
1 应用初始化
SofaBootHealthCheckInitializer
这个是 SOFABoot 对于 ApplicationContextInitializer 的实现, 这个接口的主要职责是: 在 springcontext 刷新 (refresh) 之前, 调用该接口的 initialize 做前置的初始化操作, 我们看看 SOFABoot 初始化做了什么事情:
- public class SofaBootHealthCheckInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
- @Override
- public void initialize(ConfigurableApplicationContext applicationContext) {
- Environment environment = applicationContext.getEnvironment();
- if (SOFABootEnvUtils.isSpringCloudBootstrapEnvironment(environment)) {
- return;
- }
- LOGGER.info("SOFABoot HealthCheck Starting!");
- }
- }
初始化的时候, 判断了当前是否为 SpringCloud 的引导上下文, 如果是的话, 则返回, 不打印日志, 如果不是的话, 则打印日志.
这是什么逻辑? 首先, 初始化逻辑里只做了一件事情: 打印日志, 并且是在非 SpringCloud 环境下才打印日志? 其实这是一个兼容逻辑. 在 SpringCloud 环境下, 自定义的 initializer 会被调用两次 initialize 方法 (参考 # issue1151 & # issue 232 https://github.com/sofastack/sofa-boot/issues/232 ),SpringCloud 会加载一个引导上下文(Bootstrap context) 进来, 我们自己的应用程序会加载应用上下文 (application context) 进来, 这两个同时存在, intialzie 会调用两次.
isSpringCloudBootstrapEnvironment 方法就是为了区分是否为 SpringCloud 加载进来的引导上下文, 从而屏蔽掉这次 initialize 执行, 确保日志只会在应用上下文时输出, 该方法主要是通过是否存在 SpringCloud 中的特定类来识别是否引入 SpringCloud, 这里就不赘述了, 读者可自行查看.
2 SOFABoot 实现 Readiness 的核心逻辑
2.1 核心 Bean 介绍
SofaBootHealthCheckAutoConfiguration
要搞清楚实现 Readiness 的核心实现, 我们先看下 SOFABoot 到底装配了哪些 bean, 下面列了一些核心的 bean.
- @Configuration
- public class SofaBootHealthCheckAutoConfiguration {
- @Bean
- public ReadinessCheckListener readinessCheckListener() {
- return new ReadinessCheckListener();
- }
- @Bean
- public HealthCheckerProcessor healthCheckerProcessor() {
- return new HealthCheckerProcessor();
- }
- @Bean
- public HealthIndicatorProcessor healthIndicatorProcessor() {
- return new HealthIndicatorProcessor();
- }
- @Bean
- public AfterReadinessCheckCallbackProcessor afterReadinessCheckCallbackProcessor() {
- return new AfterReadinessCheckCallbackProcessor();
- }
- }
上面一共罗列了 4 个 bean, 一个是应用监听器 ReadinessCheckListener, 这个是入口逻辑, 下文核心逻辑讲解将从这个类开始.
一个是 Readiness 检查完毕的后置处理器 AfterReadinessCheckCallbackProcessor, 这个职责也比较容易理解, 当 Readiness 完成之后, 就会执行去处理逻辑.
另外两个处理器则是健康检查的处理器, HealthCheckerProcessor 是针对 SOFABoot 提供的 HealthChecker 类型的 bean 进行处理, HealthIndicatorProcesso 是针对 SpringBoot 提供的 HealthIndicator 类型的 bean 进行处理.
2.2 核心逻辑解析
ReadinessCheckListener
这个监听器实现了 ApplicationListener, 并监听 ContextRefreshedEvent 事件, 当应用上下文刷新完成后, 触发监听器收到该事件, 执行下面的逻辑.
- // ReadinessCheckListener 接收到刷新事件后的执行逻辑
- public void onApplicationEvent(ContextRefreshedEvent event) {
- if (applicationContext.equals(event.getApplicationContext())) {
- healthCheckerProcessor.init();
- healthIndicatorProcessor.init();
- afterReadinessCheckCallbackProcessor.init();
- readinessHealthCheck();
- readinessCheckFinish = true;
- }
- }
接收到上下文的刷新事件后, 主要做了四件事情, 前面三件是为最后一件事情做准备的:
健康检查处理器初始化, 将上下文中所有 HealthChecker 类型的 bean 都放在 map 中, 等待 Readiness 检查.
健康指标处理器初始化, 将上下文中所有 ReactiveHealthIndicator 类型的 bean 都放在 map 中, 等待 Readiness 检查.
Readiness 检查后置处理器初始化, 将上下文中所有的 ReadinessCheckCallback 类型的 bean 都放在 map 中, 等待 Readiness 检查完毕后调用.
Readiness 健康检查, 前面三步已经准备好了 HealthChecker,ReactiveHealthIndicator 和 ReadinessCheckCallback 的所有 bean, 这一步是真正开始 Readiness 健康检查. Readiness 检查核心逻辑如下:
- public void readinessHealthCheck() {
- if (skipAllCheck()) {
- logger.warn("Skip all readiness health check.");
- } else {
- if (skipComponent()) {
- logger.warn("Skip HealthChecker health check.");
- } else {
- healthCheckerStatus = healthCheckerProcessor
- .readinessHealthCheck(healthCheckerDetails);
- }
- if (skipIndicator()) {
- logger.warn("Skip HealthIndicator health check.");
- } else {
- healthIndicatorStatus = healthIndicatorProcessor
- .readinessHealthCheck(healthIndicatorDetails);
- }
- }
- healthCallbackStatus = afterReadinessCheckCallbackProcessor
- .afterReadinessCheckCallback(healthCallbackDetails);
- if (healthCheckerStatus && healthIndicatorStatus && healthCallbackStatus) {
- logger.info("Readiness check result: success");
- } else {
- logger.error("Readiness check result: fail");
- }
- }
从上面的逻辑, 我们可以看到 HealthChecker 和 HealthIndicator 的处理都是可以基于配置跳过的, 不是必须执行的. 当 HealthChecker,HealthIndicator,ReadinessCheckCallback 对应的处理器都执行成功之后, 打印相应的结果信息.
healthCheckerProcessor 的 readinessHealthCheck 主要是去收集每一个 HealthChecker 的检查结果, 当所有 HealthChecker 的检查结果都为 true 时, 返回 true. 这个过程持续时间比较长, 如果一个 HealtchChecker 返回的结果是 false,processor 会定时重试再去获取其结果, 直到其返回 true 或者重试到最大次数.
healthIndicatorProcessor 的 readinessHealthCheck 逻辑和 healthCheckerProcessor 的类似, 去收集每一个 HealthIndicator 的指标的具体信息, 但持续过程比较短, 无需重试, 执行完成则返回 true.
afterReadinessCheckCallbackProcessor 在 Readiness 检查完毕之后, 逐一去调用所有 ReadinessCheckCallback 类型的 bean, 执行 readiness 的后置处理.
核心逻辑, 其实就是三个不同类型 Bean 的处理器, 去遍历执行各自的 bean 集合, 收集执行结果.
HealthChecker 类型: 这个是 SOFABoot 提供的, 其处理器是 SOFABoot 提供的, 处理器去遍历执行时需要重试获取检查结果.
ReactiveHealthIndicator 类型: 这个是 SpringBoot 本身提供的, 其处理器是 SOFABoot 提供的, 用于处理 SpringBoot 本身的健康检查, 处理器遍历执行时无需重试获取检查结果.
ReadinessCheckCallback 类型: 这个是 SOFABoot 提供的, 其处理器是 SOFABoot 提供的, Readiness 检查后的后置处理 bean.
可以看出, SOFABoot 提供的 HealthChecker 和 SpringBoot 提供的 HealthIndicator 职责有点类似, 但是存在差异性, HealthChecker 中是适合于 Readiness 的, 其实现类指明重试次数 retryCount 和重试间隔 retryTimeInterval, 在应用刚启动时候, 做 Readiness 检查不一定能一次性成功, 那么就需要这种最大重试机制, 所以 HealthChecker 的处理器在 readinessHealthCheck 过程中持续的时间会更长.
- public interface HealthChecker {
- // some method..
- default int getRetryCount() {
- return 0;
- }
- default long getRetryTimeInterval(){
- return 0;
- }
- // some method..
- }
而 SpringBoot 本身提供的是 Liveness 机制, 所以 HealthIndicator 在运行期间, 本身就是一直定时去获取的, 没有最大重试次数, 只要一直在运行, 就要定时去检查.
- public interface HealthIndicator {
- // Return an indication of health.
- Health health();
- }
当然, SOFABoot 在重试完成了 HealthCheck 的健康检查之后, 再完成了一遍 HealthIndicator 的健康检查, 且执行了一遍后置逻辑, 都成功之后, 检查结果才是健康的, 才可以正式对外提供服务.
显然, 要扩展自己的检查指标也是很容易的, 如果是要 Readiness Check 的, 则实现一个 HealthCheck 类, 如果是需要 Liveness Check 的, 则实现一个 HealthIndicator 即可.
四 结语
本文开篇介绍了 SOFABoot 和 SpringBoot 的关系, 在 SpringBoot 的健康检查中, 提供了 Liveness Check 能力, SOFABoot 在此之上新增了 Readiness Check 能力. 通过 starter 的配置一步一步找到其入口逻辑, 并对应用监听器, HealthCheck,HealthIndicator 和 ReadinessCheckCallback 对应的三个处理的逻辑进行了核心解读, 并说明了 HealthCheck 和 HealthIndicator 的区别.
近期文章:
蚂蚁 SOFA 系列(1) - 聊聊 SOFA 的模块化 https://mp.weixin.qq.com/s/yHDYC9H7Fi_VvfwkNdKj8Q
来源: https://www.cnblogs.com/404p/p/11492636.html