简介:面向需要在国产化环境中将 Nacos 注册配置中心接入达梦数据库的开发与运维人员,这份资料提供一套可直接落地的适配方案,重点解决默认数据源不兼容、初始化脚本缺失以及编译部署门槛较高的问题。压缩包共 92 个文件,约 297.51MB,包含 Windows 与 Linux 可运行服务端、可导入 IDEA 二次修改并编译的源码工程、达梦建库 SQL 脚本、启动脚本,以及运行日志、Raft 元数据和配置示例等辅助文件,便于对照排查启动与持久化问题。已有 895 人学习,适合在信创改造、内网部署和测试验证场景中减少重复踩坑、快速完成环境搭建。拿到后既可按包内已编译产物直接部署,也可基于源码调整连接参数、适配逻辑与脚本内容,兼顾开箱使用和后续定制,对注册中心持久化、集群配置及数据库连接初始化具有直接参考价值。
1. 一次相关替换踩出来的结论:nacos 达梦组合,改 URL 只是开头
某公司的中间件做替换,MySQL 被移出生产环境,Nacos 的配置和服务注册数据要落到达梦上。运维最开始的想法很朴素:把db.url.0从jdbc:mysql://改成jdbc:dm://,驱动包一换,重启,完事。结果容器起不来,日志停在Cannot load JDBC driver class;驱动补上之后,又卡在建表脚本上。等绕过这两关,配置列表能打开了,点进去却是空页。
nacos 达梦这件事,难点从来不在连接串,而在 Nacos 内部散落的 MySQL 方言——mysql-schema.sql里的AUTO_INCREMENT、mapper 里的now()、分页的LIMIT offset, size,每一处都要单独改。
这篇内容适合两类人:正在做数据库替换、手里有 Nacos 集群要搬家的运维和中间件同学;以及想先在小环境验证可行性、不想动生产源码的开发者。下面按「定路线 → 建表 → 接连接 → 排查 → 验证」推一遍,每一步都给出可直接抄的片段。
2. 先定路线:Nacos 2.2 的 datasource SPI 与三条改造成本对比
动手之前必须先把版本号钉死,因为 Nacos 在 2.2 前后对多数据源的支持方式完全不同。选错路线,后面所有工作都要返工。
2.1 三条可选路线与它们的真实成本
业内常见做法基本是这三种,没有第四种。
第一种是让达梦开 MySQL 兼容档,SQL 一行不改。听起来最省事,实际上只能解决一部分语法差异,LIMIT offset, size这种分页写法和now()这类函数仍然要单独确认;更麻烦的是兼容参数是实例级的,同一个实例上还有其他业务库时,改参数是要评估的。我只建议在 POC 阶段用这条路线快速验证连通性,不要带到生产。
第二种是拉 Nacos 源码,直接改各模块resources/mapper下的 XML。这条路在 1.4 到 2.1 的版本上是唯一选择,改动量中等,但每次升级 Nacos 都要把改动重新 merge 一遍,长期维护成本高。
第三种是 2.2 之后提供的 datasource 插件机制:把达梦方言的 Mapper 实现单独打成一个 jar,放到 Nacos 的插件目录里,主程序不用动。这是目前比较干净的方案,代价是要跟着 Nacos 的小版本重新编译插件。
| 路线 | 适用版本 | 改动位置 | 升级成本 | 主要风险 |
|---|---|---|---|---|
| MySQL 兼容模式 | 任意 | 达梦实例参数 | 低 | 分页与时间函数仍可能不兼容,参数影响同实例其他库 |
| 改 mapper XML | 1.4 ~ 2.1 | Nacos 源码 | 高,每次升级重 merge | 改漏一处就在运行期才暴露 |
| datasource 插件 | 2.2 及以上 | 独立 jar + plugins 目录 | 中,需重新编译 | 接口签名随小版本漂移 |
2.2 三步确认你手上的包属于哪种结构
不要凭记忆判断版本,直接拆包看。
# 1) 看有没有 2.2 引入的 datasource 插件相关类 unzip -l nacos-server.jar | grep -i "plugin/datasource" | head -20 # 2) 看 mapper 是以 XML 形式存在,还是以 Java 实现类存在 unzip -l nacos-server.jar | grep -iE "BOOT-INF/classes/mapper/.*\.xml|MapperByMySql" # 3) 确认具体版本号,插件接口签名跟着它走 unzip -p nacos-server.jar META-INF/MANIFEST.MF | grep -i "Implementation-Version"第一条命令有输出,说明是插件路线,可以直接外挂;第二条命令如果列出一堆ConfigInfoMapper.xml、GroupCapacityMapper.xml,说明是老结构,得改源码。第三条拿到的版本号要记下来,后面编译插件时pom.xml里的nacos-datasource-plugin依赖必须和它对齐,差一个小版本就可能出现抽象方法没实现完的编译错误。
提示:拆包前先备份一份原始
nacos-server.jar。改源码路线上线后如果发现漏改,回滚靠的就是这份备份,比重新打包快得多。
2.3 达梦侧的三个初始化参数,建库之后就改不动了
很多人栽在这一步:表建了一半发现索引键超长,或者表名大小写对不上,而PAGE_SIZE、CASE_SENSITIVE这两个参数在实例初始化之后是改不了的,只能重建库。所以先在测试环境用dminit按目标参数建一个实例,验证通过再去动生产。
# 初始化实例:页大小 16K,大小写不敏感 ./dminit path=/dm/data DB_NAME=DAMENG PAGE_SIZE=16 CASE_SENSITIVE=NPAGE_SIZE=16是为了给唯一索引留空间。Nacos 的config_info上有一个(data_id, group_id, tenant_id)的唯一索引,三个字段在 UTF-8 下按 3 字节一字符算,加起来能到 1500 字节以上,8K 页的索引键长度上限容易顶到边。CASE_SENSITIVE=N让config_info和CONFIG_INFO等价,省掉后面到处加引号的麻烦。COMPATIBLE_MODE建议调到 MySQL 兼容档,具体取值以你手上版本的《DM8 系统管理员手册》为准,不同小版本编号有出入,别照抄网上的数字。
用户和模式单独建,不要用 SYSDBA 跑业务:
-- 用 SYSDBA 登录后执行 CREATE TABLESPACE "NACOS" DATAFILE 'NACOS.DBF' SIZE 512 AUTOEXTEND ON NEXT 64 MAXSIZE 4096; CREATE USER "NACOS" IDENTIFIED BY "Abc123456" DEFAULT TABLESPACE "NACOS"; GRANT RESOURCE, PUBLIC TO "NACOS";DEFAULT TABLESPACE决定了后续建表落在哪个表空间,RESOURCE角色给了建表建索引的权限,够用了,不需要 DBA。建完用 NACOS 用户登录一次,确认SELECT USER FROM DUAL返回的是NACOS,否则后面 Nacos 连上去会默认落到 SYSDBA 的模式里,表建在别处,排查起来很费时间。
3. 建表先过关:mysql-schema.sql 翻成达梦 DDL 的六处改动
Nacos 发行包conf目录下有mysql-schema.sql,但它一行都不能直接在达梦上跑。这一章把它拆开,逐类改。
3.1 类型与自增的映射关系
先看对照表,这是改动量最大的一块。
| MySQL 写法 | 达梦写法 | 说明 |
|---|---|---|
BIGINT AUTO_INCREMENT | BIGINT IDENTITY(1,1) | 达梦的自增列,一个表只能有一个 |
LONGTEXT | CLOB | 配置内容字段,别用 VARCHAR,长度不够 |
DATETIME | TIMESTAMP | 达梦的 DATETIME 精度只到秒,Nacos 要毫秒 |
DEFAULT CURRENT_TIMESTAMP | DEFAULT SYSDATE | 语义一致 |
ON UPDATE CURRENT_TIMESTAMP | 无 | 达梦不支持,靠应用层显式赋值 |
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 | 删除 | 存储引擎和字符集在达梦是库级参数 |
ON UPDATE CURRENT_TIMESTAMP这一条要特别注意。Nacos 里gmt_modified的更新,一部分靠 SQL 里写now(),一部分靠列定义的自动更新。前者改 SQL 就行,后者在达梦上没有等价物,只能保证 mapper 里的 UPDATE 语句都显式带上gmt_modified = SYSDATE。漏了不会报错,只会让配置列表按修改时间排序时出现诡异结果。
3.2 一份可以直接执行的 config_info 建表脚本
下面这段是核心表的写法,其余表(his_config_info、config_info_beta、config_info_tag、config_tags_relation、tenant_info、users、roles、permissions、group_capacity、tenant_capacity)按同样的规则推即可。
-- 以 NACOS 用户登录后执行 CREATE TABLE "CONFIG_INFO" ( "ID" BIGINT IDENTITY(1,1) NOT NULL, "DATA_ID" VARCHAR(255) NOT NULL, "GROUP_ID" VARCHAR(128), "CONTENT" CLOB NOT NULL, "MD5" VARCHAR(32), "GMT_CREATE" TIMESTAMP DEFAULT SYSDATE NOT NULL, "GMT_MODIFIED" TIMESTAMP DEFAULT SYSDATE NOT NULL, "SRC_USER" VARCHAR(255), "SRC_IP" VARCHAR(50), "APP_NAME" VARCHAR(128), "TENANT_ID" VARCHAR(128) DEFAULT '', "C_DESC" VARCHAR(255), "C_USE" VARCHAR(10), "EFFECT" VARCHAR(10), "TYPE" VARCHAR(64), "C_SCHEMA" VARCHAR(255), "ENCRYPTED_DATA_KEY" VARCHAR(1024) NOT NULL DEFAULT '', CONSTRAINT "PK_CONFIG_INFO" PRIMARY KEY ("ID") ); -- 唯一索引与普通索引必须单独建,达梦不支持在 CREATE TABLE 里内联 KEY CREATE UNIQUE INDEX "UK_CONFIG_INFO_DATAGROUPTENANT" ON "CONFIG_INFO" ("DATA_ID", "GROUP_ID", "TENANT_ID"); CREATE INDEX "IDX_CONFIG_INFO_GMT_MODIFIED" ON "CONFIG_INFO" ("GMT_MODIFIED"); CREATE INDEX "IDX_CONFIG_INFO_GMT_CREATE" ON "CONFIG_INFO" ("GMT_CREATE");IDENTITY(1,1)的第一个参数是起始值,第二个是步长,和 MySQL 的AUTO_INCREMENT=1对应。主键统一写成约束而不是列内联,是为了和 Nacos 后续可能的批量插入保持兼容——有些 mapper 会显式插入 ID,自增列必须允许SET IDENTITY_INSERT语义,达梦这里的行为和 SQL Server 类似,遇到时按手册处理。
索引之所以拆出来,是因为达梦的CREATE TABLE语法不认 MySQL 的UNIQUE KEY xxx (...)内联写法,会直接报语法错误。把mysql-schema.sql里所有内联的KEY、UNIQUE KEY抠出来做成独立CREATE INDEX,是机械化操作,但一个字都不能漏——漏掉唯一索引的后果在第 5 章会讲。
3.3 大小写、保留字和字段长度的边界
即使初始化时设了CASE_SENSITIVE=N,我还是建议建表脚本里统一用大写加双引号。理由很实际:生产环境的实例参数未必由你决定,而 Nacos 的 mapper 里表名全是小写不加引号,一旦实例是大小写敏感的,不加引号的config_info会被折成CONFIG_INFO去找,用大写加引号建表在两种参数下都能命中。
保留字这块,Nacos 的表名users、roles、permissions在达梦里一般不冲突,但LEVEL、TYPE、SIZE这类列名在某些版本上要做转义。保险做法是建完表之后立刻做一次全表SELECT * FROM 表名 WHERE 1=0,十条表全跑一遍,有保留字问题会当场报错,比等到运行期从业务报错反推省事得多。
字段长度上,DATA_ID保持 255、GROUP_ID和TENANT_ID保持 128,不要自作主张缩短。Nacos 客户端那边对这几个长度没有强校验,缩短之后写入超长值会被数据库截断或拒绝,表现出来是「发布成功但查不到」,属于典型的黑匣子问题。
4. 接上连接:驱动、连接池与 Mapper 的四处落地改动
表建好之后,才是连接层。这一段四步,缺一步都起不来。
4.1 驱动 jar 放在哪个目录才被加载
Nacos 打成的是 Spring Boot fat jar,外部 jar 不在BOOT-INF/lib里是不会进 classpath 的。两种放法:
# 放法一:丢进插件目录,靠 loader.path 加载 cp DmJdbcDriver18.jar ${NACOS_HOME}/plugins/ # 确认启动脚本里带了这一行,没有就补上 grep -n "loader.path" ${NACOS_HOME}/bin/startup.sh # 放法二:直接塞进 fat jar 的 BOOT-INF/lib unzip -p nacos-server.jar BOOT-INF/classes/application.properties | head -5 # 先确认能读 zip -u nacos-server.jar BOOT-INF/lib/DmJdbcDriver18.jar第一种干净,升级 Nacos 不用重新打包,但要确认startup.sh里的JAVA_OPT真的带了-Dloader.path=${NACOS_HOME}/plugins;有些版本只对特定目录生效,加载不到时日志里就是那句Cannot load JDBC driver class。第二种粗暴但稳,缺点是每次升级主程序都得重新塞一遍。
驱动类名是dm.jdbc.driver.DmDriver,JDK 8 以上用DmJdbcDriver18.jar,别拿成 JDK 6 那个版本,会报Unsupported major.minor version。
4.2 application.properties 里必须改的那些行
# 走源码改 mapper 的路线时保持 mysql,让 Nacos 取到 MySQL 那套 mapper # 走插件路线时改成你注册的类型名,常见写 dm spring.sql.init.platform=dm db.num=1 db.url.0=jdbc:dm://10.0.0.11:5236?schema=NACOS&useUnicode=true&characterEncoding=utf8 db.user.0=NACOS db.password.0=Abc123456 db.pool.config.driverClassName=dm.jdbc.driver.DmDriver db.pool.config.maximumPoolSize=50 db.pool.config.minimumIdle=5 db.pool.config.connectionTimeout=30000 db.pool.config.validationTimeout=10000 db.pool.config.keepAliveTime=30000 db.pool.config.maxLifetime=600000 db.pool.config.connectionTestQuery=SELECT 1 FROM DUALspring.sql.init.platform这一行是整个改造的分水岭,它的值决定 Nacos 去MapperManager里按哪个类型取 Mapper 实现。插件路线下必须等于你注册的类型名;源码路线下保持mysql不动,因为你改的就是 MySQL 那套 mapper 文件,改了值反而找不到实现。
db.pool.config.*是直接映射到 Hikari 的,maximumPoolSize按 Nacos 官方建议不要低于 20,集群节点多的时候配置推送并发不低。connectionTestQuery在达梦驱动支持 JDBC4 的isValid()时可以不写,但写上更保险——达梦服务端有空闲会话回收策略,连接池里的连接被服务端悄悄掐掉时,没有探活语句的表现就是「跑一晚上,早上第一批请求全超时」。
4.3 插件路线:写一个 dm 的 Mapper 实现并注册 SPI
核心思路是继承官方 MySQL 实现,只覆盖方言相关的方法。
package com.example.nacos.dm; import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoMapper; import com.alibaba.nacos.plugin.datasource.constants.TableConstant; /** * 达梦方言的 config_info Mapper。 * 方法签名以你手上 nacos-datasource-plugin 的接口为准, * 升级 Nacos 后先 javap 看一眼接口有没有加方法。 */ public class ConfigInfoMapperByDm implements ConfigInfoMapper { @Override public String getDataSource() { return "dm"; } @Override public String getTableName() { return TableConstant.CONFIG_INFO; } @Override public String selectConfigInfoLike4PageFetchRows() { // 达梦分页是 LIMIT size OFFSET offset,参数顺序和 MySQL 相反 return "SELECT ID,DATA_ID,GROUP_ID,TENANT_ID,APP_NAME,CONTENT,MD5," + "GMT_CREATE,GMT_MODIFIED,SRC_USER,SRC_IP,C_DESC,C_USE,EFFECT,TYPE,C_SCHEMA," + "ENCRYPTED_DATA_KEY FROM config_info " + "WHERE data_id LIKE ? AND group_id LIKE ? AND tenant_id = ? " + "ORDER BY id LIMIT ? OFFSET ?"; } @Override public String getConfigTagsRelationCountByTag() { // 时间函数:MySQL 的 now() 在达梦上写成 SYSDATE return "SELECT COUNT(*) FROM config_tags_relation WHERE tag_name = ? " + "AND gmt_modified >= SYSDATE - INTERVAL '1' DAY"; } }需要改的就是所有带LIMIT、now()、IFNULL、字符串拼接CONCAT的 SQL。IFNULL在达梦对应NVL,CONCAT对应||,这两类在 Nacos 的 mapper 里出现频率不低,逐个过一遍,别凭印象跳。
注册走 SPI,在resources/META-INF/services/下建一个文件,文件名是实现接口的全限定名,内容是实现类的全限定名:
com.example.nacos.dm.ConfigInfoMapperByDm打成 jar 后丢进${NACOS_HOME}/plugins/,和驱动放在一起。启动日志里能看到插件被扫描到的记录,没有这行日志就说明 SPI 文件路径写错了。
4.4 源码路线:改 XML 里最容易漏的四类 SQL
老版本没有插件机制,只能改 XML。改动集中在三个地方:分页、时间函数、空值函数。
<!-- 改前:MySQL 的 LIMIT offset, size --> <select id="selectConfigInfoLike4Page" resultMap="configInfoResultMap"> SELECT * FROM config_info WHERE data_id LIKE ? AND group_id LIKE ? ORDER BY id LIMIT #{start}, #{pageSize} </select> <!-- 改后:达梦的 LIMIT size OFFSET offset,两个参数的绑定位置要跟着换 --> <select id="selectConfigInfoLike4Page" resultMap="configInfoResultMap"> SELECT * FROM config_info WHERE data_id LIKE ? AND group_id LIKE ? ORDER BY id LIMIT #{pageSize} OFFSET #{start} </select>这里是最容易翻车的地方:MySQL 是「偏移量在前、条数在后」,达梦是「条数在前、偏移量在后」,两个#{}的位置必须同时互换。只改关键字不改顺序,页面不会报错,只是翻到第二页开始数据重复或跳页,靠肉眼很难发现。
时间函数同理,把now()全部换成SYSDATE,IFNULL(x, '')换成NVL(x, '')。改完之后grep -rn "now()\|IFNULL\|LIMIT.*,.*#{" nacos-*/src/main/resources/mapper/再扫一遍,确认没漏网。改完重新mvn -Prelease-nacos clean install -DskipTests打包,注意-Prelease-nacos这个 profile,不加打出来的包缺东西。
5. 避坑记录:从启动报错到分页错乱的五个翻车现场
5.1 启动直接挂:Cannot load JDBC driver class 'dm.jdbc.driver.DmDriver'
现象:容器起不来,日志里只有这一行,没有更详细的堆栈。
原因:驱动 jar 放到了${NACOS_HOME}/plugins/目录,但startup.sh里的JAVA_OPT没有对应的-Dloader.path,或者路径变量展开后指向了别处。Spring Boot 的PropertiesLauncher不带这个参数就不会扫外部目录。
解决:grep loader.path startup.sh确认参数存在,然后在启动命令后加--debug起一次,日志里会打印实际的 classpath 扫描路径,对不上就改路径。最省事的验证方式是把 jar 直接 zip 进BOOT-INF/lib,能起来就说明问题出在加载路径上。
5.2 建表成功但运行报「无效的表或视图名」
现象:用 NACOS 用户手工SELECT * FROM config_info正常,Nacos 连上去之后报无效的表或视图。
原因:连接串上没带schema=NACOS,Nacos 用 NACOS 用户登录后默认模式不是 NACOS,或者实例开了大小写敏感而建表时用了小写引号。
解决:连接串里显式加schema=NACOS,这是最直接的办法。同时在数据库里执行SELECT USER FROM DUAL和SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)确认当前模式和登录用户一致。两者不一致的情况下,表建在了另一个模式里,Nacos 自然看不到。
5.3 配置列表能打开,但翻页出现重复行或者直接空页
现象:第一页正常,点第二页数据重复,或者干脆一条都没有。
原因:分页 SQL 的参数顺序没换。MySQL 的LIMIT #{start}, #{pageSize}改成了LIMIT #{pageSize} OFFSET #{start}但只改了关键字,两个占位符没动,等于把条数当成了偏移量。
解决:把两个#{}的位置互换,改完拿LIMIT 10 OFFSET 0、LIMIT 10 OFFSET 10两条 SQL 在达梦客户端手工跑一遍,确认返回的行数分别是 10 和 10,再去点页面。这个坑我在一次迁移里连着踩了两回,因为两处 mapper 的分页写法不一样。
5.4 配置发布成功,但列表里的修改时间不动,还差 8 小时
现象:改完配置,gmt_modified还是旧值,或者时间对不上。
原因:两个独立问题叠加。ON UPDATE CURRENT_TIMESTAMP在达梦上无效,UPDATE 语句没显式赋值就不更新;时间差 8 小时是 JVM 时区和达梦服务端时区不一致,达梦的SYSDATE取的是服务端时间。
解决:把所有 UPDATE mapper 里的gmt_modified显式写成gmt_modified = SYSDATE;时区统一到 UTC 或东八区,JVM 启动参数加-Duser.timezone=Asia/Shanghai,数据库侧确认SELECT SYSDATE FROM DUAL和系统时间一致。两边都对齐之后才不会出现「配置刚发布,按时间排序却排在最后」的玄学现象。
5.5 同一份配置在数据库里出现两行
现象:config_info表里data_id、group_id、tenant_id完全相同的记录有两条,客户端拿到的值时新时旧。
原因:唯一索引没建。达梦的CREATE TABLE不认 MySQL 的内联UNIQUE KEY,原样搬脚本时这一段被静默跳过——有的客户端执行时会忽略不认识的子句而不报错。
解决:SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME='CONFIG_INFO'确认唯一索引在不在,不在就补建。补之前先清理重复数据,保留id最大的那条。这件事的后悔药就是建表完成后立刻核对索引数量,别等数据脏了才回头查。
6. 验证与进阶:三组冒烟用例和一条 SQL 日志开关
改造完成之后不要直接看页面能不能打开,那只能证明首页的统计 SQL 跑通了。我一般固定跑三组用例,覆盖配置、历史、命名空间三条主链路。
| 用例 | 操作 | 通过标准 |
|---|---|---|
| 配置发布与回读 | 新建配置,改三次,再删一次 | 列表gmt_modified逐次更新,删除后his_config_info有对应记录 |
| 分页与模糊查询 | 造 120 条配置,翻到第 2、7、12 页 | 每页 10 条无重复,总数与SELECT COUNT(*)一致 |
| 命名空间隔离 | 建两个 tenant,同 data_id 写不同值 | 互不覆盖,tenant_id字段落值正确 |
跑用例的时候把 SQL 日志打开,能省掉大量猜测:
# 观察 mapper 实际发出的 SQL 长什么样 logging.level.com.alibaba.nacos.persistence.repository=debug logging.level.com.alibaba.nacos.persistence.datasource=debug日志里如果看到LIMIT ? OFFSET ?、SYSDATE、NVL(这些字样,说明方言改动生效了;如果还是now()、LIMIT ?, ?,说明改的那份 mapper 没被打进包里,回去检查BOOT-INF/classes/mapper下的文件时间戳。
进阶用法上,插件路线有一个额外好处:把达梦实现单独出一个 jar,同时保留官方 MySQL 实现,在application.properties里切spring.sql.init.platform就能在两种库之间来回切。做灰度迁移时特别有用——先让新集群连达梦跑一段时间,出问题一分钟切回 MySQL,比整包回滚快得多。
还有一个容易被忽略的点:Nacos 集群模式下各节点连的是同一个库,会话一致性靠数据库保证。达梦这边如果开了读写分离或者主备,要确认 Nacos 的写请求全部打到主库,否则会出现「A 节点刚发布的配置,B 节点读不到」,表现出来像集群不同步,实际上是读到了备库的延迟数据。
从那以后我每次做完这类数据库替换,都会先在生产同配置的预发环境跑满 24 小时再切流量,重点看两件事:连接池的活跃连接数有没有缓慢爬升,以及gmt_modified的时间分布是否连续。这两个指标异常,基本都能提前暴露方言差异留下的隐患。希望帮到你。
本文还有配套的精品资源,点击获取