【Spring Cloud】Nacos注册中心

文章目录

在微服务技术栈中,注册中心是整个微服务体系的核心组件。早年很多项目使用Eureka做服务注册发现,但是 Eureka 2.0宣布闭源之后,阿里开源的 Nacos 迅速成为国内微服务首选。 Nacos全称 Dynamic Naming and Configuration Service,它不只是注册中心,同时还提供配置管理、动态DNS服务,一套组件搞定服务发现+配置中心两大能力,支持Java、Go、Python等几乎主流开发语言。

一、Nacos安装部署

Linux单机安装

复制代码
#解压
unzip nacos-server-2.2.3.zip
#单机启动
bash startup.sh -m standalone

端口放行: Nacos除主端口8848,还需要开放 9848、9849(端口+1000、+1001),否则客户端通信异常。

访问地址:http://服务器IP:8848/nacos

二、Nacos服务注册与发现

Nacos实现SpringCloud标准服务注册发现,和Eureka编程模型很像,区别是不需要自己搭建注册服务,只需要启动Nacos服务端,微服务引入对应依赖即可。

1、Maven依赖

父工程统一管理版本

xml 复制代码
<properties>
    <spring-cloud-alibaba.version>2025.1.0.0</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

微服务模块引入:

xml 复制代码
<!--nacos注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--springcloud loadbalancer负载均衡-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>

2、application.yml配置

yml 复制代码
spring:
  application:
    name: product-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

3、远程调用RestTemplate

java 复制代码
@Configuration
public class BeanConfig {
    @Bean
    @LoadBalanced //开启负载均衡,支持服务名调用
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

调用时直接写服务名称,不再写IP端口

java 复制代码
//直接使用服务名 product-service
String url = "http://product-service/product/"+orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);

常见报错 java.net.UnknownHostException: product‑service:大概率缺少spring‑cloud‑loadbalancer依赖。

启动多个实例,Nacos控制台服务列表就可以看到多个服务节点,自动实现负载均衡。

三、Nacos负载均衡

SpringCloud LoadBalancer默认轮询策略,Nacos提供权重、集群优先等高级流量控制,需要手动开启Nacos负载均衡策略。

yaml 复制代码
spring:
  cloud:
    loadbalancer:
      nacos:
        enabled: true

1、服务上下线

Nacos控制台 → 服务详情,可以手动对实例执行下线操作。下线节点不会接收流量,上线之后恢复接收请求。适合机器维护、版本灰度场景。

2、权重配置

每个实例默认权重=1;权重数值越大分到流量越多。 控制台找到实例点击编辑,修改权重。比如设置权重0.1,该实例只会分到少量流量。

注意: 修改权重报错 Raft Group did not find the Leader node

  • 原因:Raft选主异常
  • 解决:删除nacos目录data/protocol文件夹,重启nacos。

3、同集群优先访问

实际生产,服务部署在北京、上海多个机房。希望优先调用本机房服务实例,跨机房网络延迟高,只在本机实例全部不可用时访问其他机房。

配置product-service服务所属集群名称:

java 复制代码
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        cluster-name: SH #上海机房

另外两个个实例VM参数指定BJ集群: ‑Dserver.port=9091 ‑Dspring.cloud.nacos.discovery.cluster‑name=BJ

order-service同样配置cluster‑name:SH,开启nacos负载均衡,调用优先访问SH集群实例;SH实例全部下线,才会转发BJ集群。

四、Nacos健康检查机制

Nacos实例分为临时实例、非临时(永久)实例,两种实例健康检查机制完全不一样。

实例类型 检查方式 操作
临时实例(默认) 客户端心跳上报 客户端每5秒上报心跳;15秒没心跳标记不健康;30秒删除实例
非临时实例 服务端反向探测 Nacos服务端主动探测实例;宕机不会自动删除,标记不健康

配置非临时实例:

yaml 复制代码
spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false #关闭临时实例,永久实例

注意:同一个IP端口服务,不能切换临时/非临时,直接启动报错。需要删除nacos/data/protocol/raft,重新启动nacos。

五、Namespace命名空间实现环境隔离

企业开发中一个服务通常有四种环境:

  • 开发环境
  • 测试环境
  • 预发布环境
  • 发布环境

其中预发布环境和发布环境都属于正式环境,数据库,配置都是一样的,预发布环境不对外,发布环境对外。

开发、测试、生产环境需要隔离,不同环境服务之间不能互相调用。Nacos使用namespace命名空间实现隔离。 默认命名空间为public。不同namespace之间服务完全不可见

Nacos控制台新建命名空间,拿到命名空间ID。 微服务配置指定namespace:

yaml 复制代码
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: "命名空间ID"

注意:服务注册的namespace 和配置中心namespace是两套独立配置,互不影响

六、Nacos配置中心

为什么要用配置中心

传统把配置写在项目yml文件:

  1. 修改配置必须重启服务;成百上千实例挨个重启效率极低
  2. 多人开发配置文件容易冲突

Nacos配置中心把配置统一托管,配置修改实时推送,服务无需重启即可热更新配置。

快速上手Nacos Config

  1. 引入依赖
xml 复制代码
<!--nacos配置中心-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--SpringCloud2020之后bootstrap默认关闭,手动引入-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
  1. application.yml:

product-service/../application.yml

yaml 复制代码
spring:  
  config:  
    import: nacos:product-service
  cloud:  
    nacos:  
      discovery:  
        server-addr: 121.41.65.106:8848  
      config:  
        server-addr: 121.41.65.106:8848  

order-service/../application.yml

yaml 复制代码
spring:
  config:  
    import: nacos:order-service
  cloud:  
    nacos:  
      config:  
        server-addr: 121.41.65.106:8848  
      discovery:  
        server-addr: 121.41.65.106:8848 # Nacos服务器地址  
    loadbalancer:  
      nacos:  
        enabled: true  
  1. Nacos控制台新建配置 DataID:product‑service,与应用名称保持一致;格式properties,写入配置

    nacos.test.num=5

  2. 代码读取配置,实现热更新

java 复制代码
@RestController
@RequestMapping("/config")
@RefreshScope //核心注解,配置变更自动刷新bean
public class NacosController {

    @Value("${nacos.test.num:0}")
    private Integer nacosNum;

    @RequestMapping("/get")
    public Integer get(){
        return nacosNum;
    }
}

访问接口,修改控制台配置内容,无需重启服务,接口返回值动态变化。

DataId完整拼接规则

java 复制代码
${prefix}-${spring.profiles.active}.${file‑extension}
  • prefix 默认等于spring.application.name
  • spring.profiles.active:环境(dev/test/prod)
  • file‑extension 文件格式,默认properties

服务启动会自动加载3份配置,优先级从高到低: product‑service‑dev.properties > product‑service.properties > product‑service 高优先级配置覆盖低优先级配置。

常见报错

  1. No spring.config.import property has been defined:缺少spring‑cloud‑starter‑bootstrap依赖。
  2. 读取不到配置:核对namespace、DataId、文件格式是否匹配。

七、Nacos vs Eureka对比

  • 相同点:都可以实现服务注册、服务发现。
  • 不同点:
对比维度 Eureka Nacos
功能 仅服务注册发现 注册中心+配置中心+动态DNS,能力丰富
CAP AP 支持切换AP/CP;临时实例AP,永久实例CP,可以混合
数据同步 客户端拉取模式,默认30s拉取一次 推送模式,服务实例变化实时推送客户端

使用过程中遇到的问题及解决方案

1. 端口:order-serivce配了 8080,为什么看到一个 9848 的连接

排查:抓端口状态看到两行关键信息------

复制代码
TCP  0.0.0.0:8080        0.0.0.0:0            LISTENING    22692
TCP  10.14.24.97:65130   121.41.65.106:9848   ESTABLISHED  22692

第一行是 LISTENING,说明 8080 确实在监听,配置生效了。

第二行是 ESTABLISHED,方向是"本机 → 远端",是主动连出去的,不是被访问。

原因:Nacos 2.x 之后,一个 8848 其实对应三个端口,客户端会用主端口自动推导:

  • 8848 = HTTP 端口,控制台和 OpenAPI(8848 + 0)
  • 9848 = 客户端 gRPC 端口(8848 + 1000),注册、心跳、订阅全走它
  • 9849 = 服务端之间的 gRPC 同步端口(8848 + 1001)
  • 7848 = Nacos 1.x 遗留的 Jraft 端口

所以只要应用是 Nacos 客户端,启动后就必然会往 9848 建一条长连接。

结论server.port 管的是"别人怎么访问我",9848 管的是"我怎么连 Nacos",两者完全无关,不需要改任何东西,访问服务就用 http://localhost:8080/...

衍生提醒 :如果 Nacos 2.x 注册失败报 Client not connected, current status: STARTING,大概率是服务端防火墙只放行了 8848,没放 9848/9849。云服务器要放行 8848、9848、9849(Nacos 1.x 还要加 7848)。

2. 数据库:Public Key Retrieval is not allowed

报错链条

复制代码
MyBatisSystemException
  → CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
    → com.mysql.cj.exceptions.UnableToConnectException: Public Key Retrieval is not allowed
      → CachingSha2PasswordPlugin.nextAuthenticationStep

原因 :MySQL 8.0 起,root 用户的默认认证插件从 mysql_native_password 换成了 caching_sha2_password

  1. 密码在服务端缓存命中时走"快速认证",直接通过;
  2. 缓存未命中时(首次连接、MySQL 重启后、FLUSH PRIVILEGES 之后)需要完整认证,客户端必须用服务端的 RSA 公钥加密密码;
  3. 公钥要么走 SSL 加密通道下发,要么让客户端主动索取,而后者被 Connector/J 默认禁止(怕被中间人利用爆破密码);
  4. 连接串里写了 useSSL=false,把 SSL 那条路也堵死了。

两条路都不通,于是在握手阶段就失败。注意报错时还没执行 SQL,纯粹是连不上库。

解决 :连接串补参数allowPublicKeyRetrieval=true,允许客户端在握手时向 MySQL 服务器索要RSA 公钥。

yaml 复制代码
url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai

另外一种解决方案 :改用 SSL 连接 useSSL=true&requireSSL=true。走 SSL 时不需要 allowPublicKeyRetrieval,二者选一,生产环境更推荐。

安全提醒allowPublicKeyRetrieval=true 在非加密连接下理论上有被伪造公钥、离线爆破的风险。本地 127.0.0.1 无所谓,公网数据库请走 SSL。

3. 服务调用: No instances available for 121.41.65.106

报错

复制代码
WARN  RoundRobinLoadBalancer : No servers available for service: 121.41.65.106
ERROR IllegalStateException: No instances available for 121.41.65.106

报错里把 IP 当成了服务名。

错误代码

java 复制代码
String url = "http://121.41.65.106:8848/product/" + orderInfo.getProductId();

原因 :RestTemplate 上打了 @LoadBalanced,这个注解会塞进一个 LoadBalancerInterceptor,它把 URL 的 host 段当作 serviceId,端口直接丢弃,然后去服务发现里查这个"服务名"的实例列表。

于是 121.41.65.106:8848 被解析成 serviceId = 121.41.65.106,去 Nacos 查当然查不到。

更根本的错误是:121.41.65.106:8848Nacos 服务端自己的地址,不是 product-service 的地址。

正确写法

java 复制代码
// 服务名必须与 product-service 在 Nacos 中注册的 spring.application.name 一致
String url = "http://product-service/product/" + orderInfo.getProductId();

product-service 正是它 application.ymlspring.application.name 的值,也是 Nacos 注册中心里的服务名。LoadBalancer 会自动把它替换成真实地址 192.168.86.1:9090

4. Nacos 服务端:改权重报 Raft Group [naming_instance_metadata] did not find the Leader node

报错

复制代码
errCode: 500, errMsg: do metadata operation failed
caused: ConsistencyException: The Raft Group [naming_instance_metadata] did not find the Leader node

这不是客户端问题,是 Nacos 服务端自己的 Raft 一致性组坏了。

很多人以为"临时实例走 Distro 协议、存在内存里,跟 Raft 无关"------在 Nacos 2.2 里这个认知是错的 。Nacos 2.2 引入了实例元数据持久化,实例的权重、metadata、enabled 这些元数据的变更,被统一交给独立的 Raft 组 naming_instance_metadata 做一致性持久化,不分临时实例还是持久实例

所以"改权重"在服务端等价于"写一条元数据 Raft 日志",Raft 组没有 Leader,就写不进去。

排查:查服务端状态接口确认版本和模式------

bash 复制代码
curl "http://<nacos>:8848/nacos/v1/console/server/state"
# {"version":"2.2.3","standalone_mode":"standalone",...}

单机模式下 Raft 本该自己选自己当 Leader,现在选不出来,说明该节点的 Raft 数据或状态坏了。

常见成因

  1. Raft 数据目录损坏或残留(最常见)------data/protocol/raft 下的日志、快照、meta 文件半写损坏,多因 kill -9、磁盘写满、异常断电
  2. 曾经用集群模式启动过------meta 里记录了旧 peer 列表,后来改成单机,残留的 peer 列表让节点找不到自己
  3. 机器 IP 变了------换了网卡或重启后 IP 变化,而 raft meta 里还记着老地址
  4. cluster.confnacos.core.member.list 残留已下线的节点地址
  5. 系统时钟不同步,影响选举超时判定

解决方案 在 Nacos 服务器上执行:

bash 复制代码
cd ~/tools/nacos

# 1. 停服务
bash bin/shutdown.sh
ps -ef | grep nacos | grep -v grep     # 确认退干净

# 2. 备份
mv data/protocol data/protocol.bak.$(date +%F-%H%M)

# 3. 单机模式启动
bash bin/startup.sh -m standalone

# 4. 看 Raft 选举日志
tail -n 100 logs/alipay-jraft.log

注意 :只动 data/protocol,绝对不要删 data/derby-data

  • data/protocol/ = Raft 数据(实例元数据、持久化实例),删了会重建
  • data/derby-data/ 时配置中心的配置内容,删了配置全没,且不可恢复

删完data/protocol :自定义权重回到默认值 1.0;持久化实例消失需重新注册;临时实例靠客户端心跳自动重新注册,基本无感。

确诊用的日志文件

bash 复制代码
grep -i -E "did not find|elect|PEER|Leader" logs/alipay-jraft.log | tail -50
grep -i -E "did not find|elect|Leader" logs/protocol-raft.log | tail -50
grep -i -E "did not find|elect|Leader" logs/naming-raft.log | tail -50

看到 PEER ... not in the peer list 或反复 start election 但选不上,印证了"旧 peer 列表残留 / IP 变了";看到 CorruptedCRCunexpected end of file 则是数据文件损坏。

建议: 启动方式固定成单机就别来回切集群/单机;别用 kill -9 停 Nacos,一定用 shutdown.sh 让 Raft 正常落盘。

相关推荐
Wang's Blog1 小时前
Java框架快速入门:Spring Security+OAuth2之用户注册与唯一性校验实现
java·数据库·spring
Wang's Blog1 小时前
Java框架快速入门: Spring Security+OAuth2之多因子认证与TOTP实战
java·spring·ui
程序员20071 小时前
AI 编程工具链碎片化?基于多端兼容的统一 Agent Rules 架构实践
后端
程序员20071 小时前
拒绝 AI 代码“暗度陈仓”:工程级 AI 编码边界与防护规则设计
后端
yunwei372 小时前
eBPF 教程:追踪 CUDA GPU 操作
linux·后端·性能优化
搬搬砖得了2 小时前
从“我写的”到“我懂它”——论工程师对代码的心理模型
后端
小满zs2 小时前
Go语言第十二章(通道)
后端·go
林冠宏_指尖下的幽灵2 小时前
AI发展下的后编程时代思考
前端·人工智能·后端
孙启超2 小时前
【AI开发之Rust】第 3 课:字符串与复合类型 —— 数据怎么放
开发语言·人工智能·后端·rust·llm·transformer