关注

Nacos 技术全景:架构、通信、一致性、高可用与常用特性

Nacos 技术全景:架构、通信、一致性、高可用与常用特性

一、通信模型:gRPC 双向流的设计

Nacos 2.x 全面转向 gRPC 通信,这不仅仅是一次协议升级,更是对客户端与服务端交互模式的重构。1.x 时代 HTTP 短连接和 UDP 推送的组合,在面对大规模客户端时暴露出连接数膨胀、推送不可靠、线程资源浪费等问题,而 gRPC 从根本上改变了这些。

客户端启动后会与 Nacos 服务端建立一个 TCP 长连接,并在连接之上创建双向流(Bidirectional Stream)。注册、订阅、心跳、配置查询、配置变更通知等全部复用这一条连接。HTTP/2 的多路复用机制使得同一连接上可以并发发送多个请求,省去了频繁的 TCP 握手开销,也大幅降低了服务端需要维持的连接数——从原先的“客户端数 × 请求频率”变成仅与客户端实例数线性相关。

连接建立后,应用层仍然需要心跳保活。客户端每 5 秒通过流发送 HealthCheckRequest,服务端回复 HealthCheckResponse。连续超时则触发重连,重连策略使用指数退避,最大间隔一般设为 30 秒。当客户端配置了多个 Nacos 地址时,SDK 会维护到每个地址的连接池,但同一时间只会选择一个响应最快的节点作为主连接,其余作为备选。主连接断开时自动切换,整个过程对业务透明。

ServerClientServerClientloop[每 5 秒]连接故障建立 gRPC 连接 + ConnectResetRequestConnectResetResponseHealthCheckRequestHealthCheckResponseHealthCheckRequest (超时)退避重连,切换备用节点ConnectResetRequest (新连接)

二、服务发现:注册、订阅与推送链路

2.1 注册

提供者启动后调用 NamingService.registerInstance,底层通过 gRPC 流发送 InstanceRequest(类型为 REGISTER)。请求体包含服务名、分组名、IP、端口、元数据和 ephemeral 标志。服务端收到后:

  • 写入内存注册表(ServiceManager 中的双层 Map:namespace -> group:serviceName -> cluster -> instances)。
  • 根据实例类型触发一致性同步:临时实例走 Distro 协议异步复制;持久实例走 Raft 强一致提交。
  • 发布 ServiceChangeEvent,通知所有订阅者。

2.2 订阅与推送

消费者通过 subscribe 订阅某个服务。SDK 发送 SubscribeServiceRequest 到服务端,服务端将当前订阅者记录在 SubscriberManager 中。当注册表中的实例列表发生变化时,服务端遍历对应服务的所有订阅者,通过各自的长连接推送 ServiceChangeNotifyRequest。这个通知消息体非常小,只包含服务标识,不携带实例数据,目的是告诉客户端“该服务有变化,请重新拉取”。

客户端收到通知后,立刻发起 QueryServiceRequest 拉取最新的全量实例列表。这种“通知 + 拉取”模式既保证了实时性,又避免了推送大量数据对服务端造成的带宽压力。

2.3 本地缓存与容灾

拉取到的实例列表会缓存在客户端内存中,同时持久化到本地磁盘的 failover 目录。当 Nacos 集群全部不可用时,SDK 会自动从本地文件中加载最后一次获取的实例列表,使服务间调用不至于完全中断。这一机制为注册中心整体宕机提供了最后的兜底。

ConsumerNacosProviderConsumerNacosProviderInstanceRequest(REGISTER)更新注册表 + Distro/Raft 同步SubscribeServiceRequest (gRPC Stream)SubscribeServiceResponse (初始全量)InstanceRequest(UNREGISTER)ServiceChangeNotifyRequest (变更通知)QueryServiceRequest (拉取全量)QueryServiceResponse (最新列表)更新内存缓存 + 落盘本地文件

三、健康检查与保护阈值

Nacos 区分两种健康检查方式:

  • 临时实例:采用客户端上报心跳的模式。SDK 定期通过 gRPC 流发送 HealthCheckRequest,服务端在 15 秒内未收到心跳则将实例标记为不健康,30 秒内未收到则剔除。这种方式依赖客户端主动上报,对服务端压力小,适合微服务弹性扩缩。
  • 持久实例:服务端主动探测,通常是 TCP 或 HTTP 健康检查。即使实例不在线也不会被自动剔除,适用于数据库、缓存等基础设施服务,生命周期由人工管理。

保护阈值是一个容易误读的参数。它作用于临时实例,表达的是“即便大量实例下线,也要保留一定比例的服务实例不被剔除”,用于防止因网络抖动造成整个服务被清空。默认值为 0,即不开启保护。当健康的实例数占总实例数的比例低于保护阈值时,Nacos 会将不健康的实例仍然保留在注册表中,避免消费者拿不到任何可用实例而引发连锁故障。这个值通常仅在网络不稳定的环境中才需要调高。

四、配置管理的实现机制

4.1 配置拉取

应用启动时,配置 SDK 通过 gRPC 流发送 ConfigQueryRequest,指定 dataIdgrouptenant(命名空间)。服务端从数据库中加载最新配置,将内容返回给客户端。客户端解析后转化为 PropertySource,注入 Spring 的 Environment

4.2 动态推送

1.x 的长轮询模型:客户端发起带有本地配置 MD5 的请求,服务端比较 MD5 值,如果没有变化则挂起连接最多 30 秒;若有变更则立即返回新 MD5。这种模式会占用服务端线程,并发能力受限。

2.x 改为 gRPC 推送:服务端在数据库配置变更后,通过长连接向所有订阅该配置的客户端发送 ConfigChangeNotifyRequest。客户端收到通知后立即发起一次 ConfigQueryRequest 拉取最新内容。不再占用线程挂起,推送延迟从秒级压缩到毫秒级。

4.3 本地缓存与降级

客户端拿到配置后,会在本地文件系统(默认 ~/nacos/config/)中缓存一份快照。当 Nacos 不可用时,SDK 会使用本地缓存配置启动。通过配置 spring.cloud.nacos.config.remote-first=false 可以强制优先使用本地文件。

4.4 @RefreshScope 与热更新

标注了 @RefreshScope 的 Bean 会在配置变更后重新创建。底层通过 Spring Cloud 的 RefreshScope 实现,监听 RefreshEvent。Nacos 的配置刷新正是通过发布此事件来驱动 Bean 的重新初始化。对于数据库连接池、线程池等资源,往往需要配合 @PreDestroy 做好旧 Bean 的资源释放,否则容易造成资源泄漏。

五、一致性协议:Distro 与 Raft 的共存

Nacos 在同一集群内同时运行两套一致性协议,分别服务于不同类型的需求。

5.1 Distro(AP 模式)

临时实例的同步采用自研的 Distro 协议。它不是简单的全异步复制,而是结合了一致性哈希的“写主读从”变体:

  • 每个节点都可以接受写请求。
  • 根据服务名计算哈希,将请求转发到负责该服务的“主节点”。
  • 主节点更新内存后,异步将数据复制到其他节点。
  • 版本号(lastModifiedTime)用来解决冲突和判断数据新旧。

Distro 保证了在网络分区时所有节点仍可接受注册,优先满足可用性,最终一致性的延迟通常在毫秒至百毫秒级。

NodeCNode B (主)NodeAClientNodeCNode B (主)NodeAClient注册 serviceXhash(serviceX) → NodeB转发注册请求写入内存Distro 异步复制Distro 异步复制

5.2 Raft(CP 模式)

持久实例和配置数据使用 Raft 协议。Nacos 内嵌了轻量级 Raft 实现(基于 SOFAJRaft)。写操作必须经过 Leader,复制到半数以上 Follower 后才提交。读操作默认使用 ReadIndex 方式,从 Leader 读取最新的 commit index 然后从本地状态机返回,以保证线性一致性。

配置选择 CP 是因为配置变更必须要求所有客户端看到相同的版本,一旦出现不一致,不同节点的应用可能加载不同的配置,引发严重故障。

六、数据持久化与集群部署

单机模式下 Nacos 使用内嵌 Derby 数据库存储数据,方便开发和测试。生产环境必须切换为 MySQL 或 PostgreSQL,配置位于 conf/application.properties

spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&...
db.user=root
db.password=root

初始化时需要执行发行包中的 mysql-schema.sql 脚本,创建 config_infohis_config_infotenant_info 等核心表。配置数据、持久实例信息、用户权限等全部落库,而临时实例数据仅存在于内存中,由 Distro 协议在集群节点间同步。

集群部署建议至少三个节点,节点间建议跨可用区分布。集群模式下,所有的 Nacos 节点地位对等,不存在主从之分(Raft 只在持久实例和配置相关模块中选举 Leader)。需要在前端用 Nginx 或 SLB 做四层或七层负载均衡,将客户端流量均匀分发到各节点。

七、鉴权与安全

2.x 版本引入了内置认证机制,通过配置 nacos.core.auth.enabled=true 开启。认证同时作用于控制台和 Open API。客户端在配置中提供用户名和密码:

spring:
  cloud:
    nacos:
      discovery:
        username: nacos
        password: nacos

服务端支持自定义 AuthPlugin,可以对接企业内部的 OAuth、LDAP 等认证系统。权限模型采用 RBAC,可以给用户分配角色,再为角色绑定命名空间级别的读写权限。生产环境建议关闭默认的 nacos/nacos 账号,创建最小权限账号。

八、监控与运维

Nacos 内置了 Prometheus 指标暴露端点(/actuator/prometheus 或独立端口),可以采集:

  • nacos_monitor:HTTP/gRPC 请求的 QPS、延时。
  • nacos_naming:注册的服务数、实例数、订阅数、心跳请求量。
  • nacos_config:配置数量、长连接数、推送次数。
  • JVM 堆内存、线程数、GC 信息。

集群健康状态可通过 nacos_raft_leader_count 判断 Raft 组是否有 Leader,nacos_distro_delay_ms 监控 Distro 同步延迟。建议将指标接入 Prometheus + Grafana,配合告警规则及时发现心跳丢失率陡增、配置推送延迟上升等异常。

九、Spring Cloud 集成中的关键技术点

  • 配置优先级:Nacos 配置会通过 PropertySourceBootstrapConfiguration 注入到 Spring 环境的最外层,优先级高于本地 application.yml。可通过 spring.cloud.nacos.config.ext-config 添加多 Data ID 配置,精细控制覆盖顺序。
  • 命名空间和分组:命名空间用于环境隔离(dev/test/prod),分组用于同一环境下服务的逻辑划分(如灰度分组 GRAY_GROUP)。配置 SDK 在拉取配置时会将 namespace 作为 tenant 参数传递,服务发现时同样基于命名空间和分组实现隔离。
  • Nacos Watch 机制:配置 SDK 会在启动时注册一个 FileWatcher 监听本地缓存文件的变化,但这主要用于本地手动修改文件的场景。真正的动态推送仍然依赖 gRPC 连接。
  • 多注册中心:Spring Cloud Alibaba 支持同时向多个注册中心注册服务,通过 spring.cloud.nacos.discovery.cluster-name 和自定义 NamingService 实现。

十、总结

Nacos 通过 gRPC 双向流解决了 HTTP 模型的连接复用和推送实时性问题;通过 Distro 和 Raft 两种一致性协议的共存,在服务发现和配置管理两个场景中分别实现了 AP 与 CP 的选择;通过保护阈值、本地缓存容灾、灰度分组等特性,适应了复杂网络环境下的生产需求。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_44551234/article/details/162143809

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--