基于eureka注册中心实现服务无损优雅下线和升级

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 无损下线或升级服务步骤

  1. 使用上面第3节的一种方法将服务实例从注册中心摘除
  2. 等待上面第2节所计算的服务下线感知时间(留足够的时间裕量),使服务消费者不再调用该实例的服务,且该实例正常处理进行中的业务
  3. 等待服务实例正在处理的业务处理完成
  4. 正式停止服务实例,可以使用kill -15kill -2,也可以使用spring-boot-actuator/actuator/shutdown接口,不要轻易使用kill -9
  5. (升级)部署发布新版本服务程序

5 eureka集群自身无损扩缩容

采用以下步骤的原理请参考文档:eureka注册中心应用详解

5.1 eureka集群扩容

  1. 通过配置中心(apollo、spring cloud config等)在线修改并生效当前在线的所有eureka server配置,将待扩容节点端点添加到配置项eureka.client.serviceUrl.defaultZone
  2. 部署上线待扩容节点,待扩容节点的配置项eureka.client.serviceUrl.defaultZone包括当前线上运行的所有eureka server节点端点
  3. 新扩容的节点可以添加到eureka client配置中,被eureka client使用了

5.2 eureka集群缩容

缩容节点的步骤与扩容节点的步骤刚好相反

  1. 针对所有配置了待下线节点的eureka client,通过配置中心(apollo、spring cloud config等)在线修改并生效它的配置,将待下线节点端点从配置项eureka.client.serviceUrl中去除
  2. 下线相应的节点
  3. 通过配置中心(apollo、spring cloud config等)在线修改并生效其他在线的所有eureka server配置,将下线节点端点从配置项eureka.client.serviceUrl.defaultZone中去除
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容