Nacos添加Netty后启动失败

项目场景

Nacos作为配置中心,添加Netty并启动后,Nacos管理端修改配置后,客户端未感知到变化。


问题描述

  1. 引入相应版本依赖
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.38.Final </version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
  1. 通过如下方式启动Netty:
@SpringBootApplication
public class Application implements ApplicationRunner {
    public static void main(String[] args)  {
        SpringApplication.run(Application.class,args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        server.start();//启动Netty
    }
    @Autowired
    EchoServer server;
}
  1. 添加如下测试配置参数的代码:
@RestController
@RequestMapping("/test")
@Slf4j
@RefreshScope
public class TestController {
    @Value("${address.port}")
    String port;

    @GetMapping("/port")
    public String get(){
        return port;
    }
}
  1. 在Nacos管理端修改address.port参数后,访问/test/port无变化。

原因分析

Nacos是通过NacosContextRefresher来注册监听器,进而监听服务端dataId的配置信息的变化,并更新客户端内存中的数据

分析过程

  1. SpringApplication.run在启动容器完成时,会调用listeners.ready(...)发布ApplicationStartedEvent事件,源码如下:
//步骤一 SpringApplicaiton.java
public ConfigurableApplicationContext run(String... args) {
//...
	listeners.ready(context, timeTakenToReady);//发布容器已启动并准备好事件
//...
}
//步骤二 SpringApplicationRunListeners.java
void ready(ConfigurableApplicationContext context, Duration timeTaken) {
	doWithListeners("spring.boot.application.ready", (listener) -> listener.ready(context, timeTaken));
}
//步骤三 EventPublishingRunListener.java
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
	//在这里发布 容器已启动并准备好了的 事件
	context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
	AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}
  1. Nacos的NacosContextRefresher监听器会监听ApplicationReadyEvent事件,并注册自己的监听器,用来监听Nacos服务端(即管理台)中配置信息的变化。
public class NacosContextRefresher
		implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {
	//...
	@Override
	public void onApplicationEvent(ApplicationReadyEvent event) {
		// many Spring context
		if (this.ready.compareAndSet(false, true)) {//通过CAS完成
			this.registerNacosListenersForApplications();
		}
	}	
	private void registerNacosListener(final String groupKey, final String dataKey) {
		String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
		Listener listener = listenerMap.computeIfAbsent(key,
				lst -> new AbstractSharedListener() {//重点:创建监听器监听服务端dataKey的配置信息变化
					@Override
					public void innerReceive(String dataId, String group,
							String configInfo) {//重点:具体处理dataKey的配置信息变化,主要是更新内存的值
						refreshCountIncrement();
						nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
						applicationContext.publishEvent(
								new RefreshEvent(this, null, "Refresh Nacos config"));
						if (log.isDebugEnabled()) {
							log.debug(String.format(
									"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
									group, dataId, configInfo));
						}
					}
				});
		//....
	}	
	//...		
}		
  1. Netty启动时,为了优雅的关闭服务端,是如下完成的:
try {
	//异步绑定到服务器,通过sync阻塞保证启动成功
	ChannelFuture future = bootstrap.bind(9080).sync();
	log.info("netty 启动成功");
	// 通过sync方法同步等待通道关闭处理完毕,这里会 阻塞 等待通道关闭完成
	future.channel().closeFuture().sync();//优雅的关闭服务端,当被唤醒后,线程就会结束阻塞,最终正常执行finally里的方法
}finally {
	boss.shutdownGracefully().sync();
}
  1. 由于future.channel().closeFuture().sync()阻塞线程,并且Netty我们是在主线程,即Spirng容器启动的线程来启动的,所以Spring启动过程会被阻塞。换句话说不再会发布ApplicationReadyEvent事件,那么自然,Nacos服务端的配置修改不会再被监听到,也不会更新客户端中内存的值了

解决方案

通过新建一个线程或交由线程池来启动Netty

下例演示新建一个线程来启动Netty:

@SpringBootApplication
public class Application implements ApplicationRunner {
    public static void main(String[] args)  {
        SpringApplication.run(Application.class,args);
    }
    @Override
    public void run(ApplicationArguments args) throws Exception {
        new Thread(()->{//新建一个线程方式来启动
            server.start();
        }).start();
    }
    @Autowired
    EchoServer server;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值