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
				
远端是本地文件之上的覆盖层。服务段热更与文件 watch 走同一条 ConfigWatcher 回调;框架段(连接池、监听、tracing)是启动时建好的, 变化只告警不应用,等重启或滚动发布。

几个刻意的选择:

业务代码长什么样

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

相关