ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Nacos 2.5.4适配达梦DM8:数据源Provider与SQL兼容改造实战

Nacos 2.5.4适配达梦DM8:数据源Provider与SQL兼容改造实战 简介面向需要将 Nacos 注册配置中心接入达梦数据库的开发者此资源提供完整适配方案。包内既包含 Windows 与 Linux 下可直接运行的编译产物也附带完整源码可导入 IDEA 按需修改并自行编译已编译版本位于 nacos-2.1.2\distribution\target 目录配合 sql 脚本即可自助导入达梦数据库。资源共 92 个文件约 297.51MB主要文件类型包括 sql 初始化脚本、sh 启停脚本、yaml/properties 配置、jar 可执行包以及日志和元数据文件目录结构围绕 nacos-server-2.1.2 展开便于定位。已有 889 人学习下载。通过这套资料可快速获得达梦数据库适配后的 Nacos 服务同时借助源码理解适配改造要点适合需要离线部署或二次定制的中高级开发者。 接手这个任务的时候我心里其实是有底的但做完之后还是踩了不少意料之外的坑。背景很简单内部系统做数据库国产化改造要求所有中间件尽量把存储层从MySQL迁走Nacos作为配置中心和注册中心自然也在名单里。目标很明确——让Nacos把配置数据、用户数据和权限数据都落到达梦数据库上而不是继续依赖MySQL。这个事听起来就是“换个数据源”但真正上手之后你会发现Nacos对数据源的处理并不是简单地改个JDBC URL就能搞定的。它内部有一套自己的数据源加载机制而且默认只内置了MySQL和Derby两种实现。如果你直接把配置里的spring.datasource.platform改成别的值Nacos启动时会直接报错说找不到对应的数据源提供者。这篇文章我就以Nacos 2.5.4 适配达梦DM8为例把整个改造过程、核心代码逻辑、SQL兼容处理还有高频问题完整梳理一遍给后面要做同类迁移的兄弟提供一份可以直接参考的实操记录。1. 为什么Nacos要单独适配达梦数据源1.1 先搞清楚Nacos的数据源机制Nacos的数据源设计其实不复杂核心代码集中在nacos-datasource-plugin这个模块里。它定义了一个DataSourceProvider接口负责根据不同的平台标识返回对应的数据源实现。默认支持的平台有derby和mysql通过配置文件里的spring.datasource.platform来指定。关键点在于Nacos并不会因为你把platform改成dm或者oracle就自动去加载对应的驱动。它内部会通过SPI机制扫描所有实现了DataSourceProvider接口的类然后根据getDataSourceType()方法的返回值匹配平台标识。如果匹配不到直接启动失败。这就解释了为什么单纯改配置文件不行——你需要提供一个能识别dm这个平台标识的Provider实现并且确保它被Nacos加载到。另外还有一个容易忽略的点Nacos的很多业务SQL比如配置查询、历史配置、权限校验虽然写在Mapper接口里但SQL语句其实是以Select注解或XML方式硬编码在代码中的而且有明显MySQL方言特征。比如反引号、LIMIT分页语法、INSERT IGNORE等。这些SQL在达梦上直接执行大概率会报语法错误所以数据源适配只是一部分SQL方言兼容才是工作量的大头。1.2 三种适配路线我为什么选了改源码在动手之前我评估了三条路线第一种是数据库网关方案也就是在Nacos和达梦之间加一层MySQL协议转换代理让Nacos以为自己在连MySQL实际上数据落到达梦。这种方案对应用层侵入最小但考虑到达梦和MySQL在SQL语法、事务行为、自增列实现上的差异网关层要做很多转译工作而且生产环境的稳定性不好验证我直接放弃了。第二种是尝试用达梦自带的MySQL兼容模式。达梦确实提供了对MySQL语法的兼容选项但实测下来它主要解决的是数据类型和基础语法的兼容对于Nacos里那些复杂的子查询、分页、自动生成主键等场景兼容得并不彻底。而且打开兼容模式会影响达梦自身的其他特性运维侧也不建议这么做。第三种就是我现在采用的方案拉取Nacos 2.5.4源码在保持核心业务逻辑不变的前提下新增达梦数据源Provider同时把初始化脚本和运行期SQL统一改成达梦兼容写法。这属于最笨但对结果最有掌控力的方式改完以后不依赖任何外部转换层运维上就是一套标准的达梦连接配置干净利落。2. 适配前的环境准备与依赖改造2.1 版本组合怎么选我做这套适配使用的环境如下可以作为一个参考基准组件版本说明Nacos2.5.4社区稳定版达梦数据库DM8建议用8.1以上版本达梦JDBC驱动DmJdbcDriver18适配JDK8及以上JDK1.8 / 17Nacos官方要求Maven3.6编译源码用有几个细节提醒一下。首先达梦JDBC驱动的命名一定要留意DmJdbcDriver18对应JDK1.8以上环境如果你们服务器还是JDK7那得用DmJdbcDriver16二者的类名都是dm.jdbc.driver.DmDriver驱动包路径也一样但兼容性差别很大。其次达梦数据库的字符集尽量用UTF-8。Nacos的配置内容中文很常见如果达梦库字符集是GBK启动时不报错但写入中文配置后再读出来就有可能出现乱码那时候查问题非常头疼。我建议在达梦初始化实例时就指定UTF-8。2.2 pom.xml改造与核心依赖Nacos源码是Maven多模块工程我们需要改动的主要是nacos-config和nacos-datasource-plugin两个模块另外nacos-console模块如果涉及到控制台登录用户校验也会间接依赖数据源。我直接在Nacos根目录的pom.xml里增加了达梦驱动的依赖dependency groupIdcom.dameng/groupId artifactIdDmJdbcDriver18/artifactId version8.1.3.140/version /dependency然后在nacos-config模块中默认引入的是MySQL驱动和HikariCP连接池。这里我做了两件事第一保留HikariCP不做替换因为Nacos内部很多组件直接依赖它贸然换成Druid或者C3P0会引发一堆适配问题第二在数据源Provider实现中明确指定使用达梦驱动类。在连接池这块我不建议折腾HikariCP本身对达梦的兼容性没有问题它就是标准的JDBC DataSource实现没有用到任何MySQL特有的连接参数。你需要关注的只是驱动类名、URL、用户名和密码这四个基础属性。最终Nacos的application.properties配置如下spring.datasource.platformdm db.num1 db.url.0jdbc:dm://10.10.10.10:5236 db.usernacos_user db.passwordnacos_pass这里有个容易踩的坑Nacos的配置项从早期版本到现在有变化老版本用db.username2.5.4里要写db.user如果写错了Nacos可能会去连接系统默认用户SYSDBA结果就是出现账号密码错误或者权限不足的报错。第一次配置的时候建议反复确认一下版本对应的配置项名称。3. 数据源Provider实现与SQL兼容改造3.1 用SPI扩展数据源Provider这是整个适配的核心代码环节。Nacos的数据源插件模块提供了一个抽象类ExternalDataSourceProvider我们只需要继承它并实现三个关键方法返回平台类型、创建数据源、初始化数据源。我新建了一个类DmDataSourceProvider对应代码逻辑大致如下package com.example.nacos.plugin.datasource; import com.alibaba.nacos.plugin.datasource.ExternalDataSourceProvider; import com.alibaba.nacos.plugin.datasource.model.DataSourceProperties; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; public class DmDataSourceProvider extends ExternalDataSourceProvider { Override public String getDataSourceType() { return dm; } Override public DataSource createDataSource(DataSourceProperties properties) { HikariDataSource ds new HikariDataSource(); ds.setDriverClassName(dm.jdbc.driver.DmDriver); ds.setJdbcUrl(jdbc:dm:// properties.getUrl()); ds.setUsername(properties.getUsername()); ds.setPassword(properties.getPassword()); ds.setMaximumPoolSize(20); ds.setMinimumIdle(2); ds.setConnectionTimeout(30000); ds.setIdleTimeout(600000); ds.setMaxLifetime(1800000); return ds; } }这里的properties.getUrl()拿到的是配置里db.url.0的值也就是jdbc:dm://10.10.10.10:5236所以拼接后完整的JDBC地址是jdbc:dm://jdbc:dm://10.10.10.10:5236不对这里要注意一下。Nacos的DataSourceProperties在解析db.url.0时格式本身也可以直接包含完整JDBC URL。为了避免拼接问题我在配置里写的是不带前缀的地址即db.url.010.10.10.10:5236然后在代码里拼上jdbc:dm://前缀。大家在自己实现时到底怎么拼、前缀放哪里一定要根据实际传入的参数值来决定先打日志确认一下再写死不要凭感觉。除了这个类还要在META-INF/services目录下注册SPI文件名是接口的全限定名文件内容写实现类的全限定名com.alibaba.nacos.plugin.datasource.spi.DataSourceProvider com.example.nacos.plugin.datasource.DmDataSourceProvider搞完这一步Nacos启动时就能通过SPI扫描到DmDataSourceProvider再结合spring.datasource.platformdm数据源就能正常初始化。3.2 初始化脚本逐条翻译Nacos的初始化SQL脚本默认只提供MySQL版本网上也能找到Derby版本。达梦没有现成的脚本只能手动基于MySQL版转换。我遇到的主要问题集中在以下几个方面。第一个是建表语句中的反引号。MySQL版本的脚本里所有表名和字段名都用了反引号达梦在默认Oracle兼容模式下不认反引号需要全部去掉。第二个是自增列MySQL用AUTO_INCREMENT达梦推荐使用IDENTITY列来实现在建表时直接声明BIGINT IDENTITY(1,1)就能自增。第三个是字段默认值比如gmt_create这类时间字段MySQL里常用DEFAULT CURRENT_TIMESTAMP达梦可以写成DEFAULT CURRENT_TIMESTAMP或DEFAULT SYSDATE但要注意如果表结构设计里需要ON UPDATE CURRENT_TIMESTAMP这种自动更新机制MySQL一条语句就搞定了达梦则没有这个特性只能通过触发器实现。这里给一个我改完的config_info表结构做参考CREATE TABLE NACOS.CONFIG_INFO ( ID BIGINT IDENTITY(1,1) NOT NULL, DATA_ID VARCHAR(255) NOT NULL, GROUP_ID VARCHAR(255), CONTENT CLOB NOT NULL, MD5 VARCHAR(32), GMT_CREATE TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, GMT_MODIFIED TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, SRC_USER VARCHAR(255), 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 VARCHAR(64), ENCRYPTED_DATA_KEY VARCHAR(512) DEFAULT , PRIMARY KEY (ID) ); CREATE INDEX IDX_CONFIGINFO_DATAID_GROUP_TENANT ON NACOS.CONFIG_INFO (DATA_ID, GROUP_ID, TENANT_ID);注意表名和字段名最好都用大写这是达梦的存储习惯。如果你用小写建表达梦默认会把它们转换成大写存储后续Nacos的SQL如果用了小写字段名有时候能匹配上有时候就会报列不存在非常烦人。不要问我为什么不用引号包小写生产环境不建议那么干。3.3 运行期SQL语句的批量适配建表只是第一步真正的硬骨头在Nacos运行期间执行的那些SQL上面。Nacos的SQL不算太多但分布在不同模块里如果不做处理启动后首次访问配置接口就会开始报错。最常见的报错就是分页SQL。Nacos内部查询配置列表、历史列表都会用到分页MySQL写法是LIMIT ?, ?。达梦支持LIMIT吗部分达梦版本在MySQL兼容模式下支持但兼容性不稳定。我全部改成了Oracle风格的ROWNUM分页代码样式如下SELECT * FROM ( SELECT T.*, ROWNUM RN FROM ( SELECT ID, DATA_ID, GROUP_ID FROM CONFIG_INFO WHERE TENANT_ID ORDER BY ID ) T WHERE ROWNUM ? ) WHERE RN ?这个改法有几点要注意内层查询必须加ORDER BY否则分页结果不稳定ROWNUM的取值是在ORDER BY之前的所以必须包三层才能得到正确的分页结果。另外一个高频场景是“插入或更新”操作MySQL里常用INSERT IGNORE或ON DUPLICATE KEY UPDATE达梦则用MERGE INTO来实现。比如Nacos在写入标签关系表时就涉及类似的逻辑。这段SQL改写后如下MERGE INTO CONFIG_TAGS_RELATION T USING (SELECT ? AS TAG_ID, ? AS TAG_NAME, ? AS TAG_TYPE, ? AS DATA_ID, ? AS GROUP_ID, ? AS TENANT_ID FROM DUAL) S ON (T.TAG_ID S.TAG_ID AND T.DATA_ID S.DATA_ID AND T.GROUP_ID S.GROUP_ID AND T.TENANT_ID S.TENANT_ID) WHEN NOT MATCHED THEN INSERT (ID, TAG_ID, TAG_NAME, TAG_TYPE, DATA_ID, GROUP_ID, TENANT_ID) VALUES (NEXT VALUE FOR SEQ_CONFIG_TAGS_RELATION_ID, S.TAG_ID, S.TAG_NAME, S.TAG_TYPE, S.DATA_ID, S.GROUP_ID, S.TENANT_ID);这里我用到了达梦的序列来模拟自增ID。如果在建表时已经定义了IDENTITY列也可以不用NEXT VALUE FOR直接让DB自动生成。具体用哪种取决于你的表结构设计。除了这些Nacos还有大量函数差异比如NOW()要换成SYSDATEMySQL的IFNULL在达梦里对应NVL字符串拼接的CONCAT函数两边都支持但参数个数不同MySQL支持多参数达梦的兼容模式对此处理不一遇到具体报错逐条改就行。这段工作的核心经验是不要试图在适配之前一次性改完所有SQL一是容易漏二是不知道哪些SQL真的会被触发。我的做法是先让服务跑起来然后通过功能测试把Nacos的主要链路走一遍——启动检查、配置发布、配置查询、配置删除、权限登录每跑一步看日志遇到SQL报错就定位修改效率反而更高。4. 启动验证与高频踩坑实录4.1 首次启动的正确验证顺序Nacos适配达梦后首次启动建议按下面的顺序来验证能少走很多弯路。第一步先确认达梦侧的环境。用达梦连接工具登录数据库确认可以连接并且nacos_user这个用户对NACOS这个schema或者说用户下的所有表具备增删改查权限以及创建表和索引的权限。权限不足时Nacos启动阶段建表或者后续写入配置会非常玄学地报错有的报权限不足有的直接报表或视图不存在。第二步是编译并启动Nacos服务。推荐先试用standalone模式不注册到其他集群节点方便排查问题。Linux环境下直接执行sh startup.sh -m standalone然后关注日志文件。Nacos在logs/start.out里会记录数据源初始化的过程如果出现类似DataSource init error或者Please check the database的提示说明你的达梦连接有问题。如果没有报错说明数据源层已经通了。第三步登录控制台做一次完整的业务验证。进入控制台后新建一个配置然后重启Nacos再登录确认配置依然存在再修改一次配置观察config_info表里的gmt_modified时间是否更新。这一步通过后说明核心链路已经打通。最后再验证用户管理新建一个账号并登录确认users、roles、permissions三张表的数据读写正常。4.2 高频问题速查表我在整个适配过程中踩过的坑以及和几个同样做过迁移的同行交流后整理的常见问题列在下面这张表里基本上覆盖了Nacos适配达梦的大部分场景。问题现象排查方向解决方案启动报ClassNotFoundException: dm.jdbc.driver.DmDriver驱动包没有打入classpath检查Maven依赖和打包产物确认DmJdbcDriver18在正确位置报[IM002] [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动这不是Nacos的问题是某些客户端工具通过ODBC连接达梦时的配置错误JDBC方式连接达梦不需要配置ODBC数据源直接用驱动类连接即可控制台能打开但配置列表报表不存在未执行初始化脚本或执行了MySQL版本的脚本使用达梦版建表脚本确保所有表已创建发布配置时报SQL语法错误位置在LIMIT附近Nacos内部分页SQL不兼容达梦全局搜索LIMIT关键字改为ROWNUM分页写法数据插入成功但ID没有自增达梦不支持AUTO_INCREMENT使用IDENTITY(1,1)或序列触发器方式生成主键中文配置内容存入后读取乱码达梦实例字符集设置问题初始化实例时选择UTF-8字符集检查JDBC URL是否追加?charsetutf8修改配置后客户端不及时感知这一步要核对配置是否正确写入达梦以及Nacos的ConfigChange通知机制是否正常先确认config_info和his_config_info数据更新情况再确认客户端连接的Nacos地址和服务端推送链路这里我想单独强调一下那个ODBC报错。因为身边真的有人被这个坑折腾了半天——他以为Nacos要用ODBC方式去连达梦于是在Windows上配置ODBC数据源报错后怀疑是数据源名称没配对反复折腾。实际上Nacos用的是纯JDBC和ODBC八竿子打不着。如果你在连接工具里遇到这个报错检查一下工具本身的数据源配置就好和项目适配无关。4.3 别忘了处理Nacos的权限表Nacos控制台的登录认证依赖users、roles、permissions三张表在MySQL版本里这三张表的建表语句非常简洁但转换到达梦时容易忽略一个点username、role这类字段名在达梦里是保留字或者特殊含义。我建表时全部使用了带引号的大写字段并且在SQL里也用双引号把字段名包裹起来避免关键字冲突。另外Nacos从2.x开始默认开启鉴权如果适配后控制台能登录但权限变更不生效多半是你没有在application.properties里设置nacos.core.auth.enabledtrue nacos.core.auth.plugin.nacos.token.secret.key你的自定义密钥不设置这个Nacos会使用默认的密钥有安全风险而且某些版本里还会导致登录态校验异常。配置完后重启再试。5. 最后说几句实在话整套适配做下来我最深刻的体会是把Nacos的数据源换成达梦真正的难点不在数据源Provider而在于你愿不愿意花时间把Nacos内部那些藏着MySQL方言的SQL一条条挖出来改掉。不要指望有一个中间件能一键搞定所有SQL转译至少在达梦和MySQL之间语法差异远比想象中多。根据我个人的操作习惯建议在动手前先做两件事一是把Nacos源码在本地编译一遍保证你对整个项目结构有数二是确认你手上达梦的版本和兼容模式到底偏向Oracle还是MySQL这直接决定了SQL改写的方向。我这边用了偏向Oracle的兼容模式整体改起来顺手很多。最后再分享一个小技巧改造过程中我会新建一个文档专门记录每一处SQL的修改原因和对应功能点比如“分页改造-配置列表查询-涉及类在ConfigInfoMapper”。这样后续达梦版本升级、Nacos版本升级时不用重新把源码从头翻一遍直接对照历史记录检查改动项就行。这个习惯帮我省了非常多时间。希望能给你的Nacos迁移之路提供一些参考。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进