为什么要优雅关闭

上线大致流程如下:
优雅关闭

在重启服务的过程中,发现调用服务1出了问题,RPC 怎么做到让调用方 系统不出问题呢?

把请求发送到正在重启中的机器里面,从而导致调用方不能拿到正确的响应结果。

重启过程中调用方会面临两种情况

  • 目标服务已经下线。对于调用方来说,跟目标节点的连接会断开,这时候调用方可以立马感知到,并且在其健康列表里面会把这个节点挪掉,自然也就不会被负载均衡选中。
  • 目标服务正在关闭。调用方并不知道它正在关闭,而且两者之间的连接也没断开,所以这个节点还会存在健康列表里面,因此该节点就有一定概率会被负载均衡选中。

如何关闭呢

核心是如何能让调用方及时知道要下线的机器是什么,及时从健康列表里删除掉。

通过服务发现去关闭,整个过程中依赖了两次 RPC 调用,一次是服务提供方通知注册中心下线 操作,一次是注册中心通知服务调用方下线节点操作。
注册中心通知服务调用方都是异步 的,我们在“服务发现”一讲中讲过在大规模集群里面,服务发现只保证最终一致性,所以还是很难保证无损。

因为在 RPC 里面调用方跟服务提供方之间是长连接,我们可以在提供方应用内存里面维护一 份调用方连接集合,当服务要关闭的时候,挨个去通知调用方去下线这台机器。这样整个调 用链路就变短了,对于每个调用方来说就一次 RPC,可以确保调用的成功率很高。

出问题请求的时间点跟收到服务提供方关闭通知的时间点很接近,只比关闭 通知的时间早不到 1ms,如果再加上网络传输时间的话,那服务提供方收到请求的时候, 它应该正在处理关闭逻辑。这就说明服务提供方关闭的时候,并没有正确处理关闭后接收到 的新请求。

  • 被动通知;当服务提供方正在关闭,如果这之后还收到了新的业务 请求,服务提供方直接返回一个特定的异常给调用方(比如 ShutdownException) ,调用方收到这个异常响应后,RPC 框架把这个节点从健康列表挪出,并把请求自动重 试到其他节点,因为这个请求是没有被服务提供方处理过,所以可以安全地重试到其他节 点,这样就可以实现对业务无损。
  • 主动通知,没有业务请求,通过加上主动通知流程,这样既可以保证实时性,也可以避免通知失败的情况。

捕获异常如何处理

可以通过捕获操作系统的进程信号来获取,在 Java 语言里面,对应的是 Runtime.addShutdownHook 方法,可以注册关闭的钩子。在 RPC 启动的时候,我们提 前注册关闭钩子,并在里面添加了两个处理程序,一个负责开启关闭标识,一个负责安全关 闭服务对象,服务对象在关闭的时候会通知调用方下线节点。同时需要在我们调用链里面加 上挡板处理器,当新的请求来的时候,会判断关闭标识,如果正在关闭,则抛出特定异常。

正在执行的如何处理

服务对象在关闭过程中,会拒绝新的请求,同时根据引用计数器等待正在处理的请求全部结 束之后才会真正关闭。但考虑到有些业务请求可能处理时间长,或者存在被挂住的情况,为 了避免一直等待造成应用无法正常退出,我们可以在整个 ShutdownHook 里面,加上超 时时间控制,当超过了指定时间没有结束,则强制退出应用。超时时间我建议可以设定成 10s,基本可以确保请求都处理完了。

所以整个关闭过程如下:

优雅关闭

Tomcat 关闭的时候也是先从外层到里层逐层进行关 闭,先保证不接收新请求,然后再处理关闭前收到的请求。

相关文章: