Nacos适配达梦与人大金仓:国产数据库持久化改造实战
1. 先搞清楚Nacos为什么连不上达梦和人大金仓这两年做信创改造的朋友应该都有体会项目要过验收中间件得换数据库也得换。Nacos做注册中心和配置中心本来挺顺手的可一旦后端存储从MySQL切成达梦或者人大金仓事情立马变复杂——启动日志里一堆建表失败、语法错误、驱动找不到控制台能进去但配置列表刷不出来集群节点之间数据还对不上。标题里说的nacos适配达梦、人大金仓数据库本质就是解决这么一件事让Nacos的配置持久化和集群数据存储从只认MySQL变成能在达梦DM8和人大金仓KingbaseES上正常跑起来包括建表、增删改查、分页、分布式锁、集群节点数据同步这一整套。这篇文章适合三类人看一是正在做国产化替换的后端或运维手里已经有Nacos需要把存储切过去二是要交付信创项目的同学得提前把坑摸清楚三是纯粹好奇Nacos内部持久化怎么设计的想顺着源码走一遍。我会把改造路线、建表脚本怎么翻、连接池参数怎么写、分页和方言这些硬骨头怎么啃还有踩过的坑全部摊开讲。涉及源码改动的地方我会说明是基于常见实践的合理方案具体到你手里的版本键名和模块结构可能略有差别动手前先对着你自己拉下来的源码核对一遍。1.1 Nacos的持久化到底依赖了什么先建立一个基本认知Nacos不是所有数据都存在数据库里。它内部是分层的临时实例ephemeral走的是内存加自研的Distro协议做节点间同步这部分压根不落库而配置中心的数据、命名空间的元数据、集群节点的容量信息这些必须持久化落库的入口集中在持久化模块里。1.x时代的做法比较粗暴直接在源码里写死了MySQL的SQL语句连建表脚本都只给了一份mysql-schema.sql到了2.xNacos把数据源做成了插件化结构不同数据库对应不同的插件模块你新增一个数据库适配等于照着已有插件再写一份。这个设计变化很关键。它意味着在2.x上做适配你不用去改核心业务代码只需要新增一个插件模块把Mapper接口和对应的SQL实现出来再通过配置项告诉Nacos用哪个插件即可。1.x就没这么幸运得直接动源码里那批Mapper和ServiceImpl改完还要自己维护一份长期分叉的分支后续升级会很痛苦。还有一层容易被忽略Nacos单机模式的默认存储其实是内嵌的Derby只有配了spring.datasource.platform并给了外部数据源参数才会切到外置库。很多人测的时候忘了改配置看到启动成功就以为适配好了结果一上集群就露馅。所以验证适配是否真正生效一定要看启动日志里数据源初始化那几行确认连的是达梦或金仓而不是Derby。1.2 MySQL专有语法才是真正的拦路虎驱动和连接串其实是最简单的部分难的是Nacos源码里那些MySQL特有的写法。我大概统计过散落在各个Mapper里的专有语法主要有这么几类每一类对应到国产库都得单独处理。MySQL写法出现场景达梦对应人大金仓对应LIMIT #{start}, #{size}配置分页查询、历史记录分页LIMIT #{size} OFFSET #{start}LIMIT #{size} OFFSET #{start}INSERT ... ON DUPLICATE KEY UPDATE配置写入、容量表更新用MERGE INTO或先查后写ON CONFLICT (...) DO UPDATEIFNULL(a, b)空值兜底、容量统计NVL(a, b)COALESCE(a, b)GROUP_CONCAT(...)标签聚合查询LISTAGG(...) WITHIN GROUPSTRING_AGG(...)NOW()时间字段赋值SYSDATE或NOW()NOW()ENGINEInnoDB DEFAULT CHARSETutf8建表语句尾部直接删掉直接删掉AUTO_INCREMENT主键定义IDENTITY(1,1)BIGSERIAL或GENERATED BY DEFAULT AS IDENTITY表格里看着简单实际动手会发现分页只是第一道坎ON DUPLICATE KEY UPDATE才是最容易翻车的。Nacos在写配置、更新容量这类场景里频繁用它本质是存在就更新、不存在就插入的原子操作。达梦的兼容模式对这条语句的支持度跟版本强相关靠不住稳妥做法是改成MERGE INTO或者干脆在代码层拆成先UPDATE判断影响行数、为0再INSERT。人大金仓因为血统亲近PostgreSQLON CONFLICT用起来最顺这一块反而比达梦省心。1.3 三种改造路线的成本对比实际项目里我看到过三种做法各有各的适用面。第一种是走数据库自带的兼容模式。达梦有COMPATIBLE_MODE参数人大金仓也有database_mode把实例调成MySQL兼容理论上MySQL语法能直接跑。这条路听着最省事但我不太推荐作为主方案一是不同小版本兼容度差异大某些函数还是缺二是你为了适配Nacos把整个实例设成兼容模式其他业务系统可能受影响三是升级数据库版本时兼容行为可能变埋雷。可以拿它当临时验证别当长期方案。第二种是改源码。下载对应版本的Nacos源码替换掉那批Mapper和SQL改数据源配置重新编译打包。这是最可控的方式也是目前社区里主流做法。代价是你要自己维护一个分叉Nacos官方出了新版、修了安全或功能问题你得手工合并一次。第三种是绕开Nacos自带持久化外部做一层适配。比如配置数据先落到达梦再用同步工具同步到Nacos背后的MySQL。这能跑通但引入了额外的数据同步链路和延迟配置变更的一致性和实时性都变差运维复杂度反而上升。除非你实在拿不到源码编译权限否则不划算。综合下来如果用的是Nacos 2.x我建议走插件方式改源码如果是1.x老版本要么整体升级到2.x再适配要么就老老实实改源码并锁定版本。下面讲的具体操作默认以2.x为基准展开1.x的差异我会单独点出来。2. 动手前的环境盘点与依赖准备改造之前先把环境对齐这一步省下的时间后面排查时会加倍还回来。我见过太多人卡在驱动找不到版本不匹配上耗掉大半天其实都是准备阶段没做扎实。2.1 版本组合怎么选才不踩雷Nacos版本、数据库版本、JDK版本这三者的组合直接决定了你后面是顺利还是折腾。下面这个组合是我实测下来比较稳的搭配可以当起点。组件推荐版本说明Nacos2.2.x / 2.3.x2.2之后数据源插件机制比较成熟JDK8 或 11跟Nacos版本对齐别乱跳达梦 DM88.1.2以上太低版本对LIMIT n OFFSET m支持不全人大金仓V8R6 及以上PostgreSQL内核较新ON CONFLICT稳定Maven3.6编译Nacos源码版本别太老JDK这块特别提醒一句Nacos 2.x对JDK 8和JDK 11都支持但如果你编译时用的JDK和运行时不一致可能出现编译过、启动报UnsupportedClassVersionError的情况。团队协作时最好把JAVA_HOME写进构建脚本别依赖各人本地环境。数据库版本也不是越新越好。达梦某些较早的8.1.1.x版本对OFFSET语法解析有问题分页查配置会直接报语法错人大金仓V8R3的PG内核偏老部分窗口函数行为跟新版不一致。有条件的话先用测试实例把分页和并发写入跑一遍再定。2.2 达梦与人大金仓的驱动包入库国产数据库的JDBC驱动通常不在中央仓库里得手动装进本地或私服。这一步不做编译时依赖解析直接失败。达梦驱动装本地仓库的命令大致是这样路径和版本号按你实际拿到的jar改mvn install:install-file \ -DfileDmJdbcDriver18.jar \ -DgroupIdcom.dameng \ -DartifactIdDmJdbcDriver18 \ -Dversion8.1.3.62 \ -Dpackagingjar人大金仓同理mvn install:install-file \ -Dfilekingbase8-8.6.0.jar \ -DgroupIdcn.com.kingbase \ -DartifactIdkingbase8 \ -Dversion8.6.0 \ -Dpackagingjar装完之后在Nacos源码里对应插件模块的pom.xml中引用这两个坐标。如果团队有私服记得推一份上去否则CI流水线编译会挂。驱动类名也要记住达梦是dm.jdbc.driver.DmDriver人大金仓是com.kingbase8.Driver写错一个字母就是ClassNotFoundException。注意驱动jar的版本要和数据库服务端版本尽量靠近驱动太新或太旧都可能出现元数据获取异常表现为能连上但查表报奇怪的错。2.3 编译环境与源码拉取源码从官方仓库拉对应tag别直接拉主干主干可能正在重构编译不过。拉下来之后先不改代码跑一次原版编译确认本地环境能出包。这一步很关键——如果原版都编译不过先解决环境问题别把编译失败误判成改造引入的。原版编译通过后找到数据源插件目录。2.x的结构里你会看到按数据库分目录的插件模块MySQL、PostgreSQL各占一份。我们的思路就是照着PostgreSQL那份复制一份改成达梦或金仓的方言而不是从零写。照葫芦画瓢比凭空造轮子稳得多因为接口方法签名、返回值类型都是现成的。编译前还要注意Maven的镜像配置。如果你的环境只能用内网仓库确认驱动和Nacos依赖的第三方包在内网都有代理否则会出现依赖找不到但报错信息很含糊的情况。3. 核心改造建表脚本、数据源与SQL方言准备工作做完进入真正的体力活。这一章是整篇的重点我按建表、配置、代码三层来讲。3.1 从MySQL脚本翻译到国产数据库的建表实践Nacos官方给的mysql-schema.sql是所有改造的起点。你不能直接拿去执行得逐条翻译。我建议拿一张表先跑通比如config_info它是配置中心的核心表字段最多、索引最全把它搞定其他表基本是重复劳动。先说主键。MySQL里是id BIGINT(20) NOT NULL AUTO_INCREMENT达梦改成id BIGINT IDENTITY(1,1) NOT NULL人大金仓改成id BIGSERIAL NOT NULL或者id BIGINT GENERATED BY DEFAULT AS IDENTITY。注意达梦的IDENTITY(1,1)两个参数分别是起始值和步长别只写一个。再说长文本字段。content这类字段MySQL用LONGTEXT国产库里没有这个类型。达梦用CLOB或TEXT人大金仓用TEXT。这里有个隐藏坑达梦的TEXT底层就是CLOB某些客户端工具显示时会有长度限制别误以为写失败了。时间字段也要动。MySQL常见写法是gmt_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP这个大多数国产库都支持但MySQL特有的ON UPDATE CURRENT_TIMESTAMP在达梦上不认得删掉把时间维护放到应用层也就是SQL语句里显式赋值。Nacos本身写数据时基本都会带上时间字段所以删掉默认值影响不大但你要确认一遍Mapper里确实赋了值。索引和唯一约束的写法差异也不小。MySQL的UNIQUE KEY uk_xxx (col1, col2)要改成CREATE UNIQUE INDEX uk_xxx ON config_info(col1, col2)达梦和人大金仓都支持这种标准写法。Nacos里config_info那张表的唯一键是data_id group_id tenant_id的组合这个键千万不能少否则同一个配置会被重复写入查询时出现多条记录控制台就乱套了。实操心得达梦建库时有LENGTH_IN_CHAR参数默认是按字节计长度。Nacos里data_id VARCHAR(255)如果存中文按字节算可能超限报错。建库时把它设成按字符计长或者干脆把这类字段扩到VARCHAR(512)一劳永逸。执行建表脚本的顺序也有讲究。有外键或者依赖关系的表要先建主表Nacos这边基本没有硬外键但config_tags_relation这类关联表建议放在config_info之后建思路清晰些。脚本执行完用客户端连上去逐张表SELECT * FROM xxx WHERE 10跑一遍确认表名、列名都没拼错。3.2 数据源与HikariCP连接池配置怎么写表和脚本就位接下来是数据源配置。Nacos的application.properties里你需要改的是这几个区块数据库平台标识、连接地址、账号密码、连接池参数。平台标识这行告诉Nacos用哪个插件。MySQL是spring.datasource.platformmysql我们要改成自定义的标识比如dm或kingbase。注意这个值要和插件模块里注册的名字一致写错了Nacos会回退到默认实现然后报找不到表的错报错信息还不会直接告诉你平台标识不认识。连接地址、账号、密码按单库多库分别写。单库就是db.url.0、db.user.0、db.password.0这一组多库往后递增编号。达梦的连接串大概是db.url.0jdbc:dm://10.0.0.11:5236?schemaNACOScharacterEncodingUTF-8 db.user.0NACOS db.password.0你的密码人大金仓db.url.0jdbc:kingbase8://10.0.0.12:54321/NACOS?currentSchemanacos db.user.0nacos db.password.0你的密码这里要对齐你自己的版本2.x不同小版本db.user和db.user.0的键名有变化以源码里ExternalDataSourceProperties实际读取的键为准别照抄网上的配置。连接池用的是HikariCPNacos通过db.pool.config.*前缀暴露参数。下面这套是我在生产环境用着比较稳的配置可以直接抄db.pool.config.driverClassNamedm.jdbc.driver.DmDriver db.pool.config.connectionTimeout30000 db.pool.config.validationTimeout10000 db.pool.config.maximumPoolSize30 db.pool.config.minimumIdle10 db.pool.config.idleTimeout600000 db.pool.config.maxLifetime1800000 db.pool.config.connectionTestQuerySELECT 1 FROM DUAL驱动类名写在这也行也可以由平台标识推断两个都写不冲突。connectionTestQuery建议显式给上达梦的SELECT 1不带FROM DUAL在某些版本能过某些不行带上最保险。人大金仓可以直接用SELECT 1。连接池大小别拍脑袋。maximumPoolSize要结合Nacos节点数和数据库最大连接数算。假设你有3个Nacos节点每个池30个连接光Nacos就占掉90个连接达梦默认最大连接数往往没这么大很容易打满。我的经验值是单节点池控制在20到30之间节点数乘一下不超过数据库上限的70%。注意池子里连接空闲被数据库端主动断开的情况国产库有时比MySQL更激进。maxLifetime一定要设并且要小于数据库端的空闲超时时间否则会出现拿到已失效连接、报连接已关闭的问题。3.3 代码层SQL方言与分页语句调整配置改完进到Mapper层。这是最耗时的部分也是决定成败的地方。你需要在插件模块里为每个Mapper接口提供一份国产库方言的实现。分页是第一个要改的。MySQL的LIMIT #{start}, #{size}在达梦和金仓都不认得改成LIMIT #{size} OFFSET #{start}。注意参数顺序反过来了别只改关键字不改位置那是最常见的低级错误。达梦对LIMIT n OFFSET m的支持从8.1.2以后比较稳定早版本建议改用ROWNUM嵌套写法会啰嗦些。ON DUPLICATE KEY UPDATE是第二个大头。前面提过达梦建议用MERGE INTO写法大致是MERGE INTO config_info t USING (SELECT ...) s ON (t.data_id s.data_id AND ...) WHEN MATCHED THEN UPDATE SET ... WHEN NOT MATCHED THEN INSERT ...。这段语句较长建议单独抽成一个Mapper方法别塞在动态SQL里调试太痛苦。人大金仓用ON CONFLICT (data_id, group_id, tenant_id) DO UPDATE SET ...简洁不少。函数替换是第三个点。IFNULL在达梦改NVL金仓改COALESCEGROUP_CONCAT在达梦用LISTAGG(...) WITHIN GROUP (ORDER BY ...)金仓用STRING_AGG(expr, ,)。还有一处容易漏达梦在Oracle兼容模式下从子查询里SELECT *必须给别名否则报缺少关键字。SQL里凡是FROM (SELECT ...)的地方都得补上别名比如FROM (SELECT ...) tmp。时间函数NOW()达梦和金仓都支持但为了稳妥我倾向统一用标准写法达梦用SYSDATE金仓用NOW()。这个按你实际库的文档来不必强求一致。集群模式下的节点同步也依赖SQL涉及tenant_info、group_capacity这些表。这部分如果方言改错表现是单机正常、集群下部分配置莫名丢失排查起来非常绕。我的做法是先单机把所有增删改查接口手工过一遍再上集群。4. 编译打包、部署与动态刷新验证代码改完编译出包进入验证环节。验证要分层次别一步跨到集群。4.1 单机启动与日志定位先在单机模式启动startup.sh -m standalone。眼睛盯着日志的前两百行重点确认三件事数据源初始化成功、建表检查通过、连接池创建了连接。如果日志里出现Load data source failed或者大量Table or view does not exist基本是平台标识、驱动或者schema不对。一个很实用的定位技巧Nacos启动过程中会打印它加载的SQL方言实现类名。看到的是你新写的那个类说明插件生效看到还是MySQL的实现说明平台标识没生效。这一步比翻代码快得多。单机起来后进控制台新建一个配置然后直接去达梦或金仓里SELECT * FROM config_info确认数据真的落库了。再从库里手工插一条回控制台查确认能读到。两边都通说明查询和写入链路没问题。历史记录表his_config_info也别忘改一次配置看有没有产生变更记录。4.2 集群部署与外部访问端口单机验证通过切集群。集群模式需要在配置里指定节点列表每个节点单独一份cluster.conf。启动后节点之间会通过固定端口做数据同步。容器部署的话端口映射千万别漏。Nacos 2.x除了8848这个HTTP端口还有9848和9849这两个gRPC端口集群内部通信还用到7848。如果你只映射了8848表现是控制台能打开但客户端注册和订阅各种超时日志里一堆连接被拒。用Rancher或者容器编排平台部署时这几个端口要一起暴露并且对外访问地址要配成客户端能直接访问到的地址别用容器内部IP。# 容器端口暴露参考 -p 8848:8848 # 控制台与OpenAPI -p 9848:9848 # gRPC客户端通信 -p 9849:9849 # gRPC集群通信 -p 7848:7848 # 集群Raft通信集群节点数建议3个起步且是奇数。两个节点没有容错能力同步出问题时会互相拉扯。配置同步的延迟在做压测时要注意配置发布后各节点生效可能有个短暂的窗口期客户端拉取到最新值的时间不完全一致这是正常现象。4.3 配置动态刷新实测适配完之后最该验证的是动态刷新。这一步能顺带暴露很多方言问题因为刷新涉及配置比对、变更记录写入、版本更新等多个库操作。方法很简单起一个Spring Boot应用用RefreshScope绑定一个配置项然后通过控制台改这个配置的值看应用日志有没有触发刷新、值有没有变。再把这套流程在集群的三个节点上都走一遍确认无论从哪个节点改最终都能生效。实测里我遇到过一种情况单机刷新正常集群下从A节点改的配置B节点上的客户端要等十几秒才感知到。排查下来是集群同步的SQL在达梦上执行慢原因是一个带子查询的方言写法没走索引。把SQL换成达梦更擅长的写法后延迟降到一秒内。这个例子的教训是方言改造不只是能跑还要看执行计划。提示压测环节建议专门跑一轮高频配置变更比如每秒发布几次配置观察达梦或金仓的CPU和慢SQL很多性能和锁问题只有在这个场景下才会冒出来。5. 高频故障排查与实战避坑心得改造过程中遇到的报错八成都集中在这几类。我把它们整理成速查表遇到问题时先对号入座能省不少时间。5.1 连接与驱动类报错速查报错信息关键词可能原因处理方向ClassNotFoundException: dm.jdbc.driver.DmDriver驱动没打进包检查lib目录或pom依赖确认驱动在classpathConnection refused端口或地址写错达梦默认5236金仓默认54321别混用invalid username/password账号大小写或schema不对达梦默认大小写敏感确认账号名schema xxx does not existschema名和库名混淆达梦的schema要在连接串里显式指定HikariPool - Connection is not available池子太小或连接泄漏调大池子查慢SQL检查maxLifetime驱动类问题还有个隐蔽情况驱动jar打进去了但被其他包里的旧版本驱动抢先加载报错信息完全正常实际用的是错的驱动。这种情况查mvn dependency:tree把冲突的驱动排掉。5.2 SQL与建表阶段典型报错建表阶段最常见的三个错一是AUTO_INCREMENT没改直接报语法错二是达梦的子查询缺别名报缺少关键字三是数据类型不匹配比如LONGTEXT没换成CLOB。运行阶段最容易踩的是ON DUPLICATE KEY UPDATE没改干净。表现很有迷惑性第一次写配置成功第二次改同一个配置报错或者静默失败控制台显示的还是旧值。排查时把SQL日志打开把实际执行的语句复制到数据库客户端里手工跑一遍问题立刻暴露。达梦的大小写问题值得单独说。默认实例是大小写敏感的建表时如果用小写查询时也得小写一旦某处SQL写成大写就会报表或视图不存在。我的处理办法有两种一是建库时把CASE_SENSITIVE设为0不敏感二是保证所有SQL、表名、列名统一小写从脚本到代码全程一致。第一种更省心但要注意对其他业务的影响。5.3 几条花钱买不到的避坑经验第一条先跑通最小闭环再铺开。不要一上来把所有表、所有Mapper全改完再启动那样一旦有错你面对的是一大片报错。拿config_info这一条链路走通从写入到查询到分页到历史记录全绿了再复制到其他表。第二条把SQL执行计划当验收项。能跑不等于跑得快尤其是带子查询的分页和标签聚合查询在国产库上很容易走全表扫。上线前用EXPLAIN过一遍关键SQL慢的加索引或者改写。第三条版本升级前先备份改造分支。Nacos迭代挺快每次升级你都得重新合并一次方言改动冲突是免不了的。把改动集中在独立模块里合并工作量能小很多。第四条达梦和人大金仓的语法差异要建对照表。如果你手上两个库都要支持别偷懒用一套LISTAGG和STRING_AGG、INPUT和ON CONFLICT这些根本不通用。维护一份差异清单改代码时对着查比翻文档快。第五条配置中心的元数据也要备份。适配过程中经常需要反复重建表、清数据tenant_info和config_info里的数据丢了会很麻烦。改造前先mysqldump一份出来后续导入国产库也方便。我个人在几个信创项目里跑下来的体会是Nacos适配国产库的难点从来不在驱动和连接串而在那些散落的方言细节和验证方法上。把验证粒度切细、把每条SQL单独验证、把插件模块独立出来改造的确定性会高很多。这套思路同样适用于Nacos往其他国产数据库上迁换个方言实现而已骨架是一样的。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →