花肉酱 2018-11-08 967浏览量
众所周知,在微服务架构下面,当应用需要进行新功能升级发布,或者异常关闭重启的时候,我们会对应用的进程进行关闭,而在关闭之前,我们希望做一些诸如关闭数据库连接,等待处理任务完成等操作,这个就涉及到我们本文中的优雅关闭功能。假如应用没有支持优雅停机,则会带来譬如数据丢失,交易中断、文件损坏以及服务未下线等情况。
微服务的优雅停机需要遵循"注销发布服务 → 通知注销服务 → 更新服务清单 → 开启请求屏蔽 → 调用销毁业务服务 → 检查所有请求是否完成 → 超时强制停机"应用服务停机流程。
SOFARPC 提供服务端/客户端优雅关闭功能特性,用来解决 kill PID,应用意外自动退出譬如 System.exit()
退出 JVM,使用脚本或命令方式停止应用等使用场景,避免服务版本迭代上线人工干预的工作量,提高微服务架构的服务高可靠性。
本文将从进程的优雅关闭,SOFARPC 应用服务优雅关闭流程,Netty 的优雅停机等方面出发详细剖析 。
在 Linux上,kill 命令发送指定的信号到相应进程,不指定信号则默认发送 SIGTERM(15) 终止指定进程。如果无法终止,可以发送 SIGKILL(9) 来强制结束进程。kill 命令信号共有64个信号值,其中常用的是:
2(SIGINT:中断,Ctrl+C)。
15(SIGTERM:终止,默认值)。
9(SIGKILL:强制终止)。
这里我们重点说一下15和9的情况。kill PID/kill -15 PID
命令系统发送 SIGTERM 进程信号给响应的应用程序,当应用程序接收到 SIGTERM 信号,可以进行释放相应资源后再停止,此时程序可能仍然继续运行。
而kill -9 PID
命令没有给进程遗留善后处理的条件。应用程序将会被直接终止。
对微服务应用而言其效果等同于突然断电,强行终止可能会导致如下几方面问题:
所以支持优雅关闭的前提是关闭的时候,不能被直接 通过发送信号为9的 Kill 来强制结束。当然,其实我们也可以对外统一暴露应用程序管理的 API 来进行控制。本文暂时不做讨论。
当应用程序收到信号为15的关闭命令时,可以进行相应的响应,Java 程序的优雅停机通常通过注册 JDK 的 ShutdownHook 来实现,当应用系统接收到退出指令,首先 JVM 标记系统当前处于退出状态,不再接收新的消息,然后逐步处理推积的消息,接着调用资源回收接口进行资源销毁,例如内存清理、对象销毁等,最后各线程退出业务逻辑执行。
优雅停机需要超时控制机制,即到达超时时间仍然尚未完成退出前资源回收等操作,则通过停机脚本调用kill-9 PID
命令强制退出进程。
其中 JVM 优雅关闭的 流程主要的阶段如下图所示:
如图所示,Java进程优雅退出流程包括如下五个步骤:
exit()
方法退出 JVM 虚拟机,自动检测用户是否注册ShutdownHook 任务,如果有则触发 ShutdownHook 线程执行自定义资源释放等操作。
在进程可以进行优雅关闭后,SOFARPC 如何实现优雅关闭呢?首先 SOFARPC 对于所有可以被优雅关闭的资源设计com.alipay.sofa.rpc.base.Destroyable
接口,通过向 JVM 的 ShutdownHook 注册来对这些可被销毁的资源进行优雅关闭,支持销毁前和销毁后操作。
这里包括两部分:
运行时上下文注册 JDK 的 ShutdownHook 执行销毁 SOFARPC 运行相关环境实现类似发布平台/用户执行kill PID
优雅停机。运行时上下文 RpcRuntimeContext 静态初始化块注册 ShutdownHook 函数:
static {
...
// 增加jvm关闭事件
if (RpcConfigs.getOrDefaultValue(RpcOptions.JVM_SHUTDOWN_HOOK, true)) {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("SOFA RPC Framework catch JVM shutdown event, Run shutdown hook now.");
}
destroy(false);
}
}, "SOFA-RPC-ShutdownHook"));
}
}
注册本身很简单,重要的是 destroy 方法实际上做的事情非常多。按照先后顺序,大致包含如下几个部分。
RpcRuntimeContext 销毁服务优雅关闭完整流程:
总体设计包含非常多的优雅关闭步骤,这里我们再单独介绍一下作为服务端的时候,几个核心步骤的原理和流程,作为服务端,SOFARPC 关闭服务进程不能直接暴力关闭,而是逐步进行关闭。需要进行如下几个步骤:
RpcRuntimeContext 销毁服务配置资源核心实现入口:
com.alipay.sofa.rpc.context.RpcRuntimeContext#destroy()
作为客户端,SOFARPC 通过实现 DestroyHook 销毁钩子接口提供优雅关闭的钩子,把 GracefulDestroyHook 关闭钩子注册到长连接管理器销毁客户端连接方法。客户端优雅关闭连接实际上是 Cluster 的关闭,关闭调用的服务实现入口:
com.alipay.sofa.rpc.client.AbstractCluster#destroy()
GracefulDestroyHook 钩子优雅关闭连接整体流程:
其中 GracefulDestroyHook 优雅关闭钩子销毁前准备断连操作:
是一个自旋检查的操作。
SOFARPC 在关闭自身 RpcServer 的时候,也会关闭启动的 Netty 服务端。这时候就涉及到 Netty 的优雅关闭。
Netty 作为高性能的异步 NIO 通信框架,负责各种通信协议的接入,解析和调度,SOFABolt 是基于 Netty 最佳实践的轻量、易用、高性能、易扩展的通信框架。当微服务应用进程优雅停机,作为基础通信框架的 Netty 需要考虑优雅停机控制,主要原因包括以下几方面因素:
这里是 Netty底层的实现逻辑,我们只要知道在关闭 Server的时候,需要进行相应的方法调用即可。
可以看到
其中,Netty 的优雅停机核心实现入口:
io.netty.channel.EventLoopGroup#shutdownGracefully()
一个完整的微服务可能不仅仅包括SOFARPC,还可能会用到各种各样的中间件,也涉及到各种流量调度等行为,所以优雅关闭是需要和发布平台联动的。如果强制 kill, 那么目前的这些优雅关闭的方案都不会生效。
所以在后续的 SOFABoot 版本中我们会增加接收一套完整的运维API,方便发布管控平台进行调用。SOFABoot 接收通过接收「关闭运维指令」而不是单纯依赖 ShutdownHook 逻辑,然后触发各个中间件的优雅关闭行为,其中就包括SOFAPRC的主动反注册服务发布和服务调用等关闭动作,各个中间件的优雅关闭执行完成后,SOFABoot 进程再退出。
本文从进程的优雅关闭,到 SOFARPC 的优雅关闭支持,并详细介绍 Netty 优雅关闭的原理。在设计优雅关闭的时候,可以考虑按照如下几个约定来进行实现。
(1)应用能够支持优雅停机
(2)优先注销注册中心注册的服务实例
(3)待停机的服务应用的接入点标记拒绝服务
(4)上游服务支持故障转移因优雅停机而拒绝的服务
(5)根据实际业务场景提供适当的停机接口。
长按关注,获取分布式架构干货
欢迎大家共同打造 SOFAStack https://github.com/alipay
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
集结各类场景实战经验,助你开发运维畅行无忧