springboot优雅停机
概述
线上系统发布重启过程中保证业务的连续性,让服务的客户端无感知。实现这种目标的停机我们称之为优雅停机(Graceful Shutdown),这样的发布部署叫平滑发布(Gentle Deploment).
线上重启面临的问题
系统间调用模型
常见问题
- 重启tomcat将导致处理中的请求丢失
- 使用kill -15重启tomcat,如果服务一直没有成功关闭,将导致不断有新的请求进入并被挂起,最终失败.
解决方案思路
- 要做到应用容器停止不影响正在执行的业务.
- 负载均衡组件能自动感知服务节点下线和上线,新请求不再分发到旧服务.
- 对无法处理的请求尝试重试补偿.
优雅停机相关知识
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).
参考资料