做者介绍:三墩SRE,移动改变生活,技术影响将来;个人三墩,个人IT。java
问题描述linux
大规模的微服务改造后,在白天进行服务实例重启或扩缩容时,系统常常出现“Can not get connection to server”报错(该报错是由微服务框架抛出来的异常,表示客户端访问不到分配的服务端),致使部分用户业务受理失败,影响用户感知。服务器
问题分析架构
咱们生产环境的微服务框架是经过Zookeeper进行服务注册发现的。以下图1所示:框架
其主要调用逻辑为:curl
生成容器,服务在容器内启动;ide
注册到服务路由器(Zookeeper);微服务
服务调用者订阅服务路由器;测试
服务路由器上发生注册变更,通知服务调用者从新获取新的注册列表;优化
服务调用者根据获取到的服务端列表,进行服务调用。
可是当服务端实例中止后,因为服务端不会主动去更改服务路由器上的注册信息,客户端须要40秒(目前应用到Zookeeper的会话超时时间配置)才能剔除这个异常配置,在这40秒内应用仍是会不断的尝试访问这个不存在的实例,致使产生大量业务报错。
这也与每次实例重启后,报错的持续时间相符。且因为微服务的特性,每笔实际业务请求,会根据业务须要屡次调用同一个服务,这增长了每笔业务请求访问到异常实例的可能性,加重了业务失败的几率。
因此这是一个因为服务实例暴力中止,并被微服务架构下单笔业务请求屡次访问的倍数放大后,产生的问题。
解决方案
既然这是因为服务实例暴力停机致使的问题,因此咱们开始研究基于微服务的优雅停机方式。
微服务架构中的应用优雅停机,主要是指应用实例有计划而平滑(即不产生须要处理的动做或不产生异常报错)的退出方式。主要有两种方式:
方式一:经过微服务框架自带的检测能力来实现,如在Spring Cloud微服务框架中,提供了actuator组件的/health端点来实现。客户端须要实现一个自定义的HealthCheckHandler,它将应用的健康状态保存到内存中,只需在服务器上利用curl发送shutdown命令,状态一旦发生改变,就会从新向服务器进行注册。
方式二:经过注册JDK的ShutdownHook(钩子)来实现,当系统接收到退出指令后,首先把本身从Zookeeper注册服务器上下线,再也不接收新的消息,而后将积压的请求处理完,最后调用资源回收接口将资源销毁,最后各线程退出执行。
因为咱们生产环境未采用开源通用型微服务架构,且应用都是基于JAVA开发,所以咱们采用的是方式二:经过注册JDK的ShutdownHook(关闭钩子)来实现优雅停机方式。
关闭钩子本质是一个线程(也称为Hook线程),用来监听JVM的关闭。经过Runtime的addShutdownHook能够向JVM注册一个关闭钩子。Hook线程在JVM正常关闭才会执行,强制关闭时不会执行。
JVM正常关闭的场景主要包括以下几种:
Java程序正常运行完退出时会被调用;
终端中经过ctrl-c终止命令时会被调用;
JVM发生OutOfMemory而退出时会被调用;
Java程序中执行System.exit()时会被调用;
操做系统关闭时会被调用;
linux经过kill pid(或者kill -15 pid)结束进程时会被调用。
JDK中ShutdownHook相关的源码以下图2所示:
ShutdownHook如何被调用呢?使用java.lang.Runtime.addShutdownHook方法, 能够注册一个JVM关闭的钩子(线程),以下图3所示。咱们想要程序在JVM退出时作的各类扫尾工做,好比:关闭资源、注销服务路由器上的注册信息、等待在途请求处理完成等均可以在该线程里添加实现。
固然优雅退出须要有超时控制机制,若是到达超时时间仍然没有完成退出前的资源回收等操做,则由停机脚本直接调用KILL -9 PID或调用强制关闭进程的代码方式进行强制退出,否则可能会等待很长时间,影响咱们正常的启停操做。
其它注意事项
一、在如下几种场景下,会直接中止JVM进程,JVM彻底没有机会执行关闭钩子线程中的扫尾工做,没法实现优雅停机:
kill -9(SIGKILL信号);
调用了java.lang.runtime.halt()方法;
主机直接crash;
主机直接关机;
主机内存(或者容器内存)不够,触发操做系统OOM-KILLER。
二、hook线程会延迟JVM的关闭时间,因此尽量减小执行时间,并作好超时控制。
效果
在代码优化后,经过测试环境的验证和固定场景的实际生产演练,容器在正常销毁、重启时,均未出现“Can not get connection to server”的报错,也解决了业务感知问题。解决了该问题后,在大规模微服务架构的场景下,容器的自动化自愈、扩缩容等功能又能够大展身手了。
总结
微服务的优雅停机没有最优的解决方案,只要抓住核心思想进行设计便可。若是使用的框架中有此类解决方法,建议直接使用,其适配性确定是最高的。在微服务架构中,咱们能够遵照如下建议规则来设计微服务的优雅停机机制:
全部微服务应用都应该支持优雅停机;
优先注销注册中心注册的服务实例;
待停机的服务应用的接入点标记拒绝服务;
上游服务支持故障转移因优雅停机而拒绝的服务;
根据具体业务也提供适当的停机接口。
【编辑推荐】
【责任编辑:未丽燕 TEL:(010)68476606】