Appearance
优雅停机-eureka注册中心
1. 优雅停机过程
优雅停机流程:执行stop.sh 停止脚本之后,kill命令执行触发hook通知tomcat执行shutdown触发优雅停机动作,tomcat执行shutdown时,整个线程池等待60秒,在60秒之后不管服务内的线程执行有没有结束都会将服务停止。首先会通知eureka-server将此服务剔除(此时服务还没停止,而且请求还可以正常访问),线程等待30秒后关闭servlet容器。
优雅停机停逻辑:
1.创建定时器,等待停机最大等待时间后关闭进程
2.执行停机前置处理,可业务系统拓展,拓展需实现ShutdownWrapper,默认实现为通知eureka此实例关闭,并等待eureka同步时间后继续
3.根据容器选择不同的执行器进行停机,目前只支持tomcat,undertow两种web容器的停机
注册到eureka-server
中的每个微服务中都有一个eureka-client
,eureka-client
会从eureka服务端获取服务列表信息放到自己的缓存中,默认为30秒(通过配置RegistryFetchIntervalSeconds
可修改)。每个服务中Ribbon的服务列表信息默认是定时任务从当前服务的eureka-client
的缓存中获取然后存到自己的缓存信息中,在每次做负载选取节点时Ribbon从自己的缓存中获取服务的可用节点信息。
在优雅停机时,可能存在服务已经停止了,但是Ribbon中的实例节点信息还没刷新过来,负载节点中存在已经停止的服务节点,导致连接失效报错。所以配置优雅停机的时候应该注意client端和Ribbon中的实例节点的同步信息的总时间加起来要比配置的register-sync-time-seconds时间小。
Ribbon的缓存刷新时间可通过配置:ribbon.ServerListRefreshInterval
去修改ribbon中的服务列表获取时间。
2. 优雅停机错误定位
(1)检查配置和停止脚本
优雅停机配置:
yaml
galaxy:
#优雅停机属性
shutdown:
#开关,缺省:false
enable: true
#最大等待线程池执行时间,单位:秒
maxTimeoutWaitSeconds: 60
#通知eureka下线,等待注册信息同步时间,单位:秒
registerSyncTimeSeconds: 30
停止脚本不能出现kill -9
命令,目前有些服务的停止脚本中可能会写执行kill
命令后等待20秒,若20秒后进程还在,执行kill -9
命令,这样是错误的。需要将此等待时间修改为与优雅停机最大等待线程池执行时间相同。
(2)请求经过网关直接响应码为500
可能原因:网关负载均衡ribbon缓存中的节点信息没有刷新过来,ribbon中同步节点数据的时间默认是30秒一次。
Ribbon是一种客户端的负载均衡,本质上是跑在服务消费者的进程里。服务消费者要访问服务时,通过ribbon向一个服务注册的列表查询,然后以配置的负载均衡策略选择一个后端服务发起请求。
在ribbon中这个服务注册列表其实就是eureka服务端集中管理的注册服务列表。获取这个列表应该就是是通过eureka的client来完成的。
通过分析ribbon源码可知,ribbon默认实在DynamicServerListLoadBalancer类中实现动态的加载后端服务列表。
DynamicServerListLoadBalancer中使用一个ServerListRefreshExecutorThread任务线程定期的更新后端服务列表。
注意:此处对于源码只是展示要用到的日志输出的位置,具体流程可自行阅读源码
java
class ServerListRefreshExecutorThread implements Runnable {
public void run() {
updateListOfServers();
}
}
//同步节点实例信息方法
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
servers = serverListImpl.getUpdatedListOfServers();//此发放的具体实现类DiscoveryEnabledNIWSServerList
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
updateAllServerList(servers);
}
在上述DynamicServerListLoadBalancer类的updateListOfServers()方法中可以看到同步节点信息的实例日志输出,所以在报错时可在网关中添加如下配置,开启日志输出查看节点是否刷新过来:
yaml
logging:
config: classpath:logback-spring.xml
level:
root: INFO
com.dcits.jupiter.gateway.filter.GatewayLoadBalancerClientFilter: INFO #负载均衡过滤器日志输出,可以打印出当前选择的服务节点ip+port
com.netflix.loadbalancer.DynamicServerListLoadBalancer: debug #负载均衡节点同步信息日志,优雅停机排错的时候可开启查看
根据日志中输出的ribbon缓存中服务节点数据和当前请求的负载到的节点ip+port确认是否是节点数据没有刷新,仍然负载到被停机的节点中。
注意:当触发优雅停机时,服务在等待的30秒内是等待停机状态,进程实际还在,只不过是eureka上的服务被剔除了,所以刚开始ribbon中的节点数据有此节点是正常的,此时此节点依然可用。当服务等待30秒后开始停机之后,要保证ribbon中的节点已经被刷新过来,此时就可通过上述的日志配置来进行判断。 **
若节点数据在30秒等待时间内没有刷新,可在网关中添加如下配置,将同步时间修改为5秒一次:
yaml
ribbon:
ServerListRefreshInterval: 5000 #ribbon 从eureka client同步节点数据的时间,5秒一次
(3)服务间RPC调用
业务中一般使用的feign调用,查看feign调用日志,当前选择的节点信息,在业务服务中开启com.dcits.jupiter.feign.loadbalance.HotLoadBalancerFeignClient 类的debug级别日志输出即可,jupiter-fegin-cloud从3.3.5之后的版本添加此日志输出,之前可能不存在。
所有服务节点获取的日志与(2)中相同,开启类
com.netflix.loadbalancer.DynamicServerListLoadBalancer的 debug级别日志输出