问题描述
大规模微服务改造后,白天重启或扩缩容服务实例时,系统经常会出现“Can not get to”错误(该错误是微服务框架抛出的异常,表明客户端无法访问该服务)分配.服务器),导致部分用户无法接受服务,影响用户感知。
问题分析
我们生产环境的微服务框架是通过服务注册来发现的。 如下图1所示:
图1:服务注册调用流程图
其主要调用逻辑为:
但是,当服务器实例停止时,由于服务器不会主动更改业务路由器上的注册信息,因此客户端需要40秒(当前应用的会话超时时间配置)来消除此异常配置,并且会在这40秒内应用秒。 它仍然会继续尝试访问这个不存在的实例,从而导致大量的业务错误。
这也与每次实例重启后错误的持续时间一致。 并且由于微服务的特性,每个实际业务请求都会根据业务需求多次调用同一个服务,这就增加了每个业务请求访问异常实例的可能性,加剧了业务失败的概率。
所以这是微服务架构下由于服务实例暴力停止而导致的问题,并因单个业务请求的多次访问而放大的问题。
解决方案
由于这是服务实例暴力关闭导致的问题,因此我们开始研究基于微服务的优雅关闭方法。
微服务架构中应用程序的优雅关闭主要是指应用程序实例有计划地平滑退出(即不进行任何需要处理的动作或产生异常错误)。 主要有两种方式:
由于我们的生产环境没有采用开源通用的微服务架构,并且应用都是基于JAVA开发的,所以我们采用第二种方法:注册JDK(关闭钩子)来实现优雅关闭。
hook本质上是一个线程(也称为Hook线程),用于监控JVM的关闭。 通过的人可以向 JVM 注册一个 hook。 Hook线程只有在JVM正常关闭时才会执行,强制关闭时不会执行。
JVM正常关闭的场景主要有以下几种:
JDK中相关源码如下图2所示:
图2:添加和删除实现
怎样称呼? 使用java.lang..方法,可以注册一个JVM关闭钩子(线程),如下图3所示。 我们希望程序在 JVM 退出时执行的各种完成任务,例如关闭资源、注销服务路由器上的注册信息、等待正在进行的请求处理完成等,都可以添加到该线程中。
图 3:注册 JVM 关闭挂钩的示例
当然,优雅退出需要超时控制机制。 如果达到超时时间,退出前的资源回收等操作仍未完成,则关闭脚本直接调用KILL -9 PID或调用强制关闭进程的代码强制退出。 否则,可能会等待。 长期以来,影响了我们正常的启停操作。
其他注意事项
1、以下场景会直接停止JVM进程。 JVM没有机会执行关闭钩子线程时的清理工作,无法实现正常关闭:
2、hook线程会延迟JVM的关闭时间,所以尽量减少执行时间,控制好超时。
影响
代码优化后,通过测试环境验证和固定场景实际生产演练,容器销毁或正常重启时没有出现“无法到达”的错误,也解决了业务感知问题。 解决了这个问题之后,在大规模微服务架构的场景下,容器的自动自愈、扩缩容等功能就可以再次使用了。
总结
微服务的正常关闭没有最佳解决方案。 只要抓住核心思想并进行设计即可。 如果你使用的框架中有这样的方案,建议直接使用,因为它的适应性肯定是最高的。 在微服务架构中,我们可以遵循以下推荐规则来设计微服务的优雅关闭机制: