优雅关闭是指在关闭前做必要的操作后再关闭应用程序,不同于简单粗暴直接关闭程序的方法,跟后者比更加文明、优雅,因而得名。
命令行模式:kill pid 发送默认信号:15 SIGTERM
Idea:
eclipse: 暂不支持
docker:docker stop 或 docker-compose stop
下面以Eureka Instance收到关闭指令后的处理流程为例说明Eureka的关闭流程
日志中能看到如下字段
Shutting down DiscoveryClient ...
查看com.netflix.discovery.DiscoveryClient代码:
public class DiscoveryClient implements EurekaClient { @PreDestroy @Override public synchronized void shutdown() { ... } }
会发现有关闭处理注解@PreDestroy
Eureka Server收到Instance cancel register的请求后,立刻执行相应的操作,比如更新连接数、更新租约阈值值。
Eureka Server返回给Eureka Client的值会存在一个缓存里,默认每隔30s(eureka.responseCacheUpdateIntervalMs)更新一下缓存值。
Eureka Client本地也有缓存,默认情况下,Client每隔30s(eureka.client.refresh.interval)跟服务器端同步一下信息。
总结:由于EurekaServer和EurekaClient都有缓存,所以Client获得服务变动消息会有一个延迟。 延迟时间为0 ~ (eureka.responseCacheUpdateIntervalMs + eureka.client.refresh.interval)
以下时关键测试代码,在mac上测试通过,可做参考
Runtime.getRuntime().exec("kill "+pid); //pid 为instance 进程id,环境不同id不同,需要在执行时传入 EurekaClient eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig); Application application = eurekaClient.getApplication("MICROSERVICE-PROVIDER-USER"); if (application == null) { System.err.println("no application:" + i); break; } else { InstanceInfo instance = application.getByInstanceId("discovery:microservice-provider-user:8000"); if (instance == null) { System.err.println("no intance:" + i); break; } else { if (instance.getStatus() == InstanceStatus.DOWN) { System.err.println("intance-- down :" + i); break; } else { System.err.println("intance :" + i + " " + instance.getStatus()); } } }
暴力关闭时,由于缺少通知机制,只能通过租约过期来判断服务是否有效。
默认租约过的时间是90s(eurka.lease.duration),但由于代码上的bug,实际过期时间是其2倍,对应此处为180。默认情况下,客户端获得信息的时间为:2 * eurka.lease.duration ~ 2 * eurka.lease.duration+ eureka.evictionIntervalTimerInMs + eureka.client.refresh.interval
bug对应的官方注释 line:103
Note that due to renew() doing the 'wrong" thing and setting lastUpdateTimestamp to +duration more than what it should be, the expiry will actually be 2 * duration. This is a minor bug and should only affect instances that ungracefully shutdown. Due to possible wide ranging impact to existing usage, this will not be fixed.
dockerfile文件启动jar时如果是直接启动的,不需要做特殊处理;如果是通过shell脚本间接启动的,SIGTERM不会从shell传递到java进程,需要参照这个文档进行修改。
目前我们采用的修改是在java指令前exec,如下所示
exec java ${JAVA_OPTS} -jar *.jar;
根据我们与spring-cloud-netflix开发人员的沟通以及netflix上issues上讨论可以看出官方的态度是暂不考虑对立刻通知的支持。 issues地址如下: https://github.com/spring-cloud/spring-cloud-netflix/issues/1453 https://github.com/Netflix/eureka/issues/820
netflix和spring-cloud官方不支持立刻通知的原因是: nextflix设计的这套微服务方案大量使用客户端故障检测隔离和客户端负载均衡,使得整体架构具备更好的弹性和容错性。 强调高可用性,允许Eureka上存在脏数据,对于无法访问的节点客户端会自动剔除或降权,稍后重试等等,这些机制足以保证正常工作。 服务是否可用或性能如何以服务消费者(客户端)视角为准,而不是以Eureka或Zookeeper为准。
引入下列依赖以获得/pause和/shutdown management endpoints(provided by actuator)
org.springframework.cloud spring-cloud-starter org.springframework.boot spring-boot-starter-actuator
A POST to /pause will result in a status of DOWN in eureka (thanks to cloud-starter)
参考文档
spring-eureka-gently-shutdown-a-service http://stackoverflow.com/questions/32969360/spring-feign-failover