nacos — 配置中心
pkg/core/nacos 把 Nacos 配置中心接到 verticle 上:
本地文件是底座,Nacos 覆盖它有的键。框架段(http/log/db/redis…)
来自一个 data_id,服务段来自另一个;服务段变化实时生效,走和改本地文件
完全一样的 ConfigWatcher。业务代码不 import Nacos SDK。
运行
需要一个真实的 Nacos 服务端(make dev 不含它,用官方镜像即可)。
HTTP 监听 :8080。先在 Nacos 控制台建好 namespace 与两个配置:
| 项 | 值 |
|---|---|
| namespace | demo(填的是 namespace id,不是显示名) |
| group | DEMO |
| data_id | matex.yaml:框架段,例如 http: {addr: ":8080"}、log: {level: info} |
| data_id | demo.yaml:服务段,例如 greeting: "Hello from Nacos, " |
go run ./examples/nacos
curl -i localhost:8080/api/v1/greeting/world
# 在控制台把 demo.yaml 改成 "Hi from Nacos, " → 保存
# 日志出现 "config reloaded from the center",接口立刻返回新文案,version +1
鉴权
平台化部署通常要按 namespace / group 隔离,鉴权两种方式都支持,且 二选一——同时配置会在启动时报错:
| 方式 | 配置 |
|---|---|
| 账号密码 | username + password(走 Nacos 登录接口) |
| 阿里云 RAM AK/SK | access_key + secret_key(走 AK/SK 凭据提供者) |
| RAM 临时凭证(STS) | AK/SK 之外再加 sts_token(映射到 SDK 的 RamConfig.SecurityToken) |
namespace 填 id,不填显示名
namespace 与 group 参与每一次请求,是平台/团队/环境隔离的边界。填错成显示名
会导致取回空内容、启动失败。
配置(config.yaml)
nacos:
server_addr: ${NACOS_ADDR:localhost:8848} # 逗号分隔多个地址,可带 https://
namespace: demo # namespace id
group: DEMO
data_id: matex.yaml # 框架段
service_data_id: demo.yaml # 服务段;删掉则服务段继续读本地文件
type: yaml # yaml | properties | text
timeout: 10s # 首次拉取预算
username: ${NACOS_USERNAME:nacos} # 二选一:账号密码
password: ${NACOS_PASSWORD:nacos}
# access_key: ${NACOS_ACCESS_KEY:} # 或:RAM AK/SK
# secret_key: ${NACOS_SECRET_KEY:}
# sts_token: ${NACOS_STS_TOKEN:} # 临时凭证时再加
nacos: 段只能放在本地文件里:它决定去哪里找配置中心,配置中心自己说不出来。
不配它、也不传 verticle.WithConfig(...),进程根本不连 Nacos。
启动与热更
flowchart TD
A["读本地 config.yaml"] --> B{"有 nacos: 段\n或有 WithConfig?"}
B -- 否 --> L["直接用文件 + 文件 watch"]
B -- 是 --> C["首次拉两个 data_id\n(超时 10s,失败即退出)"]
C --> D["框架段逐键覆盖文件\n(本地为底座)"]
C --> E["服务段:有 service_data_id 用远端\n否则用文件"]
D --> F["启动"]
E --> F
F --> G{"任一 data_id 变化?"}
G -- 服务段 --> H["ConfigWatcher 实时生效"]
G -- 框架段 --> I["WARN:需要重启 / 滚动发布"]
H --> G
ConfigWatcher 回调;框架段(连接池、监听、tracing)是启动时建好的,
变化只告警不应用,等重启或滚动发布。
几个刻意的选择:
- data_id 不存在 = 启动失败。Nacos 对不存在的 key 回答空内容,若当成"空配置", 服务会带着缺省值悄悄跑起来——比直接退出更难排查。
- 首次拉取必须成功:不会用本地缓存快照顶着跑。
- 远端 map 逐键合并:远端只写
redis: {addr: ...}时,本地redis.password仍保留。 - 重载按整体代际:任一 data_id 变化,重新拉取两个 data_id 再一起交付, 不会出现新旧混搭。
- 半坏的重载不生效:重载拉取/解析失败,保留上一代配置继续跑,不应用一半。
业务代码长什么样
Setup 用 env.DecodeService 读服务段;OnServiceConfigChange
热更并拒绝未知键——否则拼错的键会被解码成零值,服务带着空白配置继续跑。
除了这行校验,业务代码和"只读文件"完全一样。
func (s *nacosService) Setup(ctx context.Context, env *verticle.Env) error {
var cfg demoConfig
return env.DecodeService(&cfg) // 合并后的结果,和读文件一样
}
func (s *nacosService) OnServiceConfigChange(raw map[string]any) {
var cfg demoConfig
if unknown := config.UnknownKeys(raw, &cfg); len(unknown) > 0 {
obs.Warn(context.Background(), "reload rejected: unknown keys", "keys", unknown)
return
}
if err := config.ParseMap(raw, &cfg); err != nil {
obs.Warn(context.Background(), "reload rejected", "err", err)
return
}
s.mu.Lock()
s.greeting = cfg.Greeting
s.version++
s.mu.Unlock()
}
远端服务段没有 tag 级校验
配置中心里的服务段是平铺的 map,
ParseMap 只是把值搬进结构体,
没有 tag 级校验;本地文件热更有 tag 级解码把关。所以重载前必须用
config.UnknownKeys 拒绝未知键。
测试
main_test.go 不需要 Nacos:实现 verticle.ConfigSource 的假中心
起一次 verticle.Run,验证"启动读远端、推送后实时生效",以及"带未知键的
重载被拒绝、运行中配置保持不变"。
go test ./examples/nacos
相关
- 配置:tag 语义、
${VAR}展开、热更,以及配置中心的合并规则。 - pkg/core 速查:各包入口一览,
nacos这一行。 - examples/config:先看本地文件的 tag 语义,再叠加远端。
- examples/lifecycle:
ConfigWatcher的最小用法,与 Nacos 服务段共用。 - Skill:
skills/matex-setup-nacos/SKILL.md。 - 示例索引。