1. 背景
原来的系统是个单体服务,导致逻辑越来越复杂,牵一发而动全身。为了提高系统的可扩展性,我们把原来的单体系统,按照功能拆分成不同的微服务。
2. 弹性云配置
我们所有的微服务都是部署在弹性云上的,希望在部署服务时能够做到无损发布。要做到这一点,以下几个步骤是需要实现的:
- 容器销毁之前服务进程能够主动从eureka注册中心列表中删除;
- 在eureka注册中心列表删除实例后,该实例在一定的时间内还要能够承接一些流量,因为此时其他eureka客户端还有该实例的缓存;
- 最后等待其他线程全部处理完成后,再销毁容器。
下面看下如何实现上面的需求。
2.1 eureka主动下线方式
有以下几种eureka注册中心服务下线的方式:
-
直接kill服务
这种方式简单粗暴,但是在这种情况下,虽然客户端已经停止服务了,但是仍然存在于注册中心列表中,会造成部分模块调用时出错,所以这个方案pass。
-
向Eureka service发送delete请求
http://{eureka-server:port}/eureka/apps/{application.name}/{instance.name}
这种方案只是取消注册服务,但是当eureka服务再一次接收到心跳请求时,会重新把这个实例注册到eureka上,所以这个方案也pass了。
-
客户端通知Eureka service下线
DiscoveryManager.getInstance().shutdownComponent();
eureka客户端可以通过上面一行代码主动通知注册中心下线,下线后也不会再注册到eureka上,这个方案符合我们的要求,但是我们需要确认这行代码需要在什么时候被调用?
2.2 下线时机
在这里我们首先需要确定从eureka注册中心删除实例的时机,有以下几种想法:
1. 自定义controller接口
@GetMapping("/shutdown")
public void shutdown() {
DiscoveryManager.getInstance().shutdownComponent();
}
在容器部署之前,先调用此接口下线,然后再执行部署操作。但是这样做有很大的弊端:1. 该接口不能暴露出去,同时为了避免其他人恶意调用,还需要加一些鉴权操作;2. 无法集成到部署脚本中,因为和弹性云团队的同学了解到,容器销毁前并不会执行control.sh里的stop方法,而是发送一个SIGTERM信号,所以没办法将该接口调用写到部署脚本中。因此如果采用这种方式,只能每个容器上线前手动调用该接口,风险太大,因为此方案不合适。
2. 自定义Shutdown Hook
Runtime.getRuntime().addShutdownHook(new Thread(()