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 DATABASEUSE 语句,需要自己补在文件开头。

条件 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