最近在做Nacos 3.1.0的国产数据库适配,把配置中心的存储从MySQL切到了达梦。整个过程比想象中麻烦,但也比想象中有章法——麻烦在于Nacos默认只认MySQL的SQL方言,有章法在于它从2.2开始就留了数据源SPI扩展口子。这篇文章把我实测下来的适配路线、脚本迁移细节、SPI插件写法和几个埋得很深的坑整理出来,给后面要接达梦、人大金仓或者其他国产库的同学一个参考。
1. Nacos 3.1.0为什么要做数据库适配,适配的是什么
1.1 很多人误以为“外部存储=支持任意数据库”
Nacos安装完成后,默认用内嵌Derby存储配置数据,生产环境通常会把配置存储切到外部数据库。这里有一个很常见的误区:以为把spring.datasource.platform改成对应数据库名,再改一下连接串就能跑。实际上Nacos服务端的外部数据源支持一直是有边界的——官方开箱即用的只有MySQL,其他数据库要么走社区插件,要么自己适配。
达梦数据库很多人叫它DM8,它对MySQL的兼容性做得还行,但远没到“零改动直接跑”的程度。Nacos 3.1.0内部使用MyBatis作为持久层框架,SQL语句按MySQL方言编写,分页、主键生成、函数调用都带着MySQL的影子。要让它在达梦上跑起来,核心工作其实就是两件事:换掉JDBC驱动、改掉SQL方言。
1.2 Nacos的数据源扩展机制与两个适配层次
Nacos从2.2版本开始引入了数据源插件机制,服务端通过一组SPI接口抽象了不同数据库的差异。这个机制在3.x里延续了下来,3.1.0也不例外。数据源相关代码主要在nacos-datasource-plugin-ext这类模块下,核心接口大致是这样:
DataSourceService:负责创建真实的数据源Connection,对应不同数据库的驱动加载和连接池初始化。DatabaseDialect:负责输出当前数据库的方言信息,比如数据库类型标识、分页SQL生成、一部分自动转换逻辑。
Nacos服务端启动时会根据配置的平台名称,通过SPI找到对应的DataSourceService实现并加载。默认实现里只有MySQL和Derby,所以想让3.1.0支持达梦,本质就是:提供一个达梦的DataSourceService实现,再提供一个达梦的DatabaseDialect实现,通过SPI注册进去。
理解了这个机制,就不会被网上各种“改源码重新编译”的教程带偏。改源码确实能跑,但后续升级Nacos版本时要反复打Patch,非常痛苦。走SPI插件路线,Nacos主程序一行不动,升级时只要用新版本重新编译一遍插件就行,代价小很多。
1.3 适配范围:配置中心强依赖数据库,服务发现不强依赖
开始动手前需要搞清楚一个问题:Nacos适配数据库,主要影响哪个功能模块?
Nacos的服务注册与发现,核心数据(服务实例、健康状态、临时实例)都保存在内存中,底层有Raft协议做多节点同步,这部分并不依赖外部数据库。真正强依赖外部数据库的是配置中心:config_info、his_config_info、tenant_info、users这些表承载了配置内容、历史版本、租户信息和用户权限,一旦数据库不可用,配置中心基本就废了。
所以适配达梦时,回归测试的重点应该放在配置中心相关功能上,服务发现的回归相对简单,注册几个服务、确认心跳正常就行。这能省下不少测试时间。
2. 达梦实例准备:版本、兼容模式与账号权限
2.1 达梦8的MySQL兼容模式能帮到什么
达梦数据库在初始化实例或创建数据库时,可以选择兼容模式,常见的有Oracle兼容模式和MySQL兼容模式。Nacos的脚本和SQL都是MySQL方言,所以选MySQL兼容模式是最优解,这样大部分基础语法能被达梦直接映射,比如反引号、DATETIME、LIMIT这些,在兼容模式下支持度会好很多。
不过要有个心理预期:兼容模式不等于“完全兼容”。我实测下来,达梦8的MySQL兼容模式能处理好80%的MySQL语法,剩下的20%仍然要靠手工改造,尤其集中在表注释、自增列、大字段这几种写法上。这是本章节后面要重点展开的内容。
如果你手头的达梦实例不是MySQL兼容模式,就会遇到比较棘手的情况:建表时DATETIME类型可能直接报错,AUTO_INCREMENT关键字的处理方式也会不一样。建议如果条件允许,重新初始化一个MySQL兼容模式的达梦实例,后面能少踩一半的坑。
2.2 账号、模式和权限的最小规划
达梦的账号体系有点介于Oracle和MySQL之间,一个用户默认对应一个同名Schema。Nacos连接数据库后执行的所有SQL,默认都在当前用户对应的Schema下进行。所以规划账号时,要让Nacos使用的数据库账号和建表所在的Schema保持一致。
我常用的建库方式是这样:
CREATE TABLESPACE nacos DATAFILE '/opt/dmdbms/data/DAMENG/nacos.dbf' SIZE 128 AUTOEXTEND ON NEXT 8 MAXSIZE 2000; CREATE USER nacos IDENTIFIED BY "Nacos@123" DEFAULT TABLESPACE nacos; GRANT DBA TO nacos;这里直接给了DBA权限,纯粹是为了快速验证适配可行性,生产环境建议收敛到RESOURCE、CREATE SESSION这类最小权限集,然后按实际报错逐步补权限。给应用账号DBA权限在生产上风险太大,不值得。
需要注意达梦默认端口是5236,连接串是jdbc:dm://127.0.0.1:5236/NACOS,最后一个路径上的NACOS是Schema名,要和建表时使用的Schema大小写保持一致,否则启动后会报错“表或视图不存在”。
3. 把mysql-schema.sql迁移到达梦:逐条改造的细节
3.1 建表DDL的方言改造对照
Nacos 3.1.0的安装包中,conf目录下自带mysql-schema.sql和derby-schema.sql两份脚本。我们要做的是把mysql-schema.sql改造成达梦可执行的dm-schema.sql。
Nacos配置中心涉及的核心表包括:config_info、config_info_gray、config_info_beta、config_info_tag、config_relation、group_capacity、his_config_info、tenant_capacity、tenant_info、users、roles、permissions、app_configdata_relation_subs。3.1.0相比2.x多了config_info_gray这张表,用于灰度配置,改造时别漏掉。
以config_info表为例,官方MySQL脚本大概长这样:
CREATE TABLE `config_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT '配置id', `group_id` varchar(255) DEFAULT NULL, `content` longtext NOT NULL COMMENT '配置内容', `md5` varchar(32) DEFAULT NULL COMMENT 'md5值', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `src_user` text, `src_ip` varchar(50) DEFAULT NULL, `app_name` varchar(128) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `c_desc` varchar(256) DEFAULT NULL, `c_use` varchar(64) DEFAULT NULL, `effect` varchar(64) DEFAULT NULL, `type` varchar(64) DEFAULT NULL, `c_schema` text, `encrypted_data_key` text NOT NULL COMMENT '秘钥', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='config_info';改造后达梦可执行的版本:
CREATE TABLE config_info ( id BIGINT NOT NULL IDENTITY(1,1), data_id VARCHAR(255) NOT NULL, group_id VARCHAR(255), content CLOB NOT NULL, md5 VARCHAR(32), gmt_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gmt_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP, src_user CLOB, src_ip VARCHAR(50), app_name VARCHAR(128), tenant_id VARCHAR(128) DEFAULT '', c_desc VARCHAR(256), c_use VARCHAR(64), effect VARCHAR(64), type VARCHAR(64), c_schema CLOB, encrypted_data_key CLOB, CONSTRAINT pk_config_info PRIMARY KEY (id), CONSTRAINT uk_configinfo_datagrouptenant UNIQUE (data_id, group_id, tenant_id) ); COMMENT ON COLUMN config_info.id IS 'id'; COMMENT ON COLUMN config_info.data_id IS '配置id'; COMMENT ON COLUMN config_info.content IS '配置内容'; COMMENT ON COLUMN config_info.tenant_id IS '租户字段'; COMMENT ON COLUMN config_info.encrypted_data_key IS '秘钥';逐条说明一下改动逻辑:
- MySQL的
bigint(20) NOT NULL AUTO_INCREMENT改成了达梦的BIGINT NOT NULL IDENTITY(1,1)。IDENTITY(1,1)是达梦的自增列标准写法,起始值和步长都是1。 longtext和text全部改成了CLOB。这是达梦对应大文本字段的标准类型,Nacos的content字段和encrypted_data_key字段都是存大段配置内容的,如果保持VARCHAR,很容易在写入大批量配置时报长度超限。datetime DEFAULT CURRENT_TIMESTAMP改成TIMESTAMP DEFAULT CURRENT_TIMESTAMP。达梦的MySQL兼容模式下,DATETIME通常也能识别,但用TIMESTAMP更稳妥。- 表级
COMMENT 'xxx'不能放在括号里,达梦的写法是单独一行COMMENT ON COLUMN ... IS 'xxx'。 - 最后那句
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin直接删掉,达梦没有存储引擎概念,字符集在创建数据库实例时已经定了,不需要在表结构里再声明。 - 唯一约束用
CONSTRAINT uk_... UNIQUE (...)替代MySQL的UNIQUE KEY uk_... (...)写法。
其他表基本都是同样的套路。his_config_info、config_info_beta、config_info_gray这些表的字段结构高度相似,复制config_info的改法就行。
3.2 索引、约束与初始化数据的执行顺序
MySQL脚本里常常会有这种写法:
KEY `idx_configinfo_tenant_id` (`tenant_id`)这种行内索引定义在达梦里不认。改造时把这种KEY都剔出来,在CREATE TABLE语句执行成功后再单独建。比如:
CREATE INDEX idx_configinfo_tenant_id ON config_info(tenant_id);顺序很重要。建完所有表、做完所有索引和约束之后,再执行初始化数据的INSERT语句。Nacos的mysql-schema.sql里带了一部分初始化数据,包括管理员账号nacos/nacos初始密码的BCrypt哈希,以及默认的roles、permissions记录。漏掉这些,控制台登录会成为第一个拦路虎。
执行方式上,建议直接在达梦的disql命令行里执行:
disql nacos/"Nacos@123"@127.0.0.1:5236 start /opt/nacos/conf/dm-schema.sql或者用达梦自带的图形管理工具直接跑整个脚本。脚本执行前先开启事务,一旦某一条DDL失败,立即回滚,逐段排查,不要带着半截状态往下走。特别是外键和索引,前面的表没建成功,后面的约束就会报错,而且报错信息往往让人摸不着头脑。
3.3 INSERT语句里的隐藏雷点
初始化数据里还有两个小细节容易被忽略。第一个是users表的enabled字段,MySQL写法是TRUE/FALSE,达梦里如果兼容模式没完全生效,可能需要改成1/0。第二个是密码哈希值,mysql-schema.sql中nacos用户的初始密码哈希是$2a$10$...格式,这是一个BCrypt哈希,达梦的VARCHAR字段长度够用,但执行时要注意字符串里的$符号不要被命令行特殊转义。建议把INSERT语句单独存成一个init.sql文件,用disql的start命令执行,避免手工复制粘贴时丢字符。
4. 不经源码重编译的SPI数据源插件:驱动与方言
4.1 为什么选SPI而不是直接改源码
网上搜Nacos适配达梦,能看到不少“修改源码、替换驱动、重新编译”的教程。这条路不是不能走,但有个很现实的问题:Nacos 3.x的迭代速度不慢,今天你基于3.1.0改了源码,明天官方发了3.1.1修复了一个安全漏洞,你升级的时候就要把所有Patch重新打一遍。如果改动还涉及核心的ExternalDataSourceServiceImpl这类类,每次升级都是煎熬。
SPI插件方案没这个烦恼。Nacos主程序的jar包完全不动,所有差异逻辑都收敛到独立的插件jar包中。升级Nacos时,只需要用新版本的SPI接口重新编译插件即可。这也是Nacos官方推荐的扩展方式,社区里很多数据库适配插件都是这么做的。
4.2 DmDataSourceService与DmDialect的实现要点
插件模块的代码结构大致如下:
nacos-dm-datasource-plugin/ pom.xml src/main/java/com/example/nacos/dm/ DmDataSourceServiceImpl.java DmDatabaseDialect.java src/main/resources/META-INF/services/ com.alibaba.nacos.plugin.datasource.spi.DataSourceService com.alibaba.nacos.plugin.datasource.spi.DatabaseDialectDmDataSourceServiceImpl的核心逻辑,是仿照Nacos默认的ExternalDataSourceServiceImpl,把驱动类和连接地址改成达梦的。
package com.example.nacos.dm; import com.alibaba.nacos.plugin.datasource.spi.DataSourceService; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import java.util.Properties; public class DmDataSourceServiceImpl implements DataSourceService { private HikariDataSource dataSource; @Override public DataSource getDataSource() throws Exception { if (dataSource == null) { Properties props = loadProperties(); String url = props.getProperty("db.url.0"); String user = props.getProperty("db.user.0"); String password = props.getProperty("db.password.0"); dataSource = new HikariDataSource(); dataSource.setDriverClassName("dm.jdbc.driver.DmDriver"); dataSource.setJdbcUrl(url); dataSource.setUsername(user); dataSource.setPassword(password); dataSource.setMaximumPoolSize(20); dataSource.setMinimumIdle(2); } return dataSource; } private Properties loadProperties() { // 读取conf/application.properties中的db.url.0、db.user.0等配置 return new Properties(); } }这里要注意,Nacos主程序里已经有了一套读取application.properties中数据库配置的逻辑,loadProperties()的具体实现可以直接参考2.x/3.x源码里ExternalDataSourceServiceImpl的写法,或者更简单一点,直接在getDataSource()方法里硬编码从System.getProperty读取配置。生产上建议还是走配置文件,别硬编码。
DmDatabaseDialect的实现更简单,如果你的达梦兼容模式足够好,可以直接继承MySQL方言:
package com.example.nacos.dm; import com.alibaba.nacos.plugin.datasource.spi.DatabaseDialect; public class DmDatabaseDialect extends MySqlDatabaseDialect { @Override public String getDatabase() { return "dm"; } }继承MySQL方言有一个前提:达梦的MySQL兼容模式支持LIMIT分页。我在DM8上实测过它支持,但不同小版本可能存在差异。稳妥的做法是先手动执行一下:
SELECT * FROM config_info LIMIT 0, 10;如果这条SQL能跑通,方言层就直接复用MySQL的分页逻辑;如果报错,再用ROWNUM改成分页逻辑。这个验证很重要,因为Nacos配置列表查询、历史配置查询都重度依赖分页,分页挂了,控制台基本就瘫了。
4.3 打包、部署与3.1.0关键配置项
插件写好后,用Maven打包成nacos-dm-datasource-plugin.jar,然后将jar包放入Nacos 3.1.0安装目录下的插件目录。不同发行版的插件目录名可能略有差异,有的叫plugins,有的叫plugin,也有的是plugins/datasource。以你下载的3.1.0解压后的实际目录为准,找不到就全局搜索一下nacos-datasource-plugin相关的目录名。
为了让插件里的DmDriver能被正常加载,驱动依赖要么打成fat-jar塞进插件包,要么单独把达梦JDBC驱动jar放到Nacos的lib目录下。我建议打成fat-jar,这样插件分发时不需要额外告诉别人还要放一个驱动jar。
然后在conf/application.properties里配置:
spring.datasource.platform=dm nacos.datasource.platform=dm db.num=1 db.url.0=jdbc:dm://127.0.0.1:5236/NACOS db.user.0=nacos db.password.0=Nacos@123这里需要特别提醒一点:spring.datasource.platform和nacos.datasource.platform这两个配置项,在不同版本里的识别优先级和行为不完全一致。3.1.0里建议两个都写上,值都为dm。我在测试中遇到过只写其中一个时,Nacos仍然尝试加载默认MySQL分支的情况,报错信息是找不到com.mysql.cj.jdbc.Driver,排查了好久才反应过来是平台配置没生效。
5. 启动验证与配置中心核心回归用例
5.1 启动日志怎么排查
配置完成后启动Nacos,不要急着打开控制台。先盯着启动日志看几行关键信息:
- 有没有
Failed to create datasource、Cannot load driver class: dm.jdbc.driver.DmDriver这类异常。 - 有没有
com.mysql.cj.jdbc.Driver相关的ClassNotFoundException。如果出现这个,多半是SPI没生效,Nacos还在用默认的MySQL实现,立刻检查插件jar是否被正确扫描到、nacos.datasource.platform是否配置成功。 - 日志末尾出现类似
Nacos started successfully的输出,说明启动阶段没问题。
启动阶段不出错只是第一关。真正判断数据源有没有接上,可以打开控制台,进入配置管理页面,如果能正常分页展示已经导入的配置数据,说明数据源基本可用。
5.2 配置中心的核心功能回归表
适配数据库后的回归测试,我建议按下面这个表格过一遍,每一项都不能跳过:
| 功能模块 | 测试步骤 | 预期结果 |
|---|---|---|
| 登录认证 | 使用初始账号nacos/nacos登录控制台 | 登录成功,进入主页面 |
| 命名空间管理 | 新建一个命名空间,再删除 | 创建和删除均成功 |
| 配置发布 | 新建dataId为demo.properties的配置,内容包含中文和特殊字符 | 发布成功,内容完整显示 |
| 配置查询 | 在配置列表按dataId和group搜索 | 搜索结果准确 |
| 动态刷新 | 启动一个Spring Cloud应用接入该Nacos,修改配置并发布 | 客户端日志收到变更通知,本地属性刷新 |
| 历史版本 | 连续修改同一配置3次,进入历史版本列表 | 能看到3个历史版本,可回滚到指定版本 |
| 用户权限 | 新建一个用户,授予只读角色 | 该用户能查看配置但不能编辑 |
| 重启持久化 | 重启Nacos服务端,再次查询配置 | 配置数据仍然存在 |
| 集群节点 | 如果有集群环境,查看节点列表 | 节点状态正常 |
动态刷新这条要特别强调一下:不要只在控制台里改了配置就算测过。真实的客户端订阅,需要通过Nacos客户端或Spring Cloud Alibaba应用来验证。我在适配测试时遇到一个情况,控制台配置发布正常、数据库里数据也更新了,但客户端一直拿不到新值,最后发现是客户端连接的还是旧节点,重启客户端重连后才恢复。这种问题跟数据库适配本身无关,但很容易让人误判成适配有问题。
5.3 服务注册发现为什么会“没压力”
前面说过服务发现的数据主要走内存和Raft协议,不依赖外部数据库。回归时只需要启动一个服务提供方和一个服务消费方,确认注册、心跳、发现、下线这几个基本动作正常即可。不用像配置中心那样做大量数据库相关的场景测试,这块的压力主要在Nacos节点之间的Raft通信上,跟达梦没什么关系。
6. 适配过程中踩过的坑与升级兼容考虑
6.1 三个必踩的坑:驱动、大小写、字段长度
第一个坑是驱动类找不到。插件包虽然写好了,但Nacos启动时如果抛ClassNotFoundException: dm.jdbc.driver.DmDriver,说明驱动jar没有被插件类加载器看到。解决办法就是把达梦JDBC驱动jar一并打进插件fat-jar里,不要指望Nacos主程序会主动加载插件目录下所有的jar。
第二个坑是“表或视图不存在”。这个坑最容易让人崩溃,因为明明建表成功了,SQL看着也没问题,但一启动就报错查不到表。根因基本出在达梦的大小写敏感配置上。达梦默认在建库时有一个大小写敏感开关,如果开启,Nacos生成的SQL里不带双引号的标识符会被达梦自动转成大写,而你的建表脚本如果用了小写表名,自然就找不到。解决方式是在初始化达梦实例时关闭大小写敏感,或者所有表名、字段名统一用大写建表和查询。
第三个坑是中文内容写入报“字符串长度超出限制”。达梦的VARCHAR类型默认按字节计算长度,一个中文字符可能占3个字节甚至更多。Nacos脚本里如果部分字段仍然用了VARCHAR,比如c_desc、c_use这些,在写入中文描述时有可能因为长度不够而报错。稳妥做法是,凡是可能存中文的字段,长度直接放大一倍,或者干脆改用CLOB。尤其是content、c_schema、encrypted_data_key这几个字段,我全部改成了CLOB,一次性能解决所有长度问题。
6.2 从2.x升级到3.1.0时容易被忽视的改动
如果你手头已经有一套跑在Nacos 2.x上的MySQL库,想直接切到3.1.0+达梦,有几个点需要特别注意。
第一是表结构差异。3.x相比2.x多了config_info_gray表,这是灰度配置功能的核心表。直接从旧库迁数据时,别只导数据不建表,否则控制台打开灰度配置相关页面会报错。建议先通过conf目录下的升级脚本确认需要补充哪些表,再手工把这部分建表SQL改造成达梦语法执行。
第二是SPI接口的包名和类名可能变化。Nacos 2.x时代编译好的达梦插件,放到3.1.0下不一定能直接运行。3.x对插件模块做了重构,接口签名、包名、依赖版本都可能有调整。如果启动时报NoSuchMethodError或ClassCastException,基本就是插件版本不匹配,需要用3.1.0对应的源码重新编译。
第三是配置项名称的变化。2.x时期老教程里喜欢用spring.datasource.platform,3.x里可能改成nacos.datasource.platform才生效。建议升级后立刻检查启动日志,确认实际加载的数据源平台确实是dm,而不是默认的MySQL或Derby,避免表面上连接成功、实际所有数据都写进了内嵌Derby的尴尬情况。
6.3 我的几条装机建议
这套适配做完之后,我自己沉淀了几个习惯,也算是一点经验之谈。
拿到新版Nacos先别急着启动,第一件事是拉出mysql-schema.sql,把表结构和目标数据库实际结构对一遍。脚本迁移这种活儿,一次性全自动跑通是运气,多数情况都需要手工微调,慢就是快。
任何数据库适配,都优先走SPI插件而不是改源码。哪怕只是为了临时验证一个特性,也不要在Nacos主程序上动刀,否则版本升级时会非常被动。
动态刷新的回归一定要有真实的客户端订阅,不能只看控制台。配置中心的核心价值就是“改一处、到处生效”,如果客户端拿不到变更通知,数据库适配得再完美,对业务来说也等于没适配。