背景

在高并发场景下,服务部署做到客户端无感知是开发者必须解决的问题,也就是说从应用停止到重启恢复这个阶段不能影响业务的正常运行。
传统的解决方式是:手工切断流量,然后过一段时间后停机重启。虽然行之有效,但限制较多:需要借助网关支持来断流量,还需要人工判断等多久应用处理完毕(可能过早导致应用没处理完,或过晚浪费时间)。这种方式只适用小规模,无法大规模项目上应用。
因此,如果容器、框架提供某种自动化机制来自动断流并在处理完毕和后置处理后再停机重启,不仅能保证业务稳定不受影响,还可极大提高运维效率。
这个自动化机制就叫做优雅停机,目前tomcat/undertow/dubbot等容器都提供相应实现。

优雅停机是指在停止应用时,执行的一系列保证应用正常关闭的操作。 这些操作往往包括等待已有请求执行完成、关闭线程、关闭连接和释放资源等,优雅停机可以避免非正常关闭程序可能造成数据异常或丢失,应用异常等问题。 优雅停机本质上是JVM即将关闭前执行的一些额外的处理代码。

jvm关闭分类和安全退出

适用场景

  • jvm主动关闭 (System.exit)
  • jvm oom等退出
  • 应用程序收到SIGTERM或SIGINT信号

原理

dubbo的优雅停机

需要解决的问题
  1. 正在停机的提供者 不再接收请求
  2. 关闭提供者,已接收的请求,需要处理完且返回才能下线
  3. 关闭消费者,已发出的请求,需要待响应返回,后处理完毕,才能下线。

停机原理

基于java的ShutdownHook来实现的,如果用户执行kill-9 是无法触发优雅停机的。

服务提供方:
  1. 停机时,先标记提供者不再接收新请求,新请求过来时,直接报错,让客户端重试其他机器
  2. 然后检测dubbo线程池中线程是否正在运行,如果有,等待所有线程执行完毕,若超时强制关闭

    服务消费方
  3. 停止时,不再发起新的调用请求,所有新的调用在客户端报错,

  4. 然后,检测有没有正在请求的响应还没有返回的,等其响应返回,超时时强制关闭

设置方式
  1. dubbo.service.shutdown.wait=15000

如果ShutdownHook不能生效,可调用DubboShutdownHook.destroyAll();

DubboShutdownHook.destroyAll()

image.png

  1. AbstractRegistryFactory.destroyAll();

首先 destroy所有的注册中心,销毁所有InvokerExporter

  1. destroyProtocols();

注销所有的协议

  1. 关闭Server , 向所有已连接Client发送当前Server只读事件;

首先将会调用 HeaderExchangeServer#close

  1. 关闭独享/共享Client,断开连接,取消超时和重试任务;
  2. 释放所有相关资源。

与spring框架

dubbo框架一般与spring一起使用,如果dubbo正在执行优雅停机,而spring容器已完全卸载,则可能导致优雅停机失效,因此需要将AbstractRegistryFactory``.``_destroyAll_``(),注册到springBean容器销毁的监听中。
而新的2.7.X版本新增 ShutdownHookListener,继承 Spring ApplicationListener 接口,用以监听 Spring 相关事件。当Spring容器开始关闭时,会触发ShutdownHookListener内部优雅停机逻辑。

SpringBoot优雅停机

配置 server.shutdown=graceful 时表示开启优雅停机,默认是IMMEDIATE立即关机。web窗口关闭时,web服务器将不再接收新请求,并将等待活动请求完成的缓冲期。
timeout-per-shutdown-phase=30 默认缓冲30s, 超时以后无论线程任务是否执行完毕都会停机处理。