概述
微服务拆到十几二十个之后,"改一个配置"就变成了一件需要发版的事------改文件、重新打包、逐台重启。这篇笔记只做一件事:把配置从各个服务的 application.yml 里搬进 Nacos,讲清楚"添加一份配置"时那几个必填项到底填什么、为什么必须那么填。
纲要
- 问题侧
- 配置分散:每个微服务一份
application.yml,同一份配置改多次 - 改配置要重新打包 + 重启,生产环境下重启代价高
- 多环境靠多文件 / 多 profile,容易漂移
- 密码、密钥等敏感配置进了代码仓库
- 配置分散:每个微服务一份
- 概念侧
- 配置中心解决的问题:集中管理 + 配置热更新
- 定位一份配置的三个坐标:
namespace/group/dataId dataId命名规范:[服务名]-[profile].[后缀名]- Nacos、本地配置、共享配置的优先级关系
- 动手侧
- 控制台入口与默认账号:
http://localhost:8848/nacos - 新建配置表单的每个字段
- 配置内容的取舍:只放需要热更新的开关类、模板类配置
- 版本历史与回滚
- 控制台入口与默认账号:
- 原理侧
bootstrap.yml如何拼出dataId,以及名字对不上为什么会静默失败
- 避坑侧
- 六个典型坑与排查手法
从微服务上线后的那把"配置刀"说起
先看昨天搭好的那套结构。两个微服务 userservice(8081)、orderservice(8088) 各自带着一份 application.yml,负责数据库连接、端口、MyBatis 别名包这些;两个服务之间还要互相调用,所以都注册到了注册中心,消费者从注册中心做服务发现和负载均衡。
这个结构本身没问题。问题出在实例数量涨上去以后。
一个配置项,比如某个活动开关、某个日期格式,如果它和十几个服务都有关,那么:
- 得逐个服务去改
application.yml; - 改完每个服务都得重新打包、重启;
- 如果是分环境部署,还得同时维护
application-dev.yml、application-test.yml、application-prod.yml好几份,靠人工保证它们同步。
生产环境里重启一个服务的代价不用多说------正在处理的请求断了,上游的重试和熔断跟着抖一下。为了改一个开关而发一次版,这个账算不过来。
想要的东西其实很明确:
- 十几份配置要改,只在一个地方改;
- 改完之后服务不用重启就生效 ,也就是配置热更新。
这就是配置中心的位置。做法是把微服务里那些"核心的、经常变"的配置抽出来,放到配置管理服务上;微服务启动时先从这里把配置拉下来,再和本地配置文件合并,作为最终配置去初始化 Spring 容器。之后配置改了,微服务会持续去拉取,拿到最新的值直接生效,不再需要重启。
这里有个容易混淆的点:注册中心和配置中心可以由同一个 Nacos 承担。Nacos 本来就是做注册中心的,加上配置管理能力后,服务注册发现找它,读配置也找它。当然拆成两套 Nacos 集群也完全可行,成本问题,不是技术问题。
定位一份配置需要三个坐标
Nacos 里没有"文件夹"这种直觉上的东西。一份配置的唯一位置是由三层标识拼出来的,少一层都可能定位到别的地方。
| 坐标 | 作用 | 默认值 | 典型取值 |
|---|---|---|---|
namespace |
环境隔离,不同环境互相看不见 | public |
public / dev / test / prod |
group |
同一环境内按业务再分组 | DEFAULT_GROUP |
DEFAULT_GROUP / ORDER_GROUP |
dataId |
一份配置的唯一标识 | 无 | userservice-dev.yaml |
#mermaid-svg-meuqMFbN7Hq1FowJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-meuqMFbN7Hq1FowJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-meuqMFbN7Hq1FowJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-meuqMFbN7Hq1FowJ .error-icon{fill:#552222;}#mermaid-svg-meuqMFbN7Hq1FowJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-meuqMFbN7Hq1FowJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-meuqMFbN7Hq1FowJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-meuqMFbN7Hq1FowJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-meuqMFbN7Hq1FowJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-meuqMFbN7Hq1FowJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-meuqMFbN7Hq1FowJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-meuqMFbN7Hq1FowJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-meuqMFbN7Hq1FowJ .marker.cross{stroke:#333333;}#mermaid-svg-meuqMFbN7Hq1FowJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-meuqMFbN7Hq1FowJ p{margin:0;}#mermaid-svg-meuqMFbN7Hq1FowJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-meuqMFbN7Hq1FowJ .cluster-label text{fill:#333;}#mermaid-svg-meuqMFbN7Hq1FowJ .cluster-label span{color:#333;}#mermaid-svg-meuqMFbN7Hq1FowJ .cluster-label span p{background-color:transparent;}#mermaid-svg-meuqMFbN7Hq1FowJ .label text,#mermaid-svg-meuqMFbN7Hq1FowJ span{fill:#333;color:#333;}#mermaid-svg-meuqMFbN7Hq1FowJ .node rect,#mermaid-svg-meuqMFbN7Hq1FowJ .node circle,#mermaid-svg-meuqMFbN7Hq1FowJ .node ellipse,#mermaid-svg-meuqMFbN7Hq1FowJ .node polygon,#mermaid-svg-meuqMFbN7Hq1FowJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-meuqMFbN7Hq1FowJ .rough-node .label text,#mermaid-svg-meuqMFbN7Hq1FowJ .node .label text,#mermaid-svg-meuqMFbN7Hq1FowJ .image-shape .label,#mermaid-svg-meuqMFbN7Hq1FowJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-meuqMFbN7Hq1FowJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-meuqMFbN7Hq1FowJ .rough-node .label,#mermaid-svg-meuqMFbN7Hq1FowJ .node .label,#mermaid-svg-meuqMFbN7Hq1FowJ .image-shape .label,#mermaid-svg-meuqMFbN7Hq1FowJ .icon-shape .label{text-align:center;}#mermaid-svg-meuqMFbN7Hq1FowJ .node.clickable{cursor:pointer;}#mermaid-svg-meuqMFbN7Hq1FowJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-meuqMFbN7Hq1FowJ .arrowheadPath{fill:#333333;}#mermaid-svg-meuqMFbN7Hq1FowJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-meuqMFbN7Hq1FowJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-meuqMFbN7Hq1FowJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-meuqMFbN7Hq1FowJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-meuqMFbN7Hq1FowJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-meuqMFbN7Hq1FowJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-meuqMFbN7Hq1FowJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-meuqMFbN7Hq1FowJ .cluster text{fill:#333;}#mermaid-svg-meuqMFbN7Hq1FowJ .cluster span{color:#333;}#mermaid-svg-meuqMFbN7Hq1FowJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-meuqMFbN7Hq1FowJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-meuqMFbN7Hq1FowJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-meuqMFbN7Hq1FowJ .icon-shape,#mermaid-svg-meuqMFbN7Hq1FowJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-meuqMFbN7Hq1FowJ .icon-shape p,#mermaid-svg-meuqMFbN7Hq1FowJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-meuqMFbN7Hq1FowJ .icon-shape .label rect,#mermaid-svg-meuqMFbN7Hq1FowJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-meuqMFbN7Hq1FowJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-meuqMFbN7Hq1FowJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-meuqMFbN7Hq1FowJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} namespace(环境层)
public / dev / test / prod
group(业务层)
DEFAULT_GROUP
dataId
userservice-dev.yaml
dataId
orderservice-dev.yaml
dataId
gateway-dev.yaml
group
ORDER_GROUP
dataId
orderservice-dev.yaml
namespace 决定"这是哪个环境的配置",比如 dev 环境和 prod 环境各占一个 namespace,两边哪怕 dataId 完全一样也不会互相覆盖。这一层是隔离,不是命名。
group 决定"同一环境里这是哪摊业务的配置" 。默认 DEFAULT_GROUP,大多数教学和小型项目直接用默认值就行。真正需要它的场景是:同一个环境里,你想让某些服务的配置和另一些服务分开管理,或者同一份配置在不同分组下要存不同内容。
dataId 才是那份具体的配置 。它必须全局唯一,不然所有微服务都来自 Nacos 拿配置,全叫 application.yml 就直接撞车了。
举个完整的例子把三层串起来:
userservice-dev.yaml,放在public空间、DEFAULT_GROUP分组下。
拆开看这三段各自代表什么:
| 片段 | 取值 | 含义 |
|---|---|---|
| 服务名 | userservice |
这份配置属于哪个微服务,取 spring.application.name 的值 |
| profile | dev |
当前运行环境,取 spring.profiles.active 的值 |
| 后缀名 | yaml |
配置格式,取 spring.cloud.nacos.config.file-extension 的值 |
三段拼起来,就是 userservice 在 dev 环境下该读的那份配置。
dataId 命名规范为什么必须严格照抄
规范本身只有一行:
text
[服务名]-[profile].[后缀名]
但它的分量不在"好看",而在于这个名字不是给人看的,是程序拼出来的。
微服务启动时要先知道去哪儿读配置,可这时候本地 application.yml 还没读进来,自然也就不知道 Nacos 地址。Spring 为此引入了一个更早加载的文件 bootstrap.yml,它在 application.yml 之前被读取。里面写的是 Nacos 地址、应用名、环境、后缀名这些"读配置所需的元信息"。
yaml
spring:
application:
name: userservice # 服务名称
profiles:
active: dev # 开发环境
cloud:
nacos:
server-addr: localhost:8848 # Nacos 地址
config:
file-extension: yaml # 配置后缀名
Spring Cloud Alibaba 拿到这些值之后,会按固定规则拼出 dataId,再去 Nacos 找:
text
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
对应关系:
| bootstrap.yml 里的配置项 | 值 | 拼出的片段 |
|---|---|---|
spring.application.name |
userservice |
userservice |
spring.profiles.active |
dev |
dev |
spring.cloud.nacos.config.file-extension |
yaml |
yaml |
| 拼接结果 | userservice-dev.yaml |
所以 dataId 不能随手起名。你在 Nacos 上把名字写成 user-service-dev.yaml(多了个连字符)、userservice_dev.yaml(下划线)、或者后缀写成 yml 而 file-extension 写 yaml,服务就永远拉不到这份配置。
更要命的是,这类错误通常不会在启动时抛异常。它表现为:
- Spring 照样启动成功;
- 配置项读出来是
null,或者落到本地写死的默认值上; - 你以为是代码写错了,实际上是根本没读到远程那一份。
这是配置中心最典型的静默失败。排查时的第一反应应该是看日志里 Spring 到底去请求了哪个 dataId,而不是盯着业务代码。
后缀名这里也有个细节:Nacos 支持 YAML 和 Properties 两种常用格式,建议后缀统一写全称 yaml ,别写 yml。因为 file-extension 填什么,拼接出来的 dataId 后缀就是什么,两处只要有一处偷懒就会对不上。
在 Nacos 控制台添加一份配置
地址和账号都是默认的:
text
控制台:http://localhost:8848/nacos
账号 / 密码:nacos / nacos
仓库里的工程
bootstrap.yml写的是server-addr: nacos:8848,因为整套服务跑在 Docker Compose 网络里,nacos是容器服务名;讲义为了本地演示写的是localhost:8848。两种写法都正确,取决于 Nacos 和微服务是否在同一个容器网络里,关键是要能解析到、端口通。
操作路径:
#mermaid-svg-QAPdPW4hPZiOiFZW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QAPdPW4hPZiOiFZW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QAPdPW4hPZiOiFZW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QAPdPW4hPZiOiFZW .error-icon{fill:#552222;}#mermaid-svg-QAPdPW4hPZiOiFZW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QAPdPW4hPZiOiFZW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QAPdPW4hPZiOiFZW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QAPdPW4hPZiOiFZW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QAPdPW4hPZiOiFZW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QAPdPW4hPZiOiFZW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QAPdPW4hPZiOiFZW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QAPdPW4hPZiOiFZW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QAPdPW4hPZiOiFZW .marker.cross{stroke:#333333;}#mermaid-svg-QAPdPW4hPZiOiFZW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QAPdPW4hPZiOiFZW p{margin:0;}#mermaid-svg-QAPdPW4hPZiOiFZW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QAPdPW4hPZiOiFZW .cluster-label text{fill:#333;}#mermaid-svg-QAPdPW4hPZiOiFZW .cluster-label span{color:#333;}#mermaid-svg-QAPdPW4hPZiOiFZW .cluster-label span p{background-color:transparent;}#mermaid-svg-QAPdPW4hPZiOiFZW .label text,#mermaid-svg-QAPdPW4hPZiOiFZW span{fill:#333;color:#333;}#mermaid-svg-QAPdPW4hPZiOiFZW .node rect,#mermaid-svg-QAPdPW4hPZiOiFZW .node circle,#mermaid-svg-QAPdPW4hPZiOiFZW .node ellipse,#mermaid-svg-QAPdPW4hPZiOiFZW .node polygon,#mermaid-svg-QAPdPW4hPZiOiFZW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QAPdPW4hPZiOiFZW .rough-node .label text,#mermaid-svg-QAPdPW4hPZiOiFZW .node .label text,#mermaid-svg-QAPdPW4hPZiOiFZW .image-shape .label,#mermaid-svg-QAPdPW4hPZiOiFZW .icon-shape .label{text-anchor:middle;}#mermaid-svg-QAPdPW4hPZiOiFZW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QAPdPW4hPZiOiFZW .rough-node .label,#mermaid-svg-QAPdPW4hPZiOiFZW .node .label,#mermaid-svg-QAPdPW4hPZiOiFZW .image-shape .label,#mermaid-svg-QAPdPW4hPZiOiFZW .icon-shape .label{text-align:center;}#mermaid-svg-QAPdPW4hPZiOiFZW .node.clickable{cursor:pointer;}#mermaid-svg-QAPdPW4hPZiOiFZW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QAPdPW4hPZiOiFZW .arrowheadPath{fill:#333333;}#mermaid-svg-QAPdPW4hPZiOiFZW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QAPdPW4hPZiOiFZW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QAPdPW4hPZiOiFZW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QAPdPW4hPZiOiFZW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QAPdPW4hPZiOiFZW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QAPdPW4hPZiOiFZW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QAPdPW4hPZiOiFZW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QAPdPW4hPZiOiFZW .cluster text{fill:#333;}#mermaid-svg-QAPdPW4hPZiOiFZW .cluster span{color:#333;}#mermaid-svg-QAPdPW4hPZiOiFZW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QAPdPW4hPZiOiFZW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QAPdPW4hPZiOiFZW rect.text{fill:none;stroke-width:0;}#mermaid-svg-QAPdPW4hPZiOiFZW .icon-shape,#mermaid-svg-QAPdPW4hPZiOiFZW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QAPdPW4hPZiOiFZW .icon-shape p,#mermaid-svg-QAPdPW4hPZiOiFZW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QAPdPW4hPZiOiFZW .icon-shape .label rect,#mermaid-svg-QAPdPW4hPZiOiFZW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QAPdPW4hPZiOiFZW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QAPdPW4hPZiOiFZW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QAPdPW4hPZiOiFZW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 登录控制台
配置管理
配置列表
点击 + 新建配置
填写表单:Data ID / Group / 格式 / 内容
发布
列表中出现该配置
详情:查看 / 编辑 / 回滚历史版本
表单长这样:
doc
+--------------------------------------------------------------------------+
| 新建配置 |
+--------------------------------------------------------------------------+
| Data ID * [ userservice-dev.yaml ] |
| Group * [ DEFAULT_GROUP v] |
| 描述 [ userservice 开发环境配置 ] |
| 配置格式 * ( ) TEXT (•) YAML ( ) JSON ( ) XML ( ) HTML |
+--------------------------------------------------------------------------+
| 配置内容 |
| +--------------------------------------------------------------------+ |
| | pattern: | |
| | name: 开发环境dev | |
| | dateformat: yyyy-MM-dd HH:mm:ss | |
| +--------------------------------------------------------------------+ |
+--------------------------------------------------------------------------+
| [ 发布 ] [ 返回 ] |
+--------------------------------------------------------------------------+
要点:
- Data ID 必填且必须唯一 ,按
[服务名]-[profile].[后缀名]写,本例是userservice-dev.yaml。 - Group 一般不用动 ,保持
DEFAULT_GROUP;只有明确要按业务分组时才改。 - 描述随意填,但建议写清"哪个服务、哪个环境",回滚时能救命。
- 配置格式选 YAML ,要和
bootstrap.yml里的file-extension: yaml对齐。 - 配置内容不是把
application.yml整份粘过来,见下一节。
发布之后回到配置列表就能看到这条记录了。点「详情」可以查看和编辑;每次编辑发布都会生成一个历史版本,需要时可以回滚到任意历史版本------这一点是配置中心相对本地文件管理实打实的优势:本地文件改错了,没提交过就只能靠记忆反推。
配置内容里到底放什么
这是最容易用错的地方。不是所有配置都有资格上 Nacos。
判断标准只有一个:这个配置将来有没有可能需要热更新?
需要热更新的,通常是两类:
| 类型 | 例子 | 为什么需要热更新 |
|---|---|---|
| 开关类 | 活动开关、灰度开关、true 走 A 逻辑 / false 走 B 逻辑 |
业务上要随时开关,不可能每次都发版 |
| 模板 / 格式类 | 日期格式、文案模板、规则表达式 | 运营或产品想调整格式,但要重启服务才能生效就太笨重 |
不需要热更新的,就留在本地:
- 数据库连接地址、账号密码;
- 端口、日志级别这类基本不动的配置;
- Nacos 地址本身。
这份 userservice-dev.yaml 的内容示例,正好一个开关/文本类 + 一个模板类:
yaml
pattern:
name: 开发环境dev
dateformat: yyyy-MM-dd HH:mm:ss
pattern.dateformat 就是上面说的"模板类":初始定义成 yyyy-MM-dd HH:mm:ss,哪天想改成只显示年月日,在 Nacos 上改一下、发布,服务直接生效,不用重启。
pattern.name 这个例子更适合用来说明覆盖关系 。仓库里 userservice 本地的 application.yml 里本来就有一行:
yaml
pattern:
name: 本地环境local
现在远程也定义了同名的 pattern.name,那么最终生效的是远程那一份 (开发环境dev)。同名的 key 由远程覆盖本地------这是下一节要讲的优先级。
Nacos 配置与本地配置的关系
微服务启动时读到的配置不是"二选一",而是本地和远程合并,然后同名的键按优先级决出胜负。
在本节的场景里,本地仍然要保留两份东西:bootstrap.yml(Nacos 地址、应用名、profile、后缀名)和 application.yml 里的基础配置(端口、数据源等)。Nacos 地址千万不能放到 Nacos 上------服务还没连上 Nacos,怎么可能从 Nacos 上读到 Nacos 的地址?这就是典型的"鸡生蛋"。
优先级从高到低:
| 优先级 | 配置来源 | 示例 |
|---|---|---|
| 高 | [服务名]-[profile].[后缀] |
userservice-dev.yaml |
| ↑ | [服务名].[后缀] |
userservice.yaml |
| ↑ | extension-configs 扩展配置 |
手动指定的额外 dataId |
| ↑ | shared-configs 共享配置 |
跨服务共用配置 |
| 低 | 本地 application.yml |
pattern.name: 本地环境local |
记住一条就够用:带 profile 的那份最优先,本地配置兜底。
多个服务共享配置(简述)
除了每个服务自己那份 userservice-dev.yaml,Nacos 上还有一份不带 profile 的 userservice.yaml。它不绑定环境,因此 dev 和 test 都能读到,用来放多个环境共用的配置。
跨服务的共享则靠 shared-configs / extension-configs 这两组配置项,在 bootstrap.yml 里显式列出要额外读取的 dataId。这块内容细节较多,留到配置共享那一节展开,这里只需要知道:共享配置的优先级低于服务自己的配置,所以服务特有配置可以放心覆盖共享值。
几个真会踩的坑
| 现象 | 原因 | 处理 |
|---|---|---|
服务启动正常,但配置项读出 null |
dataId 名字与服务名 / profile / 后缀对不上,静默失败 | 按 [服务名]-[profile].[后缀] 逐段核对;看启动日志里实际请求的 dataId |
| 想连 Nacos 却连不上 | 把 server-addr 也放到了 Nacos 上 |
Nacos 地址必须写在本地 bootstrap.yml |
| 配置读到了但解析报错 | 格式选了 YAML,内容缩进写错(用了 Tab 或多缩进一层) | YAML 一律用空格缩进,粘贴后检查层级 |
| 改了配置没生效 | 改完没点「发布」,还是草稿状态 | 编辑后必须发布才会生成新版本 |
| dev 改了配置,test 也跟着变 | 多环境共用了一个 namespace | 每个环境独立 namespace,靠隔离而不是靠命名区分 |
| 配置内容含中文时报解析异常 | YAML 里的值没加引号,特殊字符被当成语法 | 含中文 / 特殊符号的值加英文引号,如 name: "开发环境dev" |
排查顺序建议固定成:先确认 dataId 拼对了没有 → 再确认发布成功了没有 → 最后才怀疑代码。 顺序反过来会浪费很多时间,因为前三步错了的表现和代码写错几乎一样。
API 速览
| 项 | 值 |
|---|---|
| 控制台地址 | http://localhost:8848/nacos |
| 默认账号 / 密码 | nacos / nacos |
| 配置管理入口 | 配置管理 → 配置列表 → + |
| dataId 命名 | [服务名]-[profile].[后缀名] |
| dataId 拼接来源 | ${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} |
| 默认命名空间 | public |
| 默认分组 | DEFAULT_GROUP |
| 支持格式 | YAML、Properties(建议后缀写全称 yaml) |
配置管理客户端依赖(本节只做添加,依赖在读取配置时引入,此处一并给出以免遗漏):
xml
<!-- Nacos 配置管理依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
官方文档
总结
- 配置中心要解决的是两件事:集中管理 和热更新,前者省人工,后者省发版。
- 一份配置由
namespace+group+dataId三个坐标定位,namespace管环境隔离,group管业务分组,dataId管具体那一份。 dataId必须按[服务名]-[profile].[后缀名]写,因为它会被bootstrap.yml里的三个值拼出来,写错就是拉不到配置且不报错的静默失败。- 上 Nacos 的只放需要热更新的开关类、模板类配置,数据库地址这种不动的东西留在本地;Nacos 地址本身更不能上移。
- 远程与本地同名 key 时远程优先,带 profile 的那份最优先,本地配置兜底。
- 添加配置只是第一步,微服务怎么把这份配置拉下来并实现热更新,是紧接着要解决的问题。