在微服务架构下,服务的平滑发布是保障系统稳定性的关键。作为注册中心,Nacos 需要协同处理好服务实例的上线与下线过程,避免出现流量丢失或打到不健康实例的情况。今天我们就深入探讨一下,使用 Nacos 作为注册中心时,如何实现优雅发布。
Nacos 作为注册中心的核心工作流程与其他组件类似,如下图所示:

服务提供者 (Service Provider) 启动后向 Nacos Server 注册,服务消费者 (Service Consumer) 则从 Nacos Server 拉取服务列表,并根据负载均衡算法选择一个提供者发起调用。
1. 何为优雅发布?
所谓优雅发布,核心目标是确保服务实例在整个生命周期变更中对调用方的影响降至最低。具体来说,需要满足两个基本要求:
- 优雅上线:Service Provider 在自身完全初始化、准备好接收请求之前,其地址不应该出现在 Service Consumer 可拉取的服务列表中。
- 优雅下线:Service Provider 开始关闭流程后,应尽快从 Service Consumer 的可用服务列表中移除,不再接收新的请求。
解决了这两个问题,优雅发布的目标也就基本达成了。
2. 环境搭建与原理观察
为了深入理解 Nacos 的行为,我们通过搭建一个简单的实验环境并查看日志来追踪其内部流程。实验环境架构如下:

2.1 服务提供者启动
启动一个名为 springboot-provider 的 Spring Boot 应用,它会自动注册到 Nacos。观察其启动日志,关键部分如下:
2023-06-11 18:58:10,120 [main] [INFO] com.alibaba.nacos.client.naming - [BEAT] adding beat: BeatInfo{port=8083, ip='192.168.31.94', weight=1.0, serviceName='DEFAULT_GROUP@@springboot-provider', cluster='DEFAULT', metadata={management.endpoints.web.base-path=/actuator, management.port=18082, preserved.register.source=SPRING_CLOUD, management.address=127.0.0.1}, scheduled=false, period=5000, stopped=false} to beat map.
2023-06-11 18:58:10,121 [main] [INFO] com.alibaba.nacos.client.naming - [REGISTER-SERVICE] public registering service DEFAULT_GROUP@@springboot-provider with instance: Instance{instanceId='null', ip='192.168.31.94', port=8083, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='DEFAULT', serviceName='null', metadata={management.endpoints.web.base-path=/actuator, management.port=18082, preserved.register.source=SPRING_CLOUD, management.address=127.0.0.1}}
2023-06-11 18:58:10,133 [main] [INFO] com.alibaba.cloud.nacos.registry.NacosServiceRegistry - nacos registry, DEFAULT_GROUP springboot-provider 192.168.31.94:8083 register finished
...
2023-06-11 18:58:10,840 [com.alibaba.nacos.client.naming.updater] [INFO] com.alibaba.nacos.client.naming - modified ips(1) service: DEFAULT_GROUP@@springboot-provider@@DEFAULT -> [{"instanceId":"192.168.31.94#8083#DEFAULT#DEFAULT_GROUP@@springboot-provider","ip":"192.168.31.94","port":8083,"weight":1.0,"healthy":true,"enabled":true,"ephemeral":true,"clusterName":"DEFAULT","serviceName":"DEFAULT_GROUP@@springboot-provider","metadata":{"management.endpoints.web.base-path":"/actuator","management.port":"18082","preserved.register.source":"SPRING_CLOUD","management.address":"127.0.0.1"},"ipDeleteTimeout":30000,"instanceHeartBeatInterval":5000,"instanceHeartBeatTimeOut":15000}]
同时,查看 Nacos Server 端的日志文件 naming-server.log:
2023-06-11 18:58:09,723 INFO Client connection 192.168.31.94:51885#true connect
2023-06-11 18:58:10,105 INFO Client change for service Service{namespace='public', group='DEFAULT_GROUP', name='springboot-provider', ephemeral=true, revision=1}, 192.168.31.94:8083#true
...
在 Nacos 的管理控制台,我们也能看到服务已成功注册:

2.2 服务提供者下线
当 springboot-provider 服务正常关闭时,Nacos Server 会记录如下日志,表明移除了该实例:
2023-06-11 19:01:03,375 INFO Client connection 192.168.31.94:51885#true disconnect, remove instances and subscribers
2023-06-11 19:01:05,048 INFO [AUTO-DELETE-IP] service: Service{namespace='public', group='DEFAULT_GROUP', name='springboot-provider', ephemeral=true, revision=2}, ip: {"ip":"192.168.31.94","port":8083,"healthy":false,"cluster":"DEFAULT","extendDatum":{"management.endpoints.web.base-path":"/actuator","management.port":"18082","preserved.register.source":"SPRING_CLOUD","management.address":"127.0.0.1","customInstanceId":"192.168.31.94#8083#DEFAULT#DEFAULT_GROUP@@springboot-provider"},"lastHeartBeatTime":1686481231604,"metadataId":"192.168.31.94:8083:DEFAULT"}
2023-06-11 19:01:05,048 INFO Client remove for service Service{namespace='public', group='DEFAULT_GROUP', name='springboot-provider', ephemeral=true, revision=2}, 192.168.31.94:8083#true
2.3 服务消费与列表更新延迟
在消费者 springboot-consumer 应用中,使用 OpenFeign 调用提供者服务。观察其启动时拉取服务列表的日志,会发现一个关键点:如果使用了 Ribbon 作为负载均衡器,它有自己的本地服务列表缓存刷新周期。
消费者启动日志片段显示 Ribbon 初始化并拉取到了服务列表:
2023-06-11 19:15:47,962 [main] [INFO] com.netflix.loadbalancer.BaseLoadBalancer - Client: springboot-provider instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=springboot-provider,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2023-06-11 19:15:48,064 [main] [INFO] com.netflix.loadbalancer.DynamicServerListLoadBalancer - DynamicServerListLoadBalancer for client springboot-provider initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=springboot-provider,current list of Servers=[192.168.31.94:8083],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:192.168.31.94:8083; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:com.alibaba.cloud.nacos.ribbon.NacosServerList@24d998ba
从 Ribbon 的源代码 PollingServerListUpdater 中可以看到,其默认刷新服务列表的间隔是 30秒。这会影响服务列表变更的实时性。

相关核心代码如下:
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
//...
}
};
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,//这里定义的是30s
TimeUnit.MILLISECONDS
);
}//...
}
3. 优雅发布方案详解
回顾第一章,优雅发布的核心在于“优雅上线”和“优雅下线”。在 Spring Cloud 这套微服务体系下,我们需要结合 Nacos 和 Ribbon 的机制来设计解决方案。
3.1 优雅上线
问题:在默认的自动注册机制下,Spring Boot 应用启动后,可能 Web 容器(如 Tomcat)还未完全初始化,接口尚不可用,但服务实例已经注册到了 Nacos。此时若消费者立刻拉取到该实例并发起调用,会导致请求失败。
解决方案:关闭 Spring Cloud 的自动注册,改为在应用完全初始化后手动注册。
-
关闭自动注册:在 application.properties 或 application.yml 中配置。
spring.cloud.nacos.discovery.registerEnabled=false
-
手动注册:在服务确认就绪后(例如,在 ApplicationRunner 或 CommandLineRunner 的实现中,或一个特定的健康检查接口通过后),通过 Nacos 客户端 API 手动注册。
Properties setting = new Properties();
String serverAddr = "127.0.0.1:8848";
setting.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
setting.put(PropertyKeyConst.USERNAME, "nacos");
setting.put(PropertyKeyConst.PASSWORD, "nacos");
NamingService namingService = NacosFactory.createNamingService(setting);
namingService.registerInstance("springboot-provider", "192.168.31.94", 8083);
3.2 优雅下线
优雅下线需要区分两种情况:正常停服和服务故障。
3.2.1 正常停服
对于计划内的服务重启或下线,最优雅的方式是 主动通知 Nacos 注销实例,并等待一段时间再关闭进程。
步骤:
- 暴露一个管理端点(如 HTTP API),用于触发服务注销。
- 在服务关闭脚本或容器生命周期钩子(如 Kubernetes 的
preStop)中调用该端点。
- 调用注销接口后,等待一段缓冲时间(例如 5-10 秒),让 Nacos 将变更推送给消费者,同时等待 Ribbon 缓存过期(考虑 30 秒),然后再执行真正的进程关闭。
注销接口示例:
@GetMapping(value = "/nacos/deregisterInstance")
public String deregisterInstance() {
Properties prop = new Properties();
prop.setProperty("serverAddr", "localhost");
prop.put(PropertyKeyConst.NAMESPACE, "test");
NacosNamingService client = new NacosNamingService(prop);
client.deregisterInstance("springboot-provider", "192.168.31.94", 8083);
return "success";
}
3.2.2 服务故障
当服务进程意外崩溃时,无法主动注销。此时依赖 Nacos 的心跳检测机制。
- 默认情况下,客户端每 5 秒发送一次心跳。
- Nacos Server 若 15 秒未收到心跳,会将实例标记为“不健康”。
- 若 30 秒未收到心跳,会将该实例从服务列表中删除。
这个过程相对较慢。我们可以通过元数据来调整这些参数(注意:客户端版本需要支持):
spring.cloud.nacos.discovery.metadata.preserved.heart.beat.interval=1000 #心跳间隔 5s -> 1s
spring.cloud.nacos.discovery.metadata.preserved.heart.beat.timeout=3000 #健康超时 15s -> 3s
spring.cloud.nacos.discovery.metadata.preserved.ip.delete.timeout=5000 #删除超时 30s -> 5s
然而,即使优化了参数,在故障场景下也难以做到消费者完全无感知,因为从心跳超时到客户端感知总会有延迟。更高级的方案可以结合应用本身的健康检查,当应用内部检测到关键组件故障时,主动调用注销接口,实现“自杀”,这比等待心跳超时更快。
4. 总结
实现基于 Nacos 的优雅发布,关键在于理解并协调好服务注册时机、下线通知机制以及客户端(尤其是 Ribbon)的缓存更新策略。优雅上线通过“手动注册”来控制时机;优雅下线则需区分正常与异常场景,正常停服以“主动注销+延迟关闭”为主,异常故障则需优化心跳参数并结合主动健康检查来加速故障实例的隔离。将这些实践融入到你的 Java 微服务发布流程中,能显著提升系统的可用性与发布体验。如果你在实践过程中有更多心得,欢迎在云栈社区交流分享。