在系统生命周期中, 免不了要做升级部署, 对于关键服务, 我们应该能做到不停服务完成升级。另外服务的SLA标准一般都要在四个9以上所以对于优雅停服的需要就十分有必要了。
我们服务用到的技术栈是springboot2.0、springcloud2.0、nacos。一开始我们想到一种方案,在slb配置上所有服务器的健康检查端口,每个项目的健康检查地址修改为不一样,通过域名来转发到每台服务器。方案如下图所示:
如上图就有几个问题:
第一个问题的解决,我们考虑通过脚本定时更新slb(slb有相关api接口)。第二个问题,发版是经常性的操作,有需求发布或者bugfix都需要发版,并不能避免或者减少。就需要引入优雅停机,不影响接口服务,但是还需要操作slb发版前先offline,发版后再online。
看似问题都有解决方案,但是我们既然有了网关,为什么还要多此一举在slb上再维护一套服务器信息,并且发版还需要再维护slb,如果slb有多个或者以后要做迁移就又得修改。后来就考虑健康检查端口与应用端口合并,或者在应用端口提供健康检查接口。经验证两个端口不能合并,那就尝试应用端口提供健康检查接口。就需要翻actuator组件源码 ,网上资料很多,就不在此赘述。
actuator对外输出信息代码主要是以下代码:
于是我们照猫画虎自定义的健康检查接口
或者可以使用调用健康检查接口的形式把数据透传过来
以上方法只适用于微服务的服务模块,对于网关、注册中心并不适用。因为网关不仅在微服务的管理之下,还要挂在slb下面,网关在发版的同时需要维护slb online、offline。具体api接口参考slb文档。
官方提供优雅停机方式:actuator/shutdown,在进行调研停机的时候发现两个问题:
第一个问题是由于应用中的依赖中存在ScheduledExecutor没有被应用上下文关闭,这个对象会使JVM保持存活,需要在代码中显式关闭对应的ScheduledExecutor。很多应用没有显式使用ScheduledExecutor,应该是spring框架内部有依赖,比如ribbon刷新微服务列表就使用到了定时器。去掉spring cloud相关的依赖,再次使shutdown就可以关闭掉进程。
第二个问题由于ribbon获取服务列表机制是通过定时任务拉取,并非注册中心主动通知。使用shutdown在springcloud做不到优雅停机了,就需要另辟蹊径。根据现有状况要做到优雅停机,需要满足两点:
或者使用http://ip:port/service-registry?status=DOWN 进行通知(为方便配置脚本,尽量使用后面这种方式。如果注册中心为eureka,还需要在应用发布后进行UP操作,nacos则无需此操作)
注意此处有坑:
ribbon刷新服务列表默认时间是30s,可以通过参数:ribbon.ServerListRefreshInterval 调整为10s。这样最多10s后每个微服务都能更新到最新的可用服务列表。