项目场景
Nacos作为配置中心,添加Netty并启动后,Nacos管理端修改配置后,客户端未感知到变化。
问题描述
- 引入相应版本依赖
<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>
- 通过如下方式启动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();
}
@Autowired
EchoServer server;
}
- 添加如下测试配置参数的代码:
@RestController
@RequestMapping("/test")
@Slf4j
@RefreshScope
public class TestController {
@Value("${address.port}")
String port;
@GetMapping("/port")
public String get(){
return port;
}
}
- 在Nacos管理端修改
address.port 参数后,访问/test/port 无变化。
原因分析
Nacos是通过NacosContextRefresher 来注册监听器,进而监听服务端dataId的配置信息的变化,并更新客户端内存中的数据
分析过程
SpringApplication.run 在启动容器完成时,会调用listeners.ready(...) 发布ApplicationStartedEvent 事件,源码如下:
public ConfigurableApplicationContext run(String... args) {
listeners.ready(context, timeTakenToReady);
}
void ready(ConfigurableApplicationContext context, Duration timeTaken) {
doWithListeners("spring.boot.application.ready", (listener) -> listener.ready(context, timeTaken));
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}
Nacos的NacosContextRefresher 监听器会监听ApplicationReadyEvent 事件,并注册自己的监听器,用来监听Nacos服务端(即管理台)中配置信息的变化。
public class NacosContextRefresher
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (this.ready.compareAndSet(false, true)) {
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() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
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));
}
}
});
}
}
- Netty启动时,为了
优雅 的关闭服务端,是如下完成的:
try {
ChannelFuture future = bootstrap.bind(9080).sync();
log.info("netty 启动成功");
future.channel().closeFuture().sync();
}finally {
boss.shutdownGracefully().sync();
}
- 由于
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;
}
|