Nacos 用 MySQL 持久化:五个必备条件,缺一个就起不来

Nacos 默认用内嵌 Derby,容器一重建,配置与注册信息全丢,所有服务都起不来。本篇梳理 MySQL 持久化的五个必备条件,附可直接抄的 compose 片段。

一、为什么必须做持久化

Nacos 默认使用内嵌 Derby 数据库。容器一旦被 docker compose down 或重建,所有配置与注册信息全部丢失。

在"配置中心"这个角色上,这个后果很严重:应用启动时要 spring.config.import 从 Nacos 拉配置(而且是非 optional 的强依赖),配置没了 → Nacos 里的 dataId 全没了 → 所有服务启动失败。

所以 compose 里要做这件事:

  nacos:
    image: nacos/nacos-server:v3.2.4
    environment:
      MODE: standalone
      SPRING_DATASOURCE_PLATFORM: mysql      # ← 切到外部 MySQL
      MYSQL_SERVICE_HOST: mysql
      MYSQL_SERVICE_PORT: 3306
      MYSQL_SERVICE_DB_NAME: ${NACOS_DB_NAME:-nacos_config}
      MYSQL_SERVICE_USER: ${MYSQL_USER:-api_cloud}
      MYSQL_SERVICE_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_SERVICE_DB_PARAM: >-
        characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai

注意:SPRING_DATASOURCE_PLATFORM=mysql 只是"声明意图"。 声明之后,下面五个条件必须同时满足,缺任何一个都会启动失败——而且失败方式各不相同,报错也不指向真正的原因。

二、五个必备条件

条件 1:库名必须与建库语句完全一致

-- mysql/init/01-init-nacos.sql
CREATE DATABASE nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE nacos_config;
-- ... 建表语句

对应环境变量:

MYSQL_SERVICE_DB_NAME: nacos_config

两边名字必须逐字相同。 不一致的表现是 Unknown database 'nacos_config'——这个还算直白,但要注意:mysql/init/*.sql 只在数据卷为空时执行(详见 D1 篇),如果数据库是你手动建的、名字打错了,改 SQL 文件不会生效。

条件 2:表结构必须用官方 schema,且版本要对

Nacos 3.x 的 schema 与 2.x 不兼容。3.x 共 10 张表,其中必须包含:

config_info_gray        ← 3.x 新增,灰度发布用

而 2.x 时代的这三张表已经废弃:

config_info_aggr
config_info_beta
config_info_tag

用错版本 schema 的表现:Nacos 能启动,但访问配置时抛 SQL 异常(表不存在 / 列不存在)。比直接起不来更麻烦。

官方 schema 的正确获取方式

https://raw.githubusercontent.com/alibaba/nacos/master/distribution/conf/mysql-schema.sql

只有 master 分支能取到。 实测这两条都 404:

https://raw.githubusercontent.com/alibaba/nacos/3.2.4/distribution/conf/mysql-schema.sql   ❌
https://cdn.jsdelivr.net/gh/alibaba/nacos@3.2.4/distribution/conf/mysql-schema.sql          ❌

另外注意:官方 schema 里不含 CREATE DATABASE 和 USE 语句,需要自己补在文件开头。

条件 3:必须显式授权(最容易漏的一条)

MySQL 官方镜像的初始化逻辑是这样的:

-- 镜像内部大致做的事
CREATE DATABASE IF NOT EXISTS ${MYSQL_DATABASE};
GRANT ALL ON `${MYSQL_DATABASE}`.* TO '${MYSQL_USER}'@'%';    -- ← 只授权这一个库

它只把 MYSQL_USER 授权给 MYSQL_DATABASE 指定的那一个库。

而本项目的 compose 里,MySQL 服务创建的是业务库:

  mysql:
    environment:
      MYSQL_DATABASE: ${MYSQL_DATABASE:-api_lfzx_wang}    # 业务库
      MYSQL_USER: ${MYSQL_USER:-api_cloud}

Nacos 用的是另一个库 nacos_config,同一个用户 api_cloud 去连它就会被拒绝:

Java.sql.SQLException: Access denied for user 'api_cloud'@'%' to database 'nacos_config'

必须在 init SQL 里补一条显式授权:

-- 01-init-nacos.sql 末尾
GRANT ALL PRIVILEGES ON nacos_config.* TO 'api_cloud'@'%';
FLUSH PRIVILEGES;

这条之所以容易漏:Nacos 的网络、密码、库名全都对,错误信息却是 "Access denied"——容易往"密码错了"的方向查,而实际上是权限范围问题。

条件 4:JDBC 参数必须补 allowPublicKeyRetrieval

MySQL 8 默认认证插件是 caching_sha2_password。首次握手时,客户端需要向服务端索取公钥来加密密码——而这个动作默认是被禁止的:

Java.sql.SQLException: Public Key Retrieval is not allowed

Nacos 官方镜像的 MYSQL_SERVICE_DB_PARAM 默认值不包含这个参数,必须自己补:

allowPublicKeyRetrieval=true

本项目的完整参数:

characterEncoding=utf8
&connectTimeout=10000
&socketTimeout=30000
&autoReconnect=true
&useSSL=false
&allowPublicKeyRetrieval=true
&serverTimezone=Asia/Shanghai

useSSL=false 与超时参数也不是可选的美化项:容器网络下 TLS 协商会明显拖慢启动,而 Nacos 的 healthcheck 时间窗本来就紧。

条件 5:start_period 不能小于 180 秒

Nacos 3.x 首次启动实测需要 150–240 秒(JVM 预热 + gRPC + 插件加载)。

    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8848/nacos/ >/dev/null || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 20
      start_period: 180s     # ← 关键

start_period 过短的后果是连锁的:

start_period 设成 30s
  → Nacos 还在启动,healthcheck 判定失败
  → 容器被标记 unhealthy
  → 依赖它的四个应用服务:depends_on: condition: service_healthy 永远不满足
  → 四个业务服务全部起不来

表象是"四个业务服务启动失败",根因在 Nacos 的 healthcheck 参数上。

探针路径用 GET /nacos/ 而不是 /nacos/v3/console/health/readiness —— 后者在 3.2.4 上实测 404(详见 A2 篇)。

三、诊断口诀:日志里的"启动中"可能是假的

调试 Nacos 启动时,日志里会看到一堆 banner 和初始化信息,看起来像"正在启动"。但有一行必须认识:

ThreadPoolManager Start destroying ThreadPool

这行的含义是"上一次启动失败、进程正在退出"。

如果你看到它,说明你看到的不是"正在启动",而是一次失败的启动 + 被 restart: always 拉起来的新进程。此时不要看当前这段日志,要往前翻,找那一次失败的真实异常。

restart: always 会让这个判断变得很困难——日志里会交替出现"销毁线程池"和"启动 banner",看起来像正常启动的抖动。

排查建议:

# 看完整日志,找第一次失败的位置
docker logs api-cloud-nacos 2>&1 | head -100

# 或者临时把 restart 改成 no,让失败停下来
docker update --restart=no api-cloud-nacos

四、一个容易忽略的收尾:首次登录强制改密码

Nacos 3.x 首次登录控制台会强制修改默认密码。 改完之后必须同步更新 .env:

NACOS_PASSWORD=<新密码>

否则会出现这个循环:

控制台登录 → 改了密码 → Nacos 容器重启(比如 docker compose down)
  → Nacos 又用 .env 里的旧密码初始化
  → 你新改的密码失效

这与"D1 密码唯一真源"是同一类问题:密码存在两处(Nacos 内部 + .env),哪一处改了就漂移了。

五、验证清单

# 1. 库与表都在(应为 10 张 nacos 相关表)
docker exec -i api-cloud-mysql mysql -uapi_cloud -p"$MYSQL_PASSWORD" \
  -e "USE nacos_config; SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='nacos_config';"

# 2. 关键表存在
docker exec -i api-cloud-mysql mysql -uapi_cloud -p"$MYSQL_PASSWORD" \
  -e "USE nacos_config; SHOW TABLES LIKE 'config_info%';"
# 必须包含 config_info_gray

# 3. Nacos 健康
docker compose ps nacos      # 应为 Up (healthy)
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8848/nacos/    # 非 000

# 4. 持久化真的生效(关键验证)
docker compose restart nacos
# 等 2-3 分钟,登录控制台 → 配置列表应该还在

第 4 步是最有说服力的验证。只看"启动成功"不能证明持久化生效——Nacos 完全可能用内嵌 Derby 也能启动成功,然后在你 down 的时候把数据一起带走。

六、复盘

条件 失败表现 误导方向
库名不一致 Unknown database 较直白
schema 版本错 启动成功但访问配置报 SQL 异常 容易当成 Nacos bug
未显式授权 Access denied 容易误判为密码错
缺 allowPublicKeyRetrieval Public Key Retrieval is not allowed 较直白,但报错在 JDBC 层
start_period 过短 四个业务服务起不来 根因被藏在另一个容器里

两条可复用的经验:

  1. Nacos 起不来时,先看它自己的日志,别急着看依赖它的服务。 本项目里"四个业务服务全部启动失败"这个现象,最终指向的是 Nacos 的 healthcheck 参数——一个跟业务服务毫无关系的地方。

  2. docker compose restart 不等于重建。 如果你改的是 environment,必须 up -d。这条在本项目里踩了不止一次,后面 D1 篇还会再遇到。

原创文章,作者:ECHO陈文,如若转载,请注明出处:https://www.luweipai.cn/ops/1789977059/

  • 0 赞