实现微服务预热调用之后再开始服务(下)

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 实现微服务预热调用之后再开始服务(下)

继续分析其他接入点。


其他需要初始化的接入点分析


我们有时候还需要做一些自定义的初始化操作,但是如何在注册到注册中心状态为 UP 也就是开始处理请求之前做这些操作呢?

为了更加与云环境兼容,Spring Boot 从 2.3.0 版本之后引入了一些云上部署相关的概念:

  • LivenessState(存活状态):就应用程序而言,存活状态是指应用程序的状态是否正常。如果存活状态不正常,则意味着应用程序本身已损坏,无法恢复。在 k8s 中,如果存活检测失败,则 kubelet 将杀死 Container,并且根据其重新启动策略进行重启:
  • 在 spring boot 中对应的接口是 /actuator/health/liveness
  • 对应的枚举类是 org.springframework.boot.availability.LivenessState,包括下面两个状态:
  • CORRECT:存活状态正常
  • BROKEN:存活状态不正常



  • Readiness(就绪状态):指的是应用程序是否已准备好接受并处理客户端请求。出于任何原因,如果应用程序尚未准备好处理服务请求,则应将其声明为繁忙,直到能够正常响应请求为止。如果 Readiness 状态尚未就绪,则不应将流量路由到该实例。在 k8s 中,如果就绪检测失败,则 Endpoints 控制器将从 Endpoints 中删除这个 Pod 的 IP 地址,如果你没有使用 k8s 的服务发现的话,就不用太关心这个:
  • 在 spring boot 中对应的接口是 /actuator/health/readiness
  • 对应的枚举类是 org.springframework.boot.availability.ReadinessState,包括下面两个状态:
  • ACCEPTING_TRAFFIC:准备好接受请求
  • REFUSING_TRAFFIC:目前不能接受请求了



默认情况下,Spring Boot 在初始化过程中会修改这些状态,对应源码(我们只关心 listeners 相关,这些标志着 Spring Boot 生命周期变化):

SpringApplication.java

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  ConfigurableApplicationContext context = null;
  configureHeadlessProperty();
  SpringApplicationRunListeners listeners = getRunListeners(args);
  //告诉所有 Listener 启动中
  listeners.starting(bootstrapContext, this.mainApplicationClass);
  try {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
    configureIgnoreBeanInfo(environment);
    Banner printedBanner = printBanner(environment);
    context = createApplicationContext();
    context.setApplicationStartup(this.applicationStartup);
    prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
    refreshContext(context);
    afterRefresh(context, applicationArguments);
    stopWatch.stop();
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    }
    //告诉所有 Listener 启动完成
    listeners.started(context);
    //调用各种 SpringRunners + CommandRunners
    callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, listeners);
    throw new IllegalStateException(ex);
  }
  try {
      //通知所有 Listener 运行中
    listeners.running(context);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, null);
    throw new IllegalStateException(ex);
  }
  return context;
}

其中,listeners.startedlisteners.running 里面做的事情是:

@Override
public void started(ConfigurableApplicationContext context) {
    //发布 ApplicationStartedEvent 事件
  context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
  //设置 LivenessState 为 CORRECT
  AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}
@Override
public void running(ConfigurableApplicationContext context) {
    //发布 ApplicationReadyEvent 事件
  context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
  //设置 ReadinessState 为 ACCEPTING_TRAFFIC
  AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}

由于 ApplicationContext 发布事件与订阅该事件的处理是同步进行的,所以如果我们想在 LivenessState 为 CORRECT 之前做点操作,可以监听 ApplicationStartedEvent 事件。同理,想在 ReadinessState 为 ACCEPTING_TRAFFIC 之前做点操作就监听 ApplicationStartedEvent 事件。如何将 LivenessState 还有 ReadinessState 与注册实例到注册中心的状态联系起来呢

我们用的注册中心是 Eureka,注册中心的实例是有状态的。我们的 Eureka Client 的配置是:

eureka:
  client:
    # eureka client 刷新本地缓存时间
    # 默认30s
    # 对于普通属性,用驼峰或者横杠名称配置都可以,这里用的驼峰名称,下面配置用的横杠名称
    registryFetchIntervalSeconds: 5
    healthcheck:
      # 启用健康检查
      enabled: true
    # 定时检查实例信息以及更新本地实例状态的任务的间隔
    instance-info-replication-interval-seconds: 10
    # 初始定时检查实例信息以及更新本地实例状态的任务延迟
    initial-instance-info-replication-interval-seconds: 5

我们启用了 Eureka 的健康检查,其实就是通过调用本地的 /actuator/health 接口的相同服务进行健康检查。这个健康检查,会在定时检查实例信息以及更新本地实例状态的任务中调用。这个任务的初始延迟我们设置为了 10s,之后检查间隔设置为了 5s。健康检查包括存活状态检查还有就绪状态检查,存活状态为 CORRECT 的时候 Status 才为 UP,就绪状态为 ACCEPTING_TRAFFIC 的时候 status 才为 UP。对应的 HealthIndicator 是:

public class ReadinessStateHealthIndicator extends AvailabilityStateHealthIndicator {
  public ReadinessStateHealthIndicator(ApplicationAvailability availability) {
    super(availability, ReadinessState.class, (statusMappings) -> {
        //存活状态为 CORRECT 的时候 Status 才为 UP
      statusMappings.add(ReadinessState.ACCEPTING_TRAFFIC, Status.UP);
      statusMappings.add(ReadinessState.REFUSING_TRAFFIC, Status.OUT_OF_SERVICE);
    });
  }
}
public class ReadinessStateHealthIndicator extends AvailabilityStateHealthIndicator {
  public ReadinessStateHealthIndicator(ApplicationAvailability availability) {
    super(availability, ReadinessState.class, (statusMappings) -> {
        //就绪状态为 ACCEPTING_TRAFFIC 的时候 status 才为 UP
      statusMappings.add(ReadinessState.ACCEPTING_TRAFFIC, Status.UP);
      statusMappings.add(ReadinessState.REFUSING_TRAFFIC, Status.OUT_OF_SERVICE);
    });
  }
}

定时检查实例信息以及实例状态并同步到 Eureka Server 的流程如下


image.png


我们可以使用这个机制,让初始注册到 Eureka 的状态不为 UP,等待存活状态为 CORRECT 的时候并且就绪状态为 ACCEPTING_TRAFFIC 的时候,才会通过上面的定时检查任务将实例状态设置为 UP 同步到 Eureka Server。

可以加上配置,指定初始注册状态:

eureka:
  instance:
    # 初始实例状态
    initial-status: starting

这样我们可以监听 ApplicationStartedEvent 事件实现微服务初始化操作,操作完成后才开始服务。同时还要考虑只执行一次的问题,因为你的 ApplicationContext 不止一个,例如 Spring Cloud 启用 BootStrap Context 之后,就多了一个 BootStrap Context,我们要保证只执行一次的话,可以像下面这么写代码,继承下面这个抽象类集合:

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.springframework.cloud.bootstrap.BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME;
public abstract class AbstractMicronServiceInitializer implements ApplicationListener<ApplicationStartedEvent> {
    private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        if (isBootstrapContext(event)) {
            return;
        }
        //由于spring-cloud的org.springframework.cloud.context.restart.RestartListener导致同一个context触发多次
        //我个人感觉 org.springframework.cloud.context.restart.RestartListener 这个在spring-boot2.0.0之后的spring-cloud版本是没有必要存在的
        //但是官方并没有正面回应,以防之后官方还拿这个做点事情,这里我们做个适配,参考我问的这个issue:https://github.com/spring-cloud/spring-cloud-commons/issues/693
        synchronized (INITIALIZED) {
            if (INITIALIZED.get()) {
                return;
            }
            //每个spring-cloud应用只能初始化一次
            init();
            INITIALIZED.set(true);
        }
    }
    protected abstract void init();
    static boolean isBootstrapContext(ApplicationStartedEvent applicationEvent) {
        return applicationEvent.getApplicationContext().getEnvironment().getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME);
    }
}
相关文章
|
1月前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
3月前
|
Cloud Native Java API
聊聊从单体到微服务架构服务演化过程
本文介绍了从单体应用到微服务再到云原生架构的演进过程。单体应用虽易于搭建和部署,但难以局部更新;面向服务架构(SOA)通过模块化和服务总线提升了组件复用性和分布式部署能力;微服务则进一步实现了服务的独立开发与部署,提高了灵活性;云原生架构则利用容器化、微服务和自动化工具,实现了应用在动态环境中的弹性扩展与高效管理。这一演进体现了软件架构向着更灵活、更高效的方向发展。
|
4月前
|
监控 负载均衡 安全
微服务(五)-服务网关zuul(一)
微服务(五)-服务网关zuul(一)
|
21天前
|
NoSQL 前端开发 测试技术
👀探秘微服务:从零开启网关 SSO 服务搭建之旅
单点登录(Single Sign-On,简称SSO)是一种认证机制,它允许用户只需一次登录就可以访问多个应用程序或系统。本文结合网关和SaToken快速搭建可用的Session管理服务。
75 8
|
2月前
|
弹性计算 持续交付 API
构建高效后端服务:微服务架构的深度解析与实践
在当今快速发展的软件行业中,构建高效、可扩展且易于维护的后端服务是每个技术团队的追求。本文将深入探讨微服务架构的核心概念、设计原则及其在实际项目中的应用,通过具体案例分析,展示如何利用微服务架构解决传统单体应用面临的挑战,提升系统的灵活性和响应速度。我们将从微服务的拆分策略、通信机制、服务发现、配置管理、以及持续集成/持续部署(CI/CD)等方面进行全面剖析,旨在为读者提供一套实用的微服务实施指南。
|
1月前
|
弹性计算 Kubernetes API
构建高效后端服务:微服务架构的深度剖析与实践####
本文深入探讨了微服务架构的核心理念、设计原则及实现策略,旨在为开发者提供一套系统化的方法论,助力其构建灵活、可扩展且易于维护的后端服务体系。通过案例分析与实战经验分享,揭示了微服务在提升开发效率、优化资源利用及增强系统稳定性方面的关键作用。文章首先概述了微服务架构的基本概念,随后详细阐述了其在后端开发中的应用优势与面临的挑战,最后结合具体实例,展示了如何从零开始规划并实施一个基于微服务的后端项目。 ####
|
2月前
|
监控 持续交付 数据库
构建高效的后端服务:微服务架构的深度解析
在现代软件开发中,微服务架构已成为提升系统可扩展性、灵活性和维护性的关键。本文深入探讨了微服务架构的核心概念、设计原则和最佳实践,通过案例分析展示了如何在实际项目中有效地实施微服务策略,以及面临的挑战和解决方案。文章旨在为开发者提供一套完整的指导框架,帮助他们构建出更加高效、稳定的后端服务。
|
3月前
|
Kubernetes 负载均衡 Docker
构建高效后端服务:微服务架构的探索与实践
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于任何在线业务的成功至关重要。本文将深入探讨微服务架构的概念、优势以及如何在实际项目中有效实施。我们将从微服务的基本理念出发,逐步解析其在提高系统可维护性、扩展性和敏捷性方面的作用。通过实际案例分析,揭示微服务架构在不同场景下的应用策略和最佳实践。无论你是后端开发新手还是经验丰富的工程师,本文都将为你提供宝贵的见解和实用的指导。
|
2月前
|
Kubernetes API Docker
构建高效后端服务:微服务架构的深度实践与优化####
本文深入探讨了微服务架构在现代后端开发中的应用,通过剖析其核心概念、设计原则及实施策略,结合具体案例分析,展示了如何有效提升系统的可扩展性、可靠性和维护性。文章还详细阐述了微服务拆分的方法论、服务间通信的最佳实践、以及容器化与编排工具(如Docker和Kubernetes)的应用技巧,为读者提供了一份全面的微服务架构落地指南。 ####
|
3月前
|
监控 API 持续交付
构建高效后端服务:微服务架构的深度探索
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于支撑复杂的业务逻辑和海量数据处理至关重要。本文深入探讨了微服务架构的核心理念、实施策略以及面临的挑战,旨在为开发者提供一套构建高效、可扩展后端服务的方法论。通过案例分析,揭示微服务如何帮助企业应对快速变化的业务需求,同时保持系统的稳定性和灵活性。
54 9