1 思路
当服务要下线或升级时,我们不能简单直接将服务程序kill掉,因为这时可能有服务消费和正在调用服务的接口,导致调用失败。引入了注册中心后,因为服务消费者是从注册中心获取服务的调用地址的,我们可以先将要下线或升级的服务实例人为地从注册中心拿掉或将服务实例状态设置为不可用状态,这时服务实例仍然正常提供服务,等待所有的服务消费者感知到服务实例已经下线(这种方式也叫做主动摘流),而且服务实例正在处理的任务都处理完了,我们就可以安全地kill掉服务实例了。
2 eureka服务消费者感知服务下线的时间
时间计算原理请参考:eureka注册中心应用详解
2.1 服务实例主动下线
2.1.1 使能ReadOnlyCache和loadbalancer cache
感知时间 = T(eureka.server.responseCacheUpdateIntervalMs)
+ T(eureka.client.registryFetchIntervalSeconds)
+ T(spring.cloud.loadbalancer.cache.ttl)
2.1.2 禁用ReadOnlyCache和loadbalancer cache
感知时间 = T(eureka.client.registryFetchIntervalSeconds)
2.2 服务实例被动下线(续约超时剔除)
2.2.1 使能ReadOnlyCache和loadbalancer cache
感知时间 = T(eureka.server.evictionIntervalTimerInMs)
+ T(eureka.server.responseCacheUpdateIntervalMs)
+ T(eureka.client.registryFetchIntervalSeconds)
+ T(spring.cloud.loadbalancer.cache.ttl)
2.2.2 禁用ReadOnlyCache和loadbalancer cache
感知时间 = T(eureka.server.evictionIntervalTimerInMs)
+ T(eureka.client.registryFetchIntervalSeconds)
3 服务实例从eureka注册中心主动摘流方法
3.1 通过spring-boot-actuator
的/actuator/serviceregistry
将服务状态设置为DOWN
因为服务消费者从eureka server只会拿到状态为UP
的服务实例列表,人为地将服务实例状态设置为DOWN
,这样服务消费者就获取不到该服务实例了,也就不会再调用该服务实例了。
spring-boot-actuator
提供了一个/actuator/serviceregistry
接口(低版本spring-boot可能没有这个接口)。这个接口可以获取和设置服务实例的状态。
查询服务实例的注册状态:
GET http://127.0.0.1:8100/actuator/serviceregistry
{
"overriddenStatus": "UNKNOWN",
"status": "UP"
}
将服务实例的状态设置为DOWN
POST http://127.0.0.1:8101/actuator/serviceregistry?status=DOWN
再次查询服务的状态为DOWN
了,且在eureka server上看到的服务实例状态也为DOWN
GET http://127.0.0.1:8100/actuator/serviceregistry
{
"overriddenStatus": "DOWN",
"status": "DOWN"
}
3.2 通过DiscoveryManager shutdown实例
eureka提供了一个DiscoveryManager
类,它的shutdown()
方法可以关闭整个Eureka Client
,并向eureka server主动注销注册。因为只关闭Eureka Client
,服务实例的其它功能均正常运行,可以正常处理业务。这种方式的实现非常简单,完整代码如下:
import com.netflix.discovery.DiscoveryManager;
@RestController
@RequestMapping(value = "/")
public class Controller {
@PutMapping(value = "/shutdown/discovery/client")
public String shutdownDiscoveryClient() {
DiscoveryManager.getInstance().shutdownComponent();
return "success";
}
}
3.3 通过主动强制设置服务实例的状态为DOWN
自己写代码将服务实例状态设置为DOWN
,代码比DiscoveryManager
方式稍微多点,但依然非常简单。
先自定义一个AppEurekaHealthCheckHandler
:
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.cloud.netflix.eureka.EurekaHealthCheckHandler;
import org.springframework.stereotype.Component;
@Component
public class AppEurekaHealthCheckHandler extends EurekaHealthCheckHandler {
private InstanceStatus forceInstanceStatus = null;
public AppEurekaHealthCheckHandler(StatusAggregator statusAggregator) {
super(statusAggregator);
}
public void setInstanceStatus(InstanceStatus forceInstanceStatus) {
this.forceInstanceStatus = forceInstanceStatus;
}
public InstanceStatus getInstanceStatus(InstanceStatus forceInstanceStatus) {
return this.forceInstanceStatus;
}
public void cancelForceInstanceStatus() {
this.forceInstanceStatus = null;
}
public InstanceStatus getStatus(InstanceStatus instanceStatus) {
if (forceInstanceStatus != null) {
return forceInstanceStatus;
}
return super.getHealthStatus();
}
}
然后提供一个接口人工设置服务的状态:
@RestController
@RequestMapping(value = "/")
public class Controller {
private final AppEurekaHealthCheckHandler appEurekaHealthCheckHandler;
public Controller(AppEurekaHealthCheckHandler appEurekaHealthCheckHandler) {
this.appEurekaHealthCheckHandler = appEurekaHealthCheckHandler;
}
@PutMapping(value = "/forced/instance/status")
public String setInstanceStatus(@RequestParam(value = "status") String instanceStatus) {
InstanceStatus status = InstanceStatus.valueOf(instanceStatus);
appEurekaHealthCheckHandler.setInstanceStatus(status);
return "success";
}
@DeleteMapping(value = "/forced/instance/status")
public String cancelInstanceStatus() {
appEurekaHealthCheckHandler.cancelForceInstanceStatus();
return "success";
}
}
4 无损下线或升级服务步骤
- 使用上面第3节的一种方法将服务实例从注册中心摘除
- 等待上面第2节所计算的服务下线感知时间(留足够的时间裕量),使服务消费者不再调用该实例的服务,且该实例正常处理进行中的业务
- 等待服务实例正在处理的业务处理完成
- 正式停止服务实例,可以使用
kill -15
、kill -2
,也可以使用spring-boot-actuator
的/actuator/shutdown
接口,不要轻易使用kill -9
- (升级)部署发布新版本服务程序
5 eureka集群自身无损扩缩容
采用以下步骤的原理请参考文档:eureka注册中心应用详解
5.1 eureka集群扩容
- 通过配置中心(apollo、spring cloud config等)在线修改并生效当前在线的所有eureka server配置,将待扩容节点端点添加到配置项
eureka.client.serviceUrl.defaultZone
中 - 部署上线待扩容节点,待扩容节点的配置项
eureka.client.serviceUrl.defaultZone
包括当前线上运行的所有eureka server节点端点 - 新扩容的节点可以添加到eureka client配置中,被eureka client使用了
5.2 eureka集群缩容
缩容节点的步骤与扩容节点的步骤刚好相反
- 针对所有配置了待下线节点的eureka client,通过配置中心(apollo、spring cloud config等)在线修改并生效它的配置,将待下线节点端点从配置项
eureka.client.serviceUrl
中去除 - 下线相应的节点
- 通过配置中心(apollo、spring cloud config等)在线修改并生效其他在线的所有eureka server配置,将下线节点端点从配置项
eureka.client.serviceUrl.defaultZone
中去除