Nacos添加Netty后启动失败

本文解决Nacos作为配置中心时,客户端未能感知配置变化的问题。通过分析Spring Boot启动流程及Nacos监听机制,揭示了Netty服务阻塞导致的事件监听失效,并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目场景

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;
}
### 部署 QwQ-32B 模型于 Tesla P40 24GB GPU 对于在 Tesla P40 24GB 显卡上部署 QwQ-32B 模型而言,需考虑该模型参数量庞大以及显存占用情况。Tesla P40 的 24 GB GDDR5 内存在处理大规模深度学习模型时可能面临挑战[^1]。 #### 准备环境 为了确保顺利运行,建议使用支持 CUDA 和 cuDNN 加速的 Python 环境,并安装 PyTorch 或 TensorFlow 这样的框架来加载预训练权重文件并执行推理操作。此外还需要配置合适的驱动版本以匹配所选深度学习库的要求[^2]。 #### 调整批大小与优化器设置 由于硬件资源有限,在实际测试过程中应当适当减小批量尺寸(batch size),从而降低每次迭代所需的临时存储空间;同时可以尝试调整其他超参比如学习率、动量等提高收敛速度的同时减少内存消耗[^3]。 #### 使用混合精度训练(Mixed Precision Training) 通过采用 FP16 数据格式代替传统的 FP32 来表示浮点数能够有效节省一半以上的显存开销而不影响最终效果^4]。这通常涉及到修改代码中的某些部分以便启用自动混合精度特性(Automatic Mixed Precision, AMP)[^4]。 ```python from torch.cuda import amp scaler = amp.GradScaler() for input, target in data_loader: optimizer.zero_grad() with amp.autocast(): output = model(input) loss = criterion(output, target) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() ``` #### 启用分布式数据并行(Distributed Data Parallel, DDP) 如果单张 Tesla P40 不足以支撑整个计算任务,则可利用多机多卡方案来进行加速。DDP 是一种高效的实现方式之一,它允许每个进程拥有独立副本并通过 all-reduce 协议同步梯度更新信息[^5]。 ```python import os import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP def setup(rank, world_size): os.environ['MASTER_ADDR'] = 'localhost' os.environ['MASTER_PORT'] = '12355' # initialize the process group dist.init_process_group("nccl", rank=rank, world_size=world_size) model = Model().to(device) ddp_model = DDP(model, device_ids=[device]) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值