使用undertow,服务停止后nacos下线注销延迟问题
整体问题是使用gateway作为网关时,服务下线之后。网关依然会转发到下线服务器上。这篇是解决了服务这端的问题,另一篇:spring cloud gateway+nacos 服务下线感知延迟,请求依然转发到下线服务是解决了网关端的问题。
1.场景描述
不太清楚是版本问题还是哪里配置。只是记录一下这次的问题。
nacos客户端:1.4.1
web服务器:undertow 版本 2.2.3.Final
服务停止后,nacos管理端查看服务未及时注销。
web服务器切换为tomcat,服务下线正常注销。
大概原因是因为nacos执行注销时需要的一个bean已经被先行销毁。导致注销失败。控制台有报错信息
2022-04-25 11:48:39.431 ERROR 21336 --- [extShutdownHook] c.a.cloud.nacos.discovery.NacosWatch : 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.metadata-service.urls=[ "dubbo://10.47.17.177:20880/com.alibaba.cloud.dubbo.service.DubboMetadataService?anyhost=true&application=dong-sys-server-biz&bind.ip=10.47.17.177&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dong-sys-server-biz&interface=com.alibaba.cloud.dubbo.service.DubboMetadataService&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs&pid=21336&qos.enable=false&release=2.7.14&revision=2.2.7.RELEASE&service.name=ServiceBean:dong-sys-server-biz/com.alibaba.cloud.dubbo.service.DubboMetadataService:1.0.0&side=provider&timeout=13000×tamp=1650858511190&version=1.0.0" ], dubbo.metadata.revision=0, preserved.register.source=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}
java.lang.NullPointerException: null
at io.undertow.servlet.spec.ServletContextImpl.getInitParameterNames(ServletContextImpl.java:430) ~[undertow-servlet-2.2.3.Final.jar:2.2.3.Final]
at org.springframework.web.context.support.ServletContextPropertySource.getPropertyNames(ServletContextPropertySource.java:41) ~[spring-web-5.3.3.jar:5.3.3]
at com.alibaba.spring.util.PropertySourcesUtils.getPropertyNames(PropertySourcesUtils.java:130) ~[spring-context-support-1.0.11.jar:na]
at com.alibaba.spring.util.PropertySourcesUtils.getSubProperties(PropertySourcesUtils.java:103) ~[spring-context-support-1.0.11.jar:na]
at com.alibaba.spring.util.PropertySourcesUtils.getSubProperties(PropertySourcesUtils.java:57) ~[spring-context-support-1.0.11.jar:na]
at com.alibaba.cloud.nacos.NacosDiscoveryProperties.enrichNacosDiscoveryProperties(NacosDiscoveryProperties.java:616) ~[spring-cloud-starter-alibaba-nacos-discovery-2021.1.jar:2021.1]
at com.alibaba.cloud.nacos.NacosDiscoveryProperties.getNacosProperties(NacosDiscoveryProperties.java:610) ~[spring-cloud-starter-alibaba-nacos-discovery-2021.1.jar:2021.1]
at com.alibaba.cloud.nacos.discovery.NacosWatch.stop(NacosWatch.java:165) [spring-cloud-starter-alibaba-nacos-discovery-2021.1.jar:2021.1]
at com.alibaba.cloud.nacos.discovery.NacosWatch.stop(NacosWatch.java:97) [spring-cloud-starter-alibaba-nacos-discovery-2021.1.jar:2021.1]
at org.springframework.context.support.DefaultLifecycleProcessor.doStop(DefaultLifecycleProcessor.java:234) [spring-context-5.3.3.jar:5.3.3]
at org.springframework.context.support.DefaultLifecycleProcessor.access$300(DefaultLifecycleProcessor.java:54) [spring-context-5.3.3.jar:5.3.3]
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.stop(DefaultLifecycleProcessor.java:373) [spring-context-5.3.3.jar:5.3.3]
at org.springframework.context.support.DefaultLifecycleProcessor.stopBeans(DefaultLifecycleProcessor.java:206) [spring-context-5.3.3.jar:5.3.3]
at org.springframework.context.support.DefaultLifecycleProcessor.onClose(DefaultLifecycleProcessor.java:129) [spring-context-5.3.3.jar:5.3.3]
at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1072) [spring-context-5.3.3.jar:5.3.3]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.doClose(ServletWebServerApplicationContext.java:171) [spring-boot-2.4.2.jar:2.4.2]
at org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:996) [spring-context-5.3.3.jar: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注册中心补偿
* 执行org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#destroy()销毁之前,
* 可能org.springframework.boot.web.embedded.undertow.UndertowWebServer#stop()已经被执行。
* 导致io.undertow.servlet.spec.ServletContextImpl#getInitParameterNames发生NPE,从而无法正常从注册中心下线。
*
*/
@Slf4j
@Component
public class SelfNacosDiscovery {
@Value("${spring.cloud.nacos.discovery.namespace:public}")
private String namespaceId;
private String clusterName = "DEFAULT";
@Value("${spring.application.name:}")
private String serviceName;
@Value("${server.port:8080}")
private String port;
@Value("${spring.cloud.nacos.discovery.server-addr:}")
private String nacosAddr;
@Autowired
private InetUtils inetUtils;
/**
* 补偿注销
* com.alibaba.nacos.client.naming.net.NamingProxy#deregisterService(java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)
*@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);
// com.alibaba.cloud.nacos.NacosDiscoveryProperties.init
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的方式发现服务停止会打印异常。如下:
org.springframework.beans.factory.BeanCreationNotAllowedException: 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 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton
....
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();
}
}