微服务(Nacos、eureka、consul)优雅上下线方案汇总

2020/1/1

点击勘误issues (opens new window),哪吒感谢大家的阅读

# 微服务(Nacos、eureka、consul)优雅上下线方案汇总

# 前提

必须是集群,多个实例

# 优雅的本质

一个服务有多个(如A、B 两个实例)实例提供服务时,客户端请求服务时会访问此服务的多个实例,当进行A实例进行部署时将流量切换到B实例,然后再关闭或重新发布 ;如何实现这个功能呢?

所以本质就是模拟人工在控制台触发“下线”功能(此实例还是提供服务),过一会儿等流量全部切到其他实例再部署&Kill实例;可以理解为此实例说:“我即将要先下线了,请访问另外实例”。

# 为什么要过一会儿?

可能客户端缓存的服务提供的实例Ip列表导致无效的请求或服务挂掉后瞬间的访问不到服务的情况

img_9.png

在部署脚本里面植入1、下线脚本;2 增加缓冲时间(如60s),默认Nacos客户端缓存实例列表是30s;

# 方案1 注册中心自带的方法

我们使用的这个方式,简单无需配置,默认支持;

下线

curl -X PUT "http://nacos服务ip:port/nacos/v1/ns/instance?serviceName=服务名&ip=172.25.135.221&port=8667&namespaceId=preprod&weight=0&enabled=false"

上线

curl -X PUT "http:/nacos服务ip:port/nacos/v1/ns/instance?serviceName=服务名&ip=172.25.135.221&port=8667&namespaceId=preprod&weight=1&enabled=true"

# 方案2 基于/service-registry端点

如果应用支持Spring Cloud部署那就更好了。Spring Cloud提供了/service-registry端点。但从名字就可以知道专门针对服务注册实现的一个端点。

在配置文件中开启/service-registry端点:

management:
  endpoints:
    web:
      exposure:
        include: service-registry
      base-path: /actuator
  endpoint:
    serviceregistry:
      enabled: true

访问http://localhost:8667/actuator 端点可以查看到开启了如下端点:

{
    "_links": {
        "self": {
            "href": "http://localhost:8667/actuator",
            "templated": false
        },
        "serviceregistry": {
            "href": "http://localhost:8667/actuator/service-registry",
            "templated": false
        }
    }
}

通过curl命令来进行服务状态的修改:上线是UP,下线是DOWN

curl -X "POST" "http://172.25.129.191:8667/actuator/service-registry?status=DOWN" -H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"

执行上述命令之前,查看Nacos对应实例状态为:

img_10.png

比方案1 多了一个步骤 需要配置一下开启一下才行;

其本质就是如下代码,去关闭了Nacos控制的状态

@Endpoint(id = "service-registry")
public class ServiceRegistryEndpoint {
 
    private final ServiceRegistry serviceRegistry;
 
    private Registration registration;
 
    public ServiceRegistryEndpoint(ServiceRegistry<?> serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }
 
    public void setRegistration(Registration registration) {
        this.registration = registration;
    }
 
    @WriteOperation
    public ResponseEntity<?> setStatus(String status) {
        Assert.notNull(status, "status may not by null");
 
        if (this.registration == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body("no registration found");
        }
 
        this.serviceRegistry.setStatus(this.registration, status);
        return ResponseEntity.ok().build();
    }
 
    @ReadOperation
    public ResponseEntity getStatus() {
        if (this.registration == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body("no registration found");
        }
 
        return ResponseEntity.ok()
                .body(this.serviceRegistry.getStatus(this.registration));
    }
 
}

调用的Endpoint便是通过上面代码实现的。所以不仅Nacos,只要基于Spring Cloud集成的注册中心,本质上都是支持这种方式的服务下线的。

# 方案3 个性化定义下线方式,本质还是调用方案2的

不需要配置,实现钩子方法即可

在实例启动的时默认都会进行注册实例到注册中心,如下官方实现方式:NacosServiceRegistryAutoConfiguration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
        matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
        AutoServiceRegistrationAutoConfiguration.class,
        NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {

    @Bean
    public NacosServiceRegistry nacosServiceRegistry(
            NacosDiscoveryProperties nacosDiscoveryProperties) {
        return new NacosServiceRegistry(nacosDiscoveryProperties);
    }

    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosRegistration nacosRegistration(
            NacosDiscoveryProperties nacosDiscoveryProperties,
            ApplicationContext context) {
        return new NacosRegistration(nacosDiscoveryProperties, context);
    }

    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosAutoServiceRegistration nacosAutoServiceRegistration(
            NacosServiceRegistry registry,
            AutoServiceRegistrationProperties autoServiceRegistrationProperties,
            NacosRegistration registration) {
        return new NacosAutoServiceRegistration(registry,
                autoServiceRegistrationProperties, registration);
    }

}

你只需要知道如何拿到注册实例即可,默认就可以获取,实现自己的对外接口即可

@Resource
private NacosServiceRegistry nacosServiceRegistry;
@Resource
private NacosRegistration nacosRegistration;

@GetMapping(value = "/api/nacos/{status}")
public String deregisterInstanceStatus(@PathVariable String status) {
    if (!status.equalsIgnoreCase("UP") && !status.equalsIgnoreCase("DOWN")) {
        log.warn("can't support status {},please choose UP or DOWN", status);
        return "please choose UP or DOWN,can't support status: "+status;
    }
    try {
        nacosServiceRegistry.setStatus(nacosRegistration,status);
    } catch (Exception e) {
        log.error(" deregisterInstanceStatus nacos error", e);
        return "error:"+e.getMessage();
    }
    return "success";
}