使用undertow,服务停止后nacos下线注销延迟问题

时间:2025-03-18 08:31:38

使用undertow,服务停止后nacos下线注销延迟问题

  • 1.场景描述
  • 2.解决方案
    • 2.1web服务器切换为tomcat
    • 2.2做一个自己的注销补偿
    • 2.3使用nacos内置注销(推荐)

整体问题是使用gateway作为网关时,服务下线之后。网关依然会转发到下线服务器上。这篇是解决了服务这端的问题,另一篇:spring cloud gateway+nacos 服务下线感知延迟,请求依然转发到下线服务是解决了网关端的问题。

1.场景描述

不太清楚是版本问题还是哪里配置。只是记录一下这次的问题。

nacos客户端:1.4.1
web服务器:undertow 版本 2.2.

服务停止后,nacos管理端查看服务未及时注销。
web服务器切换为tomcat,服务下线正常注销。
大概原因是因为nacos执行注销时需要的一个bean已经被先行销毁。导致注销失败。控制台有报错信息

2022-04-25 11:48:39.431 ERROR 21336 --- [extShutdownHook]      : namingService unsubscribe failed, properties:NacosDiscoveryProperties{serverAddr='localhost:8848', endpoint='', namespace='dong-dev', watchDelay=30000, logName='', service='dong-sys-server-biz', weight=1.0, clusterName='DEFAULT', group='DEFAULT_GROUP', namingLoadCacheAtStart='false', metadata={=[ "dubbo://10.47.17.177:20880/?anyhost=true&application=dong-sys-server-biz&=10.47.17.177&=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dong-sys-server-biz&interface=&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs&pid=21336&=false&release=2.7.14&revision=2.2.&=ServiceBean:dong-sys-server-biz/:1.0.0&side=provider&timeout=13000&timestamp=1650858511190&version=1.0.0" ], =0, =SPRING_CLOUD, S-Version=10.47.17.177}, registerEnabled=true, ip='10.47.17.177', networkInterface='', port=8082, secure=false, accessKey='', secretKey='', heartBeatInterval=null, heartBeatTimeout=null, ipDeleteTimeout=null}

: null
	at (:430) ~[undertow-servlet-2.2.:2.2.]
	at (:41) ~[spring-web-5.3.:5.3.3]
	at (:130) ~[spring-context-support-1.0.:na]
	at (:103) ~[spring-context-support-1.0.:na]
	at (:57) ~[spring-context-support-1.0.:na]
	at (:616) ~[spring-cloud-starter-alibaba-nacos-discovery-2021.:2021.1]
	at (:610) ~[spring-cloud-starter-alibaba-nacos-discovery-2021.:2021.1]
	at (:165) [spring-cloud-starter-alibaba-nacos-discovery-2021.:2021.1]
	at (:97) [spring-cloud-starter-alibaba-nacos-discovery-2021.:2021.1]
	at (:234) [spring-context-5.3.:5.3.3]
	at $300(:54) [spring-context-5.3.:5.3.3]
	at $(:373) [spring-context-5.3.:5.3.3]
	at (:206) [spring-context-5.3.:5.3.3]
	at (:129) [spring-context-5.3.:5.3.3]
	at (:1072) [spring-context-5.3.:5.3.3]
	at (:171) [spring-boot-2.4.:2.4.2]
	at $(:996) [spring-context-5.3.:5.3.3]

2.解决方案

2.1web服务器切换为tomcat

直接替换,问题解决

2.2做一个自己的注销补偿

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * nacos注册中心补偿
 * 执行#destroy()销毁之前,
 * 可能#stop()已经被执行。
 * 导致#getInitParameterNames发生NPE,从而无法正常从注册中心下线。
 *
 */
@Slf4j
@Component
public class SelfNacosDiscovery {
    @Value("${:public}")
    private String namespaceId;
    private String clusterName = "DEFAULT";
    @Value("${:}")
    private String serviceName;
    @Value("${:8080}")
    private String port;
    @Value("${-addr:}")
    private String nacosAddr;
    @Autowired
    private InetUtils inetUtils;

    /**
     * 补偿注销
     * #deregisterService(, )
     *@param
     *@return
     */
    public void deregisterService(){
        HttpResponse execute = null;
        try{
            if(StrUtil.isAllNotBlank(namespaceId,serviceName,port,nacosAddr)){
                String name = "DEFAULT_GROUP@@"+serviceName;
                String ipAddress = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
                Map<String, Object> params = new HashMap<String, Object>(8);
                params.put("namespaceId", namespaceId);
                params.put("serviceName", name);
                params.put("clusterName", clusterName);
                // 
                params.put("ip", ipAddress);
                params.put("port", port);
                params.put("ephemeral", "true");
                log.info("nacos补偿注销流程:执行器信息namespaceId={},serviceName={},clusterName={},ip={},port={}",namespaceId,
                        name,clusterName,ipAddress,port);
                HttpRequest request = HttpUtil.createRequest(Method.DELETE, nacosAddr + "/nacos/v1/ns/instance");
                execute = request.form(params).execute();
                log.info("nacos补偿注销流程结果:{}",execute.body());
            }else{
                log.warn("nacos补偿注销流程未执行:参数不全!");
            }
        }catch (Exception e){
            log.error("nacos补偿注销流程 异常",e);
        }finally {
            if(execute!=null){
                execute.close();
            }
        }
    }

}

2.3使用nacos内置注销(推荐)

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Slf4j
@Component
public class SelfNacosDiscovery {
    @Resource
    private AbstractAutoServiceRegistration abstractAutoServiceRegistration;

    @EventListener(ContextClosedEvent.class)
    public void doDeregister() {
        log.info("nacos补偿注销流程,开始");
        try {
            abstractAutoServiceRegistration.destroy();
        }catch (Exception e){
        }
        log.info("nacos补偿注销流程,结束");
    }
}

20220609更新
2.3的方式发现服务停止会打印异常。如下:

: Error creating bean with name 'selfNacosDiscovery': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)
	at 
	....

SelfNacosDiscovery 稍作修改即可:

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Slf4j
@Component
public class SelfNacosDiscovery implements ApplicationListener<ContextClosedEvent>{
    @Resource
    private AbstractAutoServiceRegistration abstractAutoServiceRegistration;

    @EventListener(ContextClosedEvent.class)
    public void doDeregister() {
        log.info("nacos补偿注销流程,开始");
        try {
            abstractAutoServiceRegistration.destroy();
        }catch (Exception e){
        }
        log.info("nacos补偿注销流程,结束");
    }
    
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        doDeregister();
    }
}