Spring boot 2.3优雅下线,距离生产还有多远?

简介: 对于任何一个线上应用,如何在服务更新部署过程当中保证业务无感知是开发者必需要解决的问题,即从应用中止到重启恢复服务这个阶段不能影响正常的业务请求,这使得无损下线成为应用生命周期中必不可少的一个环节。java

前言

在生产环境中,随着云原生架构的发展,自动的弹性伸缩、滚动升级、分批发布等云原生能力让用户享受到了资源、成本、稳定性的最优解。可是在应用的缩容、发布等过程当中,因为实例下线处理得不够优雅,将会致使短暂的服务不可用,短期内业务监控会出现大量 io 异常报错;若是业务没作好事务,那么还会引发数据不一致的问题,那么须要紧急手动订正错误数据;甚至每次发布,您须要发告示停机发布,不然您的用户会出现一段时间服务不可用。没处理好服务实例下线,不管发生上述哪一种状况,都会对您业务的连续性形成困扰。react

对于任何一个线上应用,如何在服务更新部署过程当中保证业务无感知是开发者必需要解决的问题,即从应用中止到重启恢复服务这个阶段不能影响正常的业务请求,这使得无损下线成为应用生命周期中必不可少的一个环节。web

同时在屡次 Dubbo Meetup 中,平滑上下线一直都是位居微服务开发痛点前 Top 3。spring

下面咱们来了解一下 Spring Boot 2.3 中提供的新特性 Graceful Shutdown,来分析一下它对咱们生产稳定性带来什么样的帮助。tomcat

Spring Boot graceful shutdown

Graceful shutdown服务器

Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. When enabled using server.shutdown=graceful, upon shutdown, the web server will no longer permit new requests and will wait for a grace period for active requests to complete. The grace period can be configured using spring.lifecycle.timeout-per-shutdown-phase. Please see the reference documentation for further details.网络

Spring Boot 2.3.0.RELEASE引入了Graceful Shutdown的功能。其中应用在等待下线期间对待新请求的方式,取决于咱们所使用的 Server 类型。根据官方文档Tomcat、Jetty 和 Reactor Netty将会在网络层面中止接收新的请求。Undertow 会继续接收新的请求,但当即会以 HTTP 503(服务不可用)来响应。架构

配置与使用

在Spring Boot 2.3.0中,优雅停机的使用很是简单,能够经过在应用程序配置文件中设置两个属性来进行。
一、 server.shutdown 属性能够支持的值有两种
app

  1. immediate 这是默认值,配置后服务器当即关闭,无优雅停机逻辑。
  2. graceful 开启优雅停机功能,并遵照 spring.lifecycle.timeout-per-shutdown-phase 属性中给出的超时来做为服务端等待的最大时间。
    二、spring.lifecycle.timeout-per-shutdown-phase 服务端等待最大超时时间,采用java.time.Duration格式的值,默认30s。

例如:Properties 文件负载均衡

一、#To enable graceful shutdown

二、server.shutdown=graceful
三、#To configure the timeout period
四、spring.lifecycle.timeout-per-shutdown-phase=20s

当咱们使用了如上配置开启了优雅停机功能,当咱们经过SIGTERM信号关闭 Spring Boot 应用时
一、 此时若是应用中没有正在进行的请求,应用程序将会直接关闭,而无需等待超时时间结束后才关闭。
二、此时若是应用中有正在处理的请求,则应用程序将等待超时时间结束后才会关闭。若是应用在超时时间以后仍然有未处理完的请求,应用程序将抛出异常并继续强制关闭。

源码实现分析

咱们以 Tomcat 为例看一下是SpringBoot 2.3如何实现graceful shutdown的

这里注意下,Tomcat 9.0.33或更高版本,才具有graceful shutdown功能。

咱们看一下 SpringBoot 的 TomcatWebServer 的实现,先看其中构造函数

一、org.springframework.boot.web.embedded.tomcat.TomcatWebServer

二、public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
三、 Assert.notNull(tomcat, "Tomcat Server must not be null");
四、 this.tomcat = tomcat;
五、 this.autoStart = autoStart;
六、 this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
七、 initialize();
八、}





能够看到当咱们配置 server.shutdown=graceful 时,其中 gracefulShutdown 成员就不为null,而是被置为 GracefulShutdown 实例。
当咱们关闭SpringBoot的应用容器时,会触发其生命周期的 stop 方法,咱们看到其中会执行webServer的shutDownGracefully方法
image.png
由于咱们配置 了server.shutdown=graceful ,因此 gracefulShutdown 成员并不为null,而是会触发 gracefulShutdown 的 shutDownGracefully 方法
image.png
咱们看一下shutDownGracefully 方法是如何作到graceful shutdown的
image.png





来看一下doShutdown的逻辑
org.springframework.boot.web.embedded.tomcat.GracefulShutdown#doShutdown
private void doShutdown(GracefulShutdownCallback callback) {

List<Connector> connectors = getConnectors();
connectors.forEach(this::close);
try {
    for (Container host : this.tomcat.getEngine().findChildren()) {
        for (Container context : host.findChildren()) {
            while (isActive(context)) {
                if (this.aborted) {
                    logger.info("Graceful shutdown aborted with one or more requests still active");
                    callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
                    return;
                }
                Thread.sleep(50);
            }
        }
    }

}
catch (InterruptedException ex) {
    Thread.currentThread().interrupt();
}
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);

}

先是关闭掉全部的链接,在网络层中止接受请求,而后再等待全部请求处理完毕。
其中关于 spring.lifecycle.timeout-per-shutdown-phase 配置,是经过等待配置的时间后,再执行TomcatWebServer的stop方法,将其aborted成员置为true,实现若是应用在宽限期以后仍然有待处理的请求,应用程序将抛出异常并继续强制关闭,而不是一直等待下去。
@Override
public void stop() throws WebServerException {


synchronized (this.monitor) {
    boolean wasStarted = this.started;
    try {
        this.started = false;
        try {
            if (this.gracefulShutdown != null) {
                this.gracefulShutdown.abort();
            }
            stopTomcat();
            this.tomcat.destroy();
        }
        catch (LifecycleException ex) {
            // swallow and continue
        }
    }
    catch (Exception ex) {
        throw new WebServerException("Unable to stop embedded Tomcat", ex);
    }
    finally {
        if (wasStarted) {
            containerCounter.decrementAndGet();
        }
    }
}

}

void abort() {

this.aborted = true;

}

在微服务场景下问题彷佛依旧存在...
总结一下一个 Spring Cloud 应用正常分批发布的流程
一、服务发布前,消费者根据负载均衡规则调用服务提供者,业务正常。
二、服务提供者 B 须要发布新版本,先对其中的一个节点进行操做,先是正常中止 Java 进程。
三、服务中止过程当中,首先去注册中心注销服务,而后等待服务端线程处理完成,再中止服务。
四、注册中心则将通知消费者,其中的一个服务提供者节点已下线。这个过程包含推送和轮询两种方式,推送能够认为是准实时的,轮询的耗时由服务消费者轮询间隔决定,最差的状况下须要 1 分钟。
五、服务消费者刷新服务列表,感知到服务提供者已经下线了一个节点,可是这个过程当中Spring Cloud 的负载均衡组件 Ribbon 默认的刷新时间是 30 秒 ,最差状况下须要耗时 30 秒。
六、服务消费者再也不调用已经下线的节点
image.png







咱们看到,当一个Spring Cloud服务端经过SpringBoot提供的graceful shutdown下线时,它会拒绝客户端新的请求,而且等待已经在处理的线程处理完成后,或者在配置的应用最长等待时间到了以后进行下线。

可是在服务端重启开始拒绝客户端新的请求的时刻开始,即执行了Connectors.stop开始,到客户端感知到服务端该实例下线这段时间内,客户端向该实例发起的全部请求都会被拒绝,从而引发服务调用异常。

image.png
若是客户端考虑增长重试能力,这必定程度上能够缓解发布过程当中服务调用报错的问题,可是没法根本上保证下线过程的无损,若是服务调用报错期过程,或者分批发布时候同一批次下线的节点数过多,没法保证仅仅增长屡次重试就可以调用到未下线的节点上。这不能根本解决问题!同时须要考虑配置重试带来的业务上存在不幂等的风险。

EDAS 3.0 无损下线

EDAS 3.0 经过Java Agent技术无侵入加强您的应用,使其具有无损下线能力。
• 您无需修改一行代码与配置,自然具有无侵入特色
• 同时支持 ECS 、K8s 场景
• 全面兼容开源,支持开源Dubbo、Spring Cloud 以及开源微服务网关
image.png



EDAS的应用如何作到无损下线?

image.png
如图看到,咱们经过3个步骤的加强,主动注销、服务提供者通知下线信息、服务消费者调用其余服务提供者。

能够看到,真正作到无损下线能力是须要客户端加强一块儿联动的

• 主动注销
咱们在应用服务下线前,主动通知注册中心注销该实例
• 通知下线信息
咱们会在服务端实例下线前主动通知客户端,该服务节点下线的信息
• 调用其余提供者
咱们在客户端加强其负载均衡能力,在服务端下线后,客户端主动调用其余服务提供者节点
同时咱们提供应用等待的逻辑,使要下线的服务端等待已经收到的请求处理完成再关闭 Spring 容器。





image.png

完整的解决方案

EDAS 3.0无损下线不只仅支持 Spring Cloud 与 Dubbo 服务,咱们还打通了消息、网关等微服务组件,让您的应用在EDAS中作到全链路的下线无损。

EDAS 3.0支持端到端的无损下线

  • 云上客户存在多种微服务网关,支持主流开源微服务网关(Spring Cloud Gateway、Zuul等)的无损下线
  • 有些用户的流量是经过 Ingress、SLB、Nginx 等方式打到服务端的场景
  • MQ消息等异步订阅关系的微服务场景
  • K8s 使用 Service 服务发现的微服务场景
    为了作到全链路的无损下线,EDAS 3.0 经过无侵入的方式涵盖多种场景的完整解决方案,确保您的发布平滑无损。

即便面对白天大流量的场景,发布依旧风轻云淡。

 

原文连接 本文为阿里云原创内容,未经容许不得转载。

相关文章
相关标签/搜索