Open Source China ежегодно проводит мероприятие по программному обеспечению с открытым исходным кодом, которое включает два проекта, связанных с SOFA (SOFABoot и SOFARPC). Пожалуйста, нажмите и проголосуйте вместе:Ооо, ооо .OSCHINA.net/project/top.... В то же время, вы также можете обратить вниманиеSOFAStack
Liveness Check & Readiness Check
Spring Boot
Предоставляет базовые возможности проверки работоспособности, а ПО промежуточного слоя и приложения могут быть расширены для реализации собственной логики проверки работоспособности. Но проверка работоспособности Spring Boot — это всего лишьLiveness Check
способности, отсутствиеReadiness Check
способность, это будет иметь более фатальную проблему. Когда приложение микросервиса запускается, необходимо убедиться, что приложение работоспособно после запуска, прежде чем восходящий трафик может быть введен (от RPC, шлюза, запланированных задач и т. д.), в противном случае это может привести к определенному периоду времени. , Случается много ошибок.
противSpring Boot
НедостатокReadiness Check
Положение способности,SOFABoot
повысилсяSpring Boot
Существующие возможности проверки работоспособности, которые обеспечиваютReadiness Check
Способность. использоватьReadiness Check
Способность,SOFA
Каждый компонент промежуточного программного обеспечения доступен только вReadiness Check
После прохождения трафик вводится в экземпляр приложения, напримерRPC
,только вReadiness Check
После прохождения он будет зарегистрирован в реестре сервиса, а трафик от вышестоящего приложения будет поступать позже.
В дополнение к промежуточному ПО можно воспользоватьсяReadiness Check
события для контроля поступления трафика,PAAS
Доступ к системе также возможен черезhttp://localhost:8080/actuator/readiness
чтобы получить заявкуReadiness Check
Статус используется для контроля входящего трафика, такого как устройства балансировки нагрузки.
Как использовать
SOFABoot
Возможность проверки работоспособности должна быть представлена:
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>healthcheck-sofa-boot-starter</artifactId>
</dependency>
отличный отSpringBoot
из:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Подробная инженерная справка:sofa-boot
Журнал запуска проверки работоспособности
анализ кода
Поскольку это Starter, начните сspring.factoriesГлядя на файл:
org.springframework.context.ApplicationContextInitializer=\
com.alipay.sofa.healthcheck.initializer.SofaBootHealthCheckInitializer
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alipay.sofa.healthcheck.configuration.SofaBootHealthCheckAutoConfiguration
SofaBootHealthCheckInitializer
SofaBootHealthCheckInitializer
ДостигнутоApplicationContextInitializer
интерфейс.
ApplicationContextInitializer
даSpring
Первоначальная концепция фреймворка, основной целью этого класса являетсяConfigurableApplicationContext
тип (или подтип)ApplicationContext
Делатьrefresh
Прежде позвольте намConfigurableApplicationContext
instance для дальнейших настроек или обработки.
public class SofaBootHealthCheckInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
Environment environment = applicationContext.getEnvironment();
if (SOFABootEnvUtils.isSpringCloudBootstrapEnvironment(environment)) {
return;
}
// init logging.level.com.alipay.sofa.runtime argument
String healthCheckLogLevelKey = Constants.LOG_LEVEL_PREFIX
+ HealthCheckConstants.SOFABOOT_HEALTH_LOG_SPACE;
SofaBootLogSpaceIsolationInit.initSofaBootLogger(environment, healthCheckLogLevelKey);
SofaBootHealthCheckLoggerFactory.getLogger(SofaBootHealthCheckInitializer.class).info(
"SOFABoot HealthCheck Starting!");
}
}
SofaBootHealthCheckInitializer
существуетinitialize
Метод в основном делает две вещи:
- проверить текущий
environment
так или иначеSpringCloud
(начиная с 3.0.0 поддержкаspringCloud
, в предыдущей версии такого не былоcheck
) - инициализация
logging.level
Эти две вещи не имеют ничего общего с проверками работоспособности, но поскольку они размещены в этом модуле, давайте посмотрим.
1. Проверка среды SpringCloud
Во-первых, почему существует эта проверка.SOFABoot
в поддержкуSpringcLoud
столкнулся с проблемой, когдаclasspath
Добавлятьspring-cloud-context
Во время зависимостей,org.springframework.context.ApplicationContextInitializer
будет вызван дважды. Для получения дополнительной информации см.# issue1151 && # issue 232
private final static String SPRING_CLOUD_MARK_NAME = "org.springframework.cloud.bootstrap.BootstrapConfiguration";
public static boolean isSpringCloudBootstrapEnvironment(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
return !((ConfigurableEnvironment) environment).getPropertySources().contains(
SofaBootInfraConstants.SOFA_BOOTSTRAP)
&& isSpringCloud();
}
return false;
}
public static boolean isSpringCloud() {
return ClassUtils.isPresent(SPRING_CLOUD_MARK_NAME, null);
}
Приведенный выше кодSOFABoot
Предоставляет метод для различения контекста начальной загрузки и контекста приложения:
- Проверьте, есть ли
"org.springframework.cloud.bootstrap.BootstrapConfiguration"
Этот класс, чтобы определить, является ли текущее введениеspingCloud
Класс конфигурации начальной загрузки для - от
environment
получено вMutablePropertySources
пример, проверкаMutablePropertySources
включать лиsofaBootstrap
(если текущая средаSOFA bootstrap environment
, затем содержитsofaBootstrap
; это вSofaBootstrapRunListener
установить в методе обратного вызова)
2. Инициализировать logging.level
вот обработкаSOFABoot
Пространство журнала изолировано.
public static void initSofaBootLogger(Environment environment, String runtimeLogLevelKey) {
// 初始化 logging.path 参数
String loggingPath = environment.getProperty(Constants.LOG_PATH);
if (!StringUtils.isEmpty(loggingPath)) {
System.setProperty(Constants.LOG_PATH, environment.getProperty(Constants.LOG_PATH));
ReportUtil.report("Actual " + Constants.LOG_PATH + " is [ " + loggingPath + " ]");
}
//for example : init logging.level.com.alipay.sofa.runtime argument
String runtimeLogLevelValue = environment.getProperty(runtimeLogLevelKey);
if (runtimeLogLevelValue != null) {
System.setProperty(runtimeLogLevelKey, runtimeLogLevelValue);
}
// init file.encoding
String fileEncoding = environment.getProperty(Constants.LOG_ENCODING_PROP_KEY);
if (!StringUtils.isEmpty(fileEncoding)) {
System.setProperty(Constants.LOG_ENCODING_PROP_KEY, fileEncoding);
}
}
SofaBootHealthCheckAutoConfiguration
Этот классSOFABoot
Реализация автоматической настройки механизма проверки работоспособности.
@Configuration
public class SofaBootHealthCheckAutoConfiguration {
/** ReadinessCheckListener: 容器刷新之后回调 */
@Bean
public ReadinessCheckListener readinessCheckListener() {
return new ReadinessCheckListener();
}
/** HealthCheckerProcessor: HealthChecker处理器 */
@Bean
public HealthCheckerProcessor healthCheckerProcessor() {
return new HealthCheckerProcessor();
}
/** HealthCheckerProcessor: HealthIndicator处理器 */
@Bean
public HealthIndicatorProcessor healthIndicatorProcessor() {
return new HealthIndicatorProcessor();
}
/** AfterReadinessCheckCallbackProcessor: ReadinessCheck之后的回调处理器 */
@Bean
public AfterReadinessCheckCallbackProcessor afterReadinessCheckCallbackProcessor() {
return new AfterReadinessCheckCallbackProcessor();
}
/** 返回 SofaBoot健康检查指标类 实例*/
@Bean
public SofaBootHealthIndicator sofaBootHealthIndicator() {
return new SofaBootHealthIndicator();
}
@ConditionalOnClass(Endpoint.class)
public static class ConditionReadinessEndpointConfiguration {
@Bean
@ConditionalOnEnabledEndpoint
public SofaBootReadinessCheckEndpoint sofaBootReadinessCheckEndpoint() {
return new SofaBootReadinessCheckEndpoint();
}
}
@ConditionalOnClass(Endpoint.class)
public static class ReadinessCheckExtensionConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public ReadinessEndpointWebExtension readinessEndpointWebExtension() {
return new ReadinessEndpointWebExtension();
}
}
}
ReadinessCheckListener
public class ReadinessCheckListener implements PriorityOrdered,
ApplicationListener<ContextRefreshedEvent>
Из кода,ReadinessCheckListener
ДостигнутоApplicationListener
Интерфейс прослушивателя, объект события, который он слушает,ContextRefreshedEvent
, то есть обратный вызов после завершения обновления контекста контейнера.SOFABoot
сделано через этот слушательreadniess check
обработка.
onApplicationEvent
Метод обратного вызова:
public void onApplicationEvent(ContextRefreshedEvent event) {
// healthCheckerProcessor init
healthCheckerProcessor.init();
// healthIndicatorProcessor init
healthIndicatorProcessor.init();
// afterReadinessCheckCallbackProcessor init
afterReadinessCheckCallbackProcessor.init();
// readiness health check execute
readinessHealthCheck();
}
- инициализация
healthCheckerProcessor
, Который является текущим всеHealthChecker
Типbean
узнать и поставитьmap
, ждем следующегоreadiness check
.
public void init() {
// 是否已经初始化了
if (isInitiated.compareAndSet(false, true)) {
// applicationContext 应用上下文不能为null
Assert.notNull(applicationContext, () -> "Application must not be null");
// 获取所有类型是 HealthChecker 的bean
Map<String, HealthChecker> beansOfType = applicationContext
.getBeansOfType(HealthChecker.class);
// 排序
healthCheckers = HealthCheckUtils.sortMapAccordingToValue(beansOfType,
applicationContext.getAutowireCapableBeanFactory());
// 构建日志信息,对应在健康检查日志里面打印出来的是:
// ./logs/health-check/common-default.log:Found 0 HealthChecker implementation
StringBuilder healthCheckInfo = new StringBuilder(512).append("Found ")
.append(healthCheckers.size()).append(" HealthChecker implementation:")
.append(String.join(",", healthCheckers.keySet()));
logger.info(healthCheckInfo.toString());
}
}
- инициализация
healthIndicatorProcessor
, поставить всеhealthIndicator
Типbean
узнать и поставитьmap
ожидающийreadiness check
. если вы хотитеSOFABoot
изReadiness Check
Добавьте в него пункт проверки, тогда вы сможете напрямую развернутьSpring Boot
изHealthIndicator
этот интерфейс.
public void init() {
// 是否已经初始化
if (isInitiated.compareAndSet(false, true)) {
// applicationContext 验证
Assert.notNull(applicationContext, () -> "Application must not be null");
// 获取所有HealthIndicator类型的bean
Map<String, HealthIndicator> beansOfType = applicationContext
.getBeansOfType(HealthIndicator.class);
// 支持 Reactive 方式
if (ClassUtils.isPresent(REACTOR_CLASS, null)) {
applicationContext.getBeansOfType(ReactiveHealthIndicator.class).forEach(
(name, indicator) -> beansOfType.put(name, () -> indicator.health().block()));
}
// 排序
healthIndicators = HealthCheckUtils.sortMapAccordingToValue(beansOfType,
applicationContext.getAutowireCapableBeanFactory());
// 构建日志信息
// Found 2 HealthIndicator implementation:
// sofaBootHealthIndicator, diskSpaceHealthIndicator
StringBuilder healthIndicatorInfo = new StringBuilder(512).append("Found ")
.append(healthIndicators.size()).append(" HealthIndicator implementation:")
.append(String.join(",", healthIndicators.keySet()));
logger.info(healthIndicatorInfo.toString());
}
}
- инициализация
afterReadinessCheckCallbackProcessor
. если вы хотитеReadiness Check
Сделав что-то, его можно продлитьSOFABoot
этого интерфейса
public void init() {
// 是否已经初始化
if (isInitiated.compareAndSet(false, true)) {
// applicationContext 验证
Assert.notNull(applicationContext, () -> "Application must not be null");
// 找到所有 ReadinessCheckCallback 类型的 bean
Map<String, ReadinessCheckCallback> beansOfType = applicationContext
.getBeansOfType(ReadinessCheckCallback.class);
// 排序
readinessCheckCallbacks = HealthCheckUtils.sortMapAccordingToValue(beansOfType,
applicationContext.getAutowireCapableBeanFactory());
// 构建日志
StringBuilder applicationCallbackInfo = new StringBuilder(512).append("Found ")
.append(readinessCheckCallbacks.size())
.append(" ReadinessCheckCallback implementation: ")
.append(String.join(",", beansOfType.keySet()));
logger.info(applicationCallbackInfo.toString());
}
}
-
readinessHealthCheck
, первые несколькоinit
методыreadinessHealthCheck
готовься, иди сюдаSOFABoot
У меня есть то, что у меня есть сейчасHealthChecker
,HealthIndicator
а такжеReadinessCheckCallback
Типbean
Информация.// readiness health check public void readinessHealthCheck() { // 是否跳过所有check,可以通过 com.alipay.sofa.healthcheck.skip.all 配置项配置决定 if (skipAllCheck()) { logger.warn("Skip all readiness health check."); } else { // 是否跳过所有 HealthChecker 类型bean的 readinessHealthCheck, // 可以通过com.alipay.sofa.healthcheck.skip.component配置项配置 if (skipComponent()) { logger.warn("Skip HealthChecker health check."); } else { //HealthChecker 的 readiness check healthCheckerStatus = healthCheckerProcessor .readinessHealthCheck(healthCheckerDetails); } // 是否跳过所有HealthIndicator 类型bean的readinessHealthCheck // 可以通过 com.alipay.sofa.healthcheck.skip.indicator配置项配置 if (skipIndicator()) { logger.warn("Skip HealthIndicator health check."); } else { //HealthIndicator 的 readiness check healthIndicatorStatus = healthIndicatorProcessor .readinessHealthCheck(healthIndicatorDetails); } } // ReadinessCheck 之后的回调函数,做一些后置处理 healthCallbackStatus = afterReadinessCheckCallbackProcessor .afterReadinessCheckCallback(healthCallbackDetails); if (healthCheckerStatus && healthIndicatorStatus && healthCallbackStatus) { logger.info("Readiness check result: success"); } else { logger.error("Readiness check result: fail"); } }
Что делает проверка готовности
спередиSOFABoot
Общий процесс обработки логики проверки работоспособности компонента проверки работоспособности, изученныйReadiness
включая осмотрHealthChecker
Типbean
а такжеHealthIndicator
Типbean
. вHealthIndicator
даSpringBoot
собственный интерфейс, аHealthChecker
даSOFABoot
提供的接口。 Давай продолжимXXXProcess
посмотриReadiness Check
Что именно он сделал?
HealthCheckerProcessor
HealthChecker
процессор проверки работоспособности,readinessHealthCheck
метод
public boolean readinessHealthCheck(Map<String, Health> healthMap) {
Assert.notNull(healthCheckers, "HealthCheckers must not be null.");
logger.info("Begin SOFABoot HealthChecker readiness check.");
boolean result = healthCheckers.entrySet().stream()
.map(entry -> doHealthCheck(entry.getKey(), entry.getValue(), true, healthMap, true))
.reduce(true, BinaryOperators.andBoolean());
if (result) {
logger.info("SOFABoot HealthChecker readiness check result: success.");
} else {
logger.error("SOFABoot HealthChecker readiness check result: failed.");
}
return result;
}
здесь каждыйHealthChecker
порученоdoHealthCheck
проверить
private boolean doHealthCheck(String beanId, HealthChecker healthChecker, boolean isRetry,
Map<String, Health> healthMap, boolean isReadiness) {
Assert.notNull(healthMap, "HealthMap must not be null");
Health health;
boolean result;
int retryCount = 0;
// check 类型 readiness ? liveness
String checkType = isReadiness ? "readiness" : "liveness";
do {
// 获取 Health 对象
health = healthChecker.isHealthy();
// 获取 健康检查状态结果
result = health.getStatus().equals(Status.UP);
if (result) {
logger.info("HealthChecker[{}] {} check success with {} retry.", beanId, checkType,retryCount);
break;
} else {
logger.info("HealthChecker[{}] {} check fail with {} retry.", beanId, checkType,retryCount);
}
// 重试 && 等待
if (isRetry && retryCount < healthChecker.getRetryCount()) {
try {
retryCount += 1;
TimeUnit.MILLISECONDS.sleep(healthChecker.getRetryTimeInterval());
} catch (InterruptedException e) {
logger
.error(
String
.format(
"Exception occurred while sleeping of %d retry HealthChecker[%s] %s check.",
retryCount, beanId, checkType), e);
}
}
} while (isRetry && retryCount < healthChecker.getRetryCount());
// 将当前 实例 bean 的健康检查结果存到结果集healthMap中
healthMap.put(beanId, health);
try {
if (!result) {
logger
.error(
"HealthChecker[{}] {} check fail with {} retry; fail details:{}; strict mode:{}",
beanId, checkType, retryCount,
objectMapper.writeValueAsString(health.getDetails()),
healthChecker.isStrictCheck());
}
} catch (JsonProcessingException ex) {
logger.error(
String.format("Error occurred while doing HealthChecker %s check.", checkType), ex);
}
// 返回健康检查结果
return !healthChecker.isStrictCheck() || result;
}
здесьdoHealthCheck
Результат зависит от конкретногоHealthChecker
Реализация обработки классов. Таким способом вы можетеSOFABoot
Это может быть реализовано очень дружелюбно, поэтомуHealthChecker
проверка здоровья.HealthIndicatorProcessor
изreadinessHealthCheck
а такжеHealthChecker
Основы практически одинаковые, кому интересно, можно прочитать исходный код самостоятельно.Alipay-SOFABoot.
AfterReadinessCheckCallbackProcessor
Этот интерфейсSOFABoot
Предоставляет интерфейс расширения дляReadiness Check
Сделайте что-нибудь после этого. Идеи его реализации и предыдущиеXXXXProcessor
одинакова для всехReadinessCheckCallbacks
примерbean
Обработка обратного вызова выполняется по одному.
public boolean afterReadinessCheckCallback(Map<String, Health> healthMap) {
logger.info("Begin ReadinessCheckCallback readiness check");
Assert.notNull(readinessCheckCallbacks, "ReadinessCheckCallbacks must not be null.");
boolean result = readinessCheckCallbacks.entrySet().stream()
.map(entry -> doHealthCheckCallback(entry.getKey(), entry.getValue(), healthMap))
.reduce(true, BinaryOperators.andBoolean());
if (result) {
logger.info("ReadinessCheckCallback readiness check result: success.");
} else {
logger.error("ReadinessCheckCallback readiness check result: failed.");
}
return result;
}
также делегированdoHealthCheckCallback
иметь дело с
private boolean doHealthCheckCallback(String beanId,
ReadinessCheckCallback readinessCheckCallback,
Map<String, Health> healthMap) {
Assert.notNull(healthMap, () -> "HealthMap must not be null");
boolean result = false;
Health health = null;
try {
health = readinessCheckCallback.onHealthy(applicationContext);
result = health.getStatus().equals(Status.UP);
// print log 省略
} catch (Throwable t) {
// 异常处理
} finally {
// 存入 healthMap
healthMap.put(beanId, health);
}
return result;
}
Расширенные возможности проверки готовности
Согласно приведенному выше анализу, мы можем реализовать эти расширения самостоятельно.
Реализовать интерфейс HealthChecker
@Component
public class GlmapperHealthChecker implements HealthChecker {
@Override
public Health isHealthy() {
// 可以检测数据库连接是否成功
// 可以检测zookeeper是否启动成功
// 可以检测redis客户端是否启动成功
// everything you want ...
if(OK){
return Health.up().build();
}
return Health.down().build();
}
@Override
public String getComponentName() {
// 组件名
return "GlmapperComponent";
}
@Override
public int getRetryCount() {
// 重试次数
return 1;
}
@Override
public long getRetryTimeInterval() {
// 重试间隔
return 0;
}
@Override
public boolean isStrictCheck() {
return false;
}
}
Реализовать интерфейс ReadinessCheckCallback
@Component
public class GlmapperReadinessCheckCallback implements ReadinessCheckCallback {
@Override
public Health onHealthy(ApplicationContext applicationContext) {
Object glmapperHealthChecker = applicationContext.getBean("glmapperHealthChecker");
if (glmapperHealthChecker instanceof GlmapperHealthChecker){
return Health.up().build();
}
return Health.down().build();
}
}
Давайте посмотрим на журнал проверки работоспособности:
Вы можете увидеть тип чека, который мы определяем самиready
.
Из журнала видно, что естьsofaBootHealthIndicator
, выполненоHealthIndicator
интерфейс.
public class SofaBootHealthIndicator implements HealthIndicator {
private static final String CHECK_RESULT_PREFIX = "Middleware";
@Autowired
private HealthCheckerProcessor healthCheckerProcessor;
@Override
public Health health() {
Map<String, Health> healths = new HashMap<>();
// 调用了 healthCheckerProcessor 的 livenessHealthCheck
boolean checkSuccessful = healthCheckerProcessor.livenessHealthCheck(healths);
if (checkSuccessful) {
return Health.up().withDetail(CHECK_RESULT_PREFIX, healths).build();
} else {
return Health.down().withDetail(CHECK_RESULT_PREFIX, healths).build();
}
}
}
livenessHealthCheck
а такжеreadinessHealthCheck
Оба метода даныdoHealthCheck
разобрался, разницы не увидел.
резюме
Эта статья основана наSOFABoot 3.0.0
версия, есть некоторые отличия от предыдущей версии. Подробные изменения см.SOFABoot upgrade_3_x. Эта статья кратко знакомитSOFABoot
правильноSpringBoot
Конкретные сведения о реализации расширения возможности проверки работоспособности.
Наконец, добавьтеliveness
а такжеreadiness
, понимается буквально,liveness
жив ли он,readiness
Это означает, доступен ли он.
-
readiness
: Даже если приложение уже запущено, ему все равно требуется определенное время для предоставления услуг.Это время может быть использовано для загрузки данных, может использоваться для создания кешей, может использоваться для регистрации услуг, может использоваться для выбораLeader
и т.п. во всяком случаеReadiness
Ни один трафик не будет отправлен на приложение, пока проверка не будет передана. В настоящее времяSOFARPC
только что вreadiness check
Только тогда все сервисы будут зарегистрированы в реестре. -
liveness
: определяет, запущено ли приложение