优雅停机方案

概述

线上系统发布重启过程中保证业务的连续性,让服务的客户端无感知。实现这种目标的停机我们称之为优雅停机(Graceful Shutdown),这样的发布部署叫平滑发布(Gentle Deploment).

线上重启面临的问题

系统间调用模型
系统间调用模型

常见问题

  • 重启tomcat将导致处理中的请求丢失
  • 使用kill -15重启tomcat,如果服务一直没有成功关闭,将导致不断有新的请求进入并被挂起,最终失败.

解决方案思路

  1. 要做到应用容器停止不影响正在执行的业务.
  2. 负载均衡组件能自动感知服务节点下线和上线,新请求不再分发到旧服务.
  3. 对无法处理的请求尝试重试补偿.

优雅停机相关知识

Linux中断

在Linux/unix 下,中止一个Java进程有两种方式,一种是 kill -9 pid,一种是 kill -15 pill(默认)。

  • SIGNKILL(9) 的效果是立即杀死进程。 该信号不能被阻塞, 处理和忽略。
  • SIGNTERM(15) 的效果是正常退出进程,退出前可以被阻塞或回调处理。并且它是 Linux 缺省的程序中断信号。

ShutdownHook

JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,它是一个回调函数.在一下几种场景中被调用:

  • 程序正常退出
  • 使用System.exit()
  • 终端使用Ctrl+C触发的中断
  • 系统关闭
  • OutOfMemory宕机
  • 使用Kill pid命令干掉进程

SpringCloud对优雅停机的处理机制

SpringBoot ApplicationContext生命周期

SpringBoot Application在启动时注册了ShutdownHook,在doClose()中声明了在ShutdownHook对DisposableBean进行关闭.
org.springframework.context.support.AbstractApplicationContext.registerShutdownHook()
org.springframework.context.support.AbstractApplicationContext.doClose()

Spring Bean生命周期

Spring框架中在bean初始化和销毁时候执行某个方法的三种实现方式。

  • 通过注解@PostConastruct 和 @PreDestroy来实现Bean初始化执行和销毁时候执行方法;
  • 通过实现接口InitializingBean ,DisposableBean来实现Bean初始化执行和销毁时候执行方法;
  • 通过xml配置文件中bean的init-method="" destroy-method=""来实现Bean初始化执行和销毁时候执行方法;

服务注册生命周期

ServiceRegistry声明了spring cloud对服务服务注册的生命周期.
通过AbstractAutoServiceRegistration.destroy()声明了Application关闭时的回调动作.
@PreDestroy
AbstractAutoServiceRegistration.destroy()

EurekaClientAutoConfiguration这个自动配置类做了相应的工作。

Ribbon自动重试

Ribbon针对以下异常自动重试(HttpClientLoadBalancerErrorHandler.retriable )

  • ConnectException.class,
  • SocketTimeoutException.class
  • ConnectTimeoutException.class,
  • NoHttpResponseException.class,
  • ConnectionPoolTimeoutException.class,
  • ConnectionClosedException.class,
  • HttpHostConnectException.class);

服务发现的心跳检测

ConsulCatalogWatch#catalogServicesWatch()声明了每隔一段时间会去注册中心检测服务状态,状态异常的服务节点将在负载均衡时排除掉.

Ribbon负载均衡

Ribbon的负载均衡体现在selectServer时.
在这里插入图片描述

tomcat的响应机制

  • tomcat的Connections和Thread不是直接对于的关系,当线程阻塞或者线程池关闭时,依然可以建立连接.
  • 当线程池正常,线程不够时,如果没有达到最大线程数,将不断新建线程.

Nginx的负载均衡策略

  • 当nginx发现upstream无法连接时,将不再分配请求到该节点
  • 如果可以建立连接,将基于负载均衡策略分发

传统架构的优雅停机方案

使用Nginx做tomcat的负载均衡,使用JDK自带的HttpURLConnection.

方案

  • 使用kill -15,等待30S,使用kill -9
  • 尽量保证所有请求在3S内处理完成
  • 接口调用方要设置超时参数,超时后对异常做处理
  • 接口调用方有选择的通过http重试或者整体任务重试实现完整性

微服务架构的优雅停机方案

使用Feign+Ribbon作为客户端和负载均衡.

方案

  • 使用kill -15,等待10S,使用kill -9
  • 尽量保证所有请求在1S内处理完成
  • 接口调用方要设置超时参数,超时后对异常做处理
  • 缩短服务发现健康检查间隔,建议配置在1S以内(默认1S).

参考资料

  1. 微服务部署与优雅停机
  2. 如何优雅关闭 Spring Boot 应用
  3. Spring Boot 2.0 之优雅停机
  4. nginx反向代理+负载均衡+服务器宕机解决办法
发布了47 篇原创文章 · 获赞 40 · 访问量 8万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章