Chapter 01
核心概念与数据模型
起点页给了你那条脊柱:ephemeral 字段决定一条数据走 AP 还是 CP。
这一章把脊柱两端的名词逐个建立起来——先有准确的词汇表,后面三章的原理才挂得上去。
本章你将建立的 schema
- 注册中心到底替你解决了哪个"手动会很痛"的问题,以及它自己因此背上了什么新问题。
- Nacos 的五级数据模型:命名空间 → 分组 → 服务 → 集群 → 实例,以及"唯一定位一个服务"需要几个坐标。
- 临时实例与持久实例的区别——以及为什么这个布尔字段是整份教程的分流开关。
- 健康检查的两种模型(客户端心跳 vs 服务端探活),和 5s / 15s / 30s 这三个数字的含义。
- 配置中心的三级定位:namespace + group + dataId,以及 dataId 的命名构成。
1.1注册中心解决什么问题
注册中心是一本"实时电话簿":服务实例启动时登记自己的地址,消费方按服务名查到当前可用的地址列表。
没有注册中心时,order-service 要调 user-service,得把后者的 IP:Port 写进配置。一旦 user-service 扩容、重启换 IP、或某个实例宕机,调用方的配置就过期了——你要么手动改配置重启,要么自己搭一套脚本去探活和下发。注册中心把"谁还活着、地址是多少"这件事集中管理:实例自己上报,消费方实时查询。
关键的一步深入:注册中心自己也是一个分布式系统(生产环境是集群)。于是它接手了"维护电话簿"的同时,也继承了一个新问题——这本电话簿在多个节点之间如何保持一致。电话簿写得快还是读得准、网络分区时还能不能继续登记,这些取舍就是后面 AP / CP 的来源。把这句话记住:注册中心解决了调用方的地址问题,却给自己挖了个一致性问题,而 Nacos 对这个问题的回答,就是起点页那条分流线。
注册中心像公司前台的"在岗名单":员工到岗打卡(注册),访客问"张三在不在"前台报当前在岗的人(发现)。类比失效之处:真实前台只有一个,而 Nacos 前台是一组互为副本的前台,它们之间还要同步各自记下的名单——这正是单机直觉解释不了、需要一致性协议的地方。
1.2数据模型:五级坐标
Nacos 用 命名空间 → 分组 → 服务 → 集群 → 实例 五层组织一切;唯一定位一个服务需要前三个坐标。
这套层级是后面所有"发现不到""配置读不到"问题的根。先看图,每一层套在上一层里:
| 层级 | 作用 | 默认值 / 关键点 |
|---|---|---|
| 命名空间 namespace | 环境 / 租户隔离,跨命名空间完全互不可见 | public;代码里用 UUID 引用,不是显示名 |
| 分组 group | 业务维度再分组(同一环境内) | DEFAULT_GROUP |
| 服务 service | 逻辑服务,如 order-service | 唯一键 = namespace + group + service |
| 集群 cluster | 服务内按机房 / 可用区分实例,支持就近访问 | DEFAULT;不参与服务定位 |
| 实例 instance | 一个具体进程 ip:port,带权重、元数据、健康标记 | 有 ephemeral 标记(见 1.3) |
为什么用 namespace 而不是"搭三套 Nacos 隔离 dev/test/prod"?因为一套集群就能逻辑隔离,省机器、省运维。而 namespace 在 API 里必须用 UUID(不是 "dev" 这种名字)——隔离边界靠不可猜的 ID 守住,这也是新手最容易踩的配置错误:填了显示名,结果默默注册进了 public。
A 服务注册时 namespace=dev,B 服务注册时 namespace=prod,两者服务名都叫 order-service。B 能通过服务名发现 A 吗?
展开答案(先停 10 秒)
不能。namespace 是硬隔离边界,dev 和 prod 下即使服务名、分组完全相同,也互相不可见。这正是 namespace 的设计目的:让同名服务在不同环境下并存而不串台。
推论:当"本地能发现、同事发现不到"时,第一个要核对的就是两边的 namespace(UUID)是否一致。
1.3临时实例 vs 持久实例:分流开关
实例上的 ephemeral 布尔字段,决定它的健康由谁判定、数据存在哪、以及——走 AP 还是 CP。
这是全教程最重要的一个概念。同样是"一个 ip:port",它是临时还是持久,会引出两套完全不同的机制:
| 维度 | 临时实例 ephemeral=true(默认) | 持久实例 ephemeral=false |
|---|---|---|
| 健康判定 | 客户端主动上报心跳,断了即不健康 | 服务端主动探活(TCP/HTTP/MySQL) |
| 存储 | 只在服务端内存 | 持久化到磁盘(可重启恢复) |
| 下线行为 | 超时自动从内存删除 | 探活失败只标记不健康,不自动删除 |
| 一致性协议 | AP · Distro(异步复制) | CP · Raft(多数派提交) |
| 典型场景 | 无状态微服务(可随时增减) | 有固定身份的节点(如数据库、网关) |
这一个字段为什么能牵动一致性协议?因为两类数据的"容错预期"根本不同。一个无状态微服务实例短暂消失,调用方重试别的实例就行,可用性比"列表绝对准确"更重要 → 选 AP。而一个被运维当成固定资产登记的持久节点,宁可写入慢一点也不能在两个 Nacos 节点上看到不一致的状态 → 选 CP。ephemeral 就是把这个容错预期编码成了一个布尔值。
默认是临时实例,这符合微服务的直觉:实例是"用完即弃"的牲畜,不是"精心照料"的宠物,消失即下线最省事,也省掉了服务端逐个探活的开销。Spring Cloud Alibaba 里默认就是临时:
spring:
application:
name: order-service # 这就是服务名 service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: 3a8d2f1c-... # 用 UUID,不是 "dev"
group: DEFAULT_GROUP
cluster-name: BJ # 集群:北京机房,用于就近访问
ephemeral: true # 默认即 true;改 false 才是持久实例
1.4健康检查:5s / 15s / 30s
临时实例靠客户端每 5s 一次心跳维持;服务端 15s 没收到就标记不健康,30s 没收到就从内存删除。
承接 1.3:临时实例的"健康"是客户端推出来的。它的生命周期由三个时间点切分——这三个数字是面试高频,也是"实例抖动"故障的根:
持久实例正相反:它的健康由服务端主动探活(向实例发 TCP 连接 / HTTP 请求 / MySQL 探测),失败只把它标成不健康,不会删除。这就是运维常困惑的"持久实例下线后还在列表里"——它不是 bug,是设计:持久实例代表有固定身份的节点,Nacos 不替你决定它该不该消失,要删得你显式调 API。
一个临时实例所在进程被 kill -9(没有优雅下线、来不及注销),从这一刻起,最多多久之后消费方在 Nacos 上彻底查不到它?
展开答案(先停 10 秒)
从最后一次心跳算起,约 30s 后它从服务端内存删除(15s 标 unhealthy、30s 删除)。但这只是服务端的视角——消费方本地还有一份服务列表缓存,可能要再过一个刷新周期才更新。所以"进程死了,调用方还在打它"的窗口,比 30s 更长。这条客户端缓存的尾巴,是第 2 章和第 4 章失败模式的主角。
1.5配置中心的数据模型:三级定位
一条配置由 namespace + group + dataId 三个坐标唯一定位;dataId 自身还有命名构成。
注册中心管"服务在哪",配置中心管"服务读什么参数"。两者复用同一套 namespace / group 隔离,只是最后一层从"服务"换成了 dataId:
prefix-profile.扩展名 拼成,分别对应 Spring 的应用名、激活的 profile 和文件格式,错一段就读不到。这套命名规则在 Spring Cloud Alibaba 里是:${prefix}-${profile}.${file-extension}。prefix 默认取 spring.application.name,profile 是激活的环境,file-extension 默认是 properties(可改 yaml)。三段任意一段对不上,配置就静默读不到——这是第 3 章会展开的高频失败模式。
注意到没有:namespace 和 group 在服务发现和配置中心里是同一套隔离维度。学会一次,两边通用。这也意味着——配错 namespace 既会让你"发现不到服务",也会让你"读不到配置",症状不同,根因同一个。
§本章 self-check
先合上教程,把答案写在纸上或编辑器里。写完再点开对照——直接点开等于把这一节当再读一遍。
- 用一句话说明 namespace、group、service 三者如何共同唯一定位一个服务。集群(cluster)参与定位吗?
- 临时实例和持久实例,健康分别由谁判定?(一个词概括各自的判定方)
- 一个临时实例最后一次心跳后,多少秒被标记 unhealthy、多少秒被删除?这两段为什么要分开?
- dataId
user-service-prod.yaml拆成三段分别是什么?各对应 Spring 的哪个配置? - (设计题)为什么 Nacos 默认把实例注册成临时的,而不是持久的?从"无状态微服务的容错预期"角度回答。
答案(先做完再展开)
- 唯一键 = namespace + group + service 三者拼起来;三者全相同才是"同一个服务"。集群不参与定位,它是服务内部按机房对实例的细分。
- 临时实例:客户端(每 5s 上报心跳);持久实例:服务端(主动探活 TCP/HTTP/MySQL)。
- 15s 标记 unhealthy,30s 从内存删除。分两段是为短暂网络抖动留缓冲——抖动恢复回到健康,只有持续失联才彻底移除。
user-service= prefix(默认 = spring.application.name);prod= profile(spring.profiles.active);yaml= file-extension(文件格式)。- 无状态微服务实例是可随时增减的"牲畜",短暂消失时调用方重试别的实例即可,可用性比列表绝对准确更重要 → 适合 AP/临时;同时省去服务端逐个探活的开销。持久实例留给有固定身份的节点。
"本机能注册、能发现,同事连同一个 Nacos 却发现不到"
你的服务在本机注册成功、控制台能看到、本机也能发现它;但同事在他机器上(配的是同一个 server-addr)就是发现不到,报 "no available server"。
按"唯一定位一个服务需要几个坐标"这条线索,列出你会按顺序排查的隔离维度(至少 3 个),并说明每个为什么会导致"发现不到"。
提示(卡住再展开)
定位一个服务 = namespace + group + service。逐个核对两边是否一致:① namespace 的 UUID 是否相同(最常见:一边填了名字默默落到 public);② group 是否相同(默认 DEFAULT_GROUP,有人改了一边);③ 服务名拼写 / 大小写;④ 是不是其实连到了两个不同的 Nacos(server-addr 看着像,实际指向不同环境)。注意:cluster 不同不影响能否发现,只影响就近策略——这是个容易误判的干扰项。