微服务如何优雅停机
目的
为了解决版本发布时停机造成业务处理异常,如何停机可以使用户无感知,做到7*24进行业务处理
最开始的处理方法:kill -9 java进程id
kill -9 属于强杀进程,首先微服务正在执行的任务被强制中断了;其次,没有通过Eureka注册中心服务下线,Eureka Client仍保存这个服务的路由信息,上游系统会继续调用服务,请求会出现503服务不可用的错误信息
改进之后处理方法:
kill java进程号后给JVM进程发送TERM终止信号时,会调用其注册的 Shutdown Hook,当SpringBoot微服务启动时也注册了 Shutdown Hook,主要关注的Spring的应用上下文抽象类AbstractApplicationContext注册了针对整个Spring容器的Shutdown Hook,在执行Shutdown Hook时的逻辑在 AbstractApplicationContext#doClose()
AbstractApplicationContext#doClose()方法
AbstractApplicationContext#doClose() 的关键点在于
- publishEvent(new ContextClosedEvent(this)): 发送ContextClosedEvent事件,会有对应此事件的Listener处理相应的逻辑
- getLifecycleProcessor().onClose(): 调用所有 Lifecycle bean 的 stop() 方法
而ContextClosedEvent事件的Listener有很多,实现了Lifecycle生命周期接口的bean也很多,但其中我们关心一个,即 EurekaAutoServiceRegistration ,它即监听了ContextClosedEvent事件,也实现了Lifecycle接口
EurekaAutoServiceRegistration的stop()事件
EurekaAutoServiceRegistration中对 ContextClosedEvent事件 和 Lifecycle接口 的实现都调用了stop()方法,虽然都调用了stop()方法,但由于各种对于状态的判断导致不会重复执行,如
- Lifecycle的running标示置为false,就不会调用到此Lifecycle#stop()
- EurekaServiceRegistry#deregister()方法包含将实例状态置为DOWN操作,其中状态置为DOWN一次后,下一次只要状态不变就不会触发状态同步请求
EurekaServiceRegistry#deregister() 注销
主要更新Instance状态为 DOWN: 更新状态会触发StatusChangeListener监听器,状态同步器InstanceInfoReplicator会向Eureka Server发送状态更新请求。实际上状态更新和Eureka Client第一次注册时都是调用的DiscoveryClient.register(),都是发送POST /eureka/apps/appID请求到Eureka Server,只不过请求Body中的Instance实例状态不同。执行完此步骤后,Eureka Server页面该服务状态会变为DOWN。
之后我们加一个ContextClosedEvent实现类将优先级设置为最低
这里duration睡的时间设置的是90s
30s(Eureka Server服务列表刷新缓存ReadOnlyMap的时间,Eureka Client默认读此缓存)
+30s(Eureka Client默认每30秒拉取一次服务列表)
+30s(loadBalance默认动态刷新其ServerList的时间间隔)
=90s
用90s的时间来处理上游系统由于缓存继续发过来的信息,这事件已经是优先级最低,因此EurekaServiceRegistry已经将该服务状态置为DOWN,running状态为false,Eureka Server以及其他服务拉取列表时也不会将此服务拉取,等90s之后再DiscoveryManager.getInstance().shutdownComponent();将服务从Eureka下线,以及this.gracefulShutdownHandler.shutdown();将Undertow容器关闭
注意:不要先将服务从Eureka下线,不然此服务虽然还能还能接收上游系统的请求,但是无法调用Eureka上其他下游系统,因为从Eureka下线后服务列表已经清空了。
- 点赞
- 收藏
- 关注作者
评论(0)