Skip to content

优雅停机-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-clienteureka-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级别日志输出