ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

从MySQL到Kingbase:Nacos注册中心存储层国产化适配实践

从MySQL到Kingbase:Nacos注册中心存储层国产化适配实践

1. 为什么要把Nacos迁到Kingbase,这事的难度在哪

先回答一个很多人会问的问题:Nacos 2.5.2默认就能跑,为什么非要去动数据库?因为信创项目对整条技术栈有硬性审计要求,数据库就是其中一项。如果注册中心和配置中心的数据还落在MySQL实例上,测评的时候这一项直接不过。我这边遇到的就是标准的三节点微服务底座,Nacos既要当注册中心又要管配置,业务侧全是Spring Cloud Alibaba体系,服务发现、配置管理全依赖它。做数据库国产化替换时,最麻烦的不是业务库,反而是这些中间件的存储层。

听上去只是把数据库从MySQL换成Kingbase,改个连接串就行,真做起来完全不是这么回事。Nacos的存储层设计得很重,涉及配置信息、历史版本、用户权限、服务状态、命名空间等十几张表,整个持久化逻辑里有大量MySQL的痕迹。比如建表脚本用ENGINE=InnoDB和AUTO_INCREMENT,初始化数据里有ON DUPLICATE KEY UPDATE,字段类型有datetime、tinyint(1),这些在Kingbase里如果不做转换,要么直接语法报错,要么跑起来之后自增逻辑和类型判断出错。所以这个适配工作,核心就两件事:让驱动能被Nacos正确加载,让SQL方言能被Kingbase完整消化。

还有一个容易忽略的点:Nacos 2.5.2有鉴权、有租户、有命名空间,这些元数据同样要落到数据库。如果只盯着核心配置表适配,登录和权限验证在运行时会陆续报错。我在刚开始规划的时候,就把问题拆成了四块:驱动接入、建表脚本、运行时SQL、配置与验证。每块单独验证通过以后再做整体联调,这个拆解思路建议你也直接用起来,能省掉大量无头绪的排查时间。

1.1 信创适配的典型场景

我这次是在一个全新的信创环境里部署,操作系统和数据库都有国产化要求,业务系统全部切到国产数据库,Nacos作为基础中间件当然也得跟着切。当时有同事觉得“Kingbase不是兼容MySQL吗,直接连不就行了”,这个想法只能说对了一半。Kingbase在兼容模式下的确能接受不少MySQL语法,但Nacos的初始化脚本和运行时生成的SQL并没有完全停留在MySQL最通用的那部分,兼容模式帮不了所有忙。实际操作的时候,还是要把建表脚本整体过一遍,把所有MySQL独有的部分手工改写掉,不能指望一条连接串解决所有问题。

另外,信创环境往往还有个特点:网络策略比较严格,数据库端口、应用端口都要走白名单。Nacos默认端口是8848,Kingbase默认端口不是常见的5432而是54321,如果防火墙或者安全组没有提前放行,启动阶段会发现连接一直超时。这种问题跟代码没关系,但排查起来很耗时间。我建议在动手之前先确认目标环境的端口互通情况,用最简单的JDBC测试程序先连一下库,把网络层的问题排除掉,再进入正式的适配流程。

1.2 为什么选择Nacos 2.5.2这个版本

版本选择是我在动手前犹豫过的一件事。Nacos 2.5.2是2.5系列里比较新的补丁版本,相比老版本,它在鉴权默认策略、配置灰度、控制台交互上有不少调整。尤其要注意,从2.4.0开始Nacos服务端默认开启鉴权,如果直接照搬网上那种“改个数据源就能跑”的旧教程,很可能会在登录阶段栽跟头。因为除了数据源配置,还要把token密钥、身份标识这些鉴权参数补齐,否则控制台打不开,OpenAPI请求也会被拦。

选2.5.2还有一个现实原因:它的数据源抽象做得更干净,内部对存储层做了一些隔离,替换底层数据库时不需要改动业务API。虽然官方没有直接提供Kingbase的适配插件,但Nacos在2.2之后陆续支持过其他国产数据库,设计上留了扩展空间。从实操角度说,2.5.2的包结构里配置文件和驱动加载路径都比较清晰,外挂JDBC驱动是可行方案。如果选过于老的版本,这些改造点会分散在代码里,难度反而更大。

2. 适配方案选型:改包还是改配置?先把三条路想清楚

2.1 方案一:官方发行包加外挂驱动

这个方案思路最简单:Nacos的启动脚本会把lib目录下的所有jar加入classpath,那我只要把Kingbase的JDBC驱动jar复制到nacos/lib下面,再修改conf/application.properties里的数据源配置,理论上就能连上Kingbase。好处是全程不用自己编译源码,不会引入Nacos内部的构建问题,工作量小、风险低,非常适合先把主链路快速打通。

坏处是,如果Nacos运行时生成某些MySQL特定语法,靠改配置是解决不了的。比如说在一些初始化场景下,Nacos内部可能会执行INSERT ... ON DUPLICATE KEY UPDATE这类写法,这种语法在Kingbase的非兼容模式下是不认的。不过在开启兼容模式之后,这类问题能减少很多,所以这条路在实际项目中跑通的概率并不低。我最后走的就是这条路,但前面做了不少准备工作,并不是简单丢个jar进去就完事。

2.2 方案二:源码编译,彻底改写SQL方言

更彻底的做法是把Nacos源码拉下来,把跟MySQL强绑定的部分全部改掉。具体来说,在pom.xml里增加Kingbase驱动依赖,再把运行时SQL里的MySQL方言改成标准SQL,最后自己重新打包。这个方案能实现对SQL语句的完全掌控,某些特殊场景下确实比依赖兼容模式更保险。比如Nacos内部有些批量写入语句,在源码层面改成标准SQL后,不管Kingbase开不开兼容模式都能跑。

但代价也很明显:Nacos源码模块非常多,涉及nacos-config、nacos-naming、nacos-console等,改动一个数据源问题要重新梳理整个构建链。而且改完以后,你相当于维护了一个内部定制版本,后续官方升级Nacos补丁时,这些改动都要重新适配一遍。对一般企业项目来说,这个维护成本着实有点高,我也一直没有走到这条路,除非官方原生的方式彻底跑不通,否则不建议轻易尝试。

2.3 方案三:让Kingbase开启MySQL兼容

第三种路线看起来最省事,就是把Kingbase当成MySQL来用。建库的时候选择MySQL兼容模式,然后拿Nacos自带的nacos-mysql.sql直接去执行。因为我用的Kingbase版本支持这种兼容特性,不少表结构确实能顺利建出来,部分初始数据也能插入成功。

但我实际试下来发现,MySQL兼容模式并不是万能的。Nacos的数据库脚本里有几处写法是兼容模式也处理不好的,比如ENGINE=InnoDB这种存储引擎指定,以及列级别的COMMENT 'xxx'。这些语法在MySQL里是家常便饭,到了Kingbase兼容模式下还是会报错。所以哪怕走兼容模式路线,脚本的准备工作也不能省。换句话说,兼容模式能帮我们减少一部分SQL改写工作量,但没办法完全替代人工清洗。

最终我选择的是这样一个组合:官方发行包加外挂驱动,配合Kingbase的MySQL兼容模式,同时把建表脚本手工改写一遍。这条路线在保证可控性的前提下,把实施成本压到了最低,全程不需要碰Nacos源码,后续升级Nacos版本时处理起来也相对简单。如果你也在做同类适配,没有特殊定制需求的话,这个组合基本是最优解。

3. 适配前的准备:驱动、库表权限与SQL脚本改写

3.1 准备合适版本的JDBC驱动

Kingbase官方提供的JDBC驱动,类名是com.kingbase8.Driver,对应的连接URL格式是jdbc:kingbase8://ip:端口/数据库名。默认端口不是5432,而是54321,这个细节很容易踩坑,很多人下意识会按照PostgreSQL的默认端口来配,结果连不上。第一次配置时,我建议先确认数据库服务端实际监听的端口,可以通过数据库管理工具查,也可以直接问DBA。

驱动jar的版本要和数据库服务端版本匹配。我这次用的Kingbase是V8系列,驱动用kingbase8开头的jar基本没问题。如果你拿到的是老的7.x版本驱动,建议先和服务端核对,否则会报协议不兼容或者类加载异常。另外,驱动jar的引入方式也要注意,因为Nacos启动脚本会自动加载lib目录下的jar,所以最简单的做法就是把这个驱动jar直接复制到nacos/lib中。复制前先看一眼里面有没有同类的旧驱动,避免多个版本的驱动同时出现在classpath里导致加载顺序错乱。

3.2 建库建用户与执行建表脚本的注意点

在Kingbase里创建数据库时,建议把字符集设为UTF8,避免中文注释或配置内容出现乱码。命名方面,库名我用的是nacos,账号单独建了一个nacos_user,没有使用数据库的超级用户来跑Nacos。权限只需要授予这个账号在nacos库下的建表、增删改查权限,不需要更多。过度授权对安全审计来说是个减分项,能收敛就收敛。

建表脚本执行前,一定要把nacos-mysql.sql完整改写一遍。除了去掉MySQL专属语法,还要注意脚本自带了一些初始数据,比如默认管理员账号和内置角色。这些INSERT语句如果带了MySQL特殊写法,同样要提前处理。执行时建议用数据库自带的可视化工具或命令行逐条执行,不要一次性批量跑完。因为如果脚本中间某条SQL报错,后面的表可能不会继续执行,留下一堆半成品表,再排查起来反而更麻烦。我习惯的做法是先把整个脚本按注释的建表段落拆开,逐个表执行,每张表建完以后确认一下结果,再继续下一张。

3.3 SQL脚本改写核心点

我整理了一下,主要的改写点可以归成七类,把它们全部处理干净,脚本基本就能在Kingbase上顺利运行:

MySQL写法Kingbase改写方案说明
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4删除字符集在建库时统一指定
id bigint(20) NOT NULL AUTO_INCREMENTid bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY使用自增标识列
datetime NOT NULLtimestamp NOT NULL时间类型替换
tinyint(1)smallint布尔值用短整型表示
ON UPDATE CURRENT_TIMESTAMP删除该列属性Kingbase不支持列级自动更新
KEY idx_name (field(64))KEY idx_name (field)去掉索引字段的前缀长度
COMMENT 'xxx'删除或改为COMMENT ON COLUMN注释语法不兼容
INSERT ... ON DUPLICATE KEY UPDATEINSERT ... ON CONFLICT ... DO UPDATE冲突处理改写

这几类问题里,最容易忽略的是索引前缀长度。Nacos的脚本里有几个索引是建在varchar字段上的,MySQL允许在索引里指定字段前N个字符,比如(data_id(64)),但Kingbase里不支持这种语法,会直接报错。我把所有索引定义扫了一遍,遇到带数字长度的全部改成不带长度的写法,然后重新执行。

另外,列级注释我直接全部删掉了。因为Nacos的建表脚本里,几乎每个字段后面都跟着COMMENT 'xxx',要改成Kingbase标准写法得拆成一句一句的COMMENT ON COLUMN,工作量大且容易漏。反正注释不影响功能,删掉以后脚本会干净很多。表级别的注释可以保留,转成标准SQL的COMMENT ON TABLE也没问题,但我图省事,同样是直接省略。表结构本身的字段信息对于后期维护已经够用了,这次适配的重点是先让系统跑起来。

4. 配置改造与启动验证

4.1 application.properties的完整改造

Nacos 2.5.2的数据源配置集中在conf/application.properties。默认内容指向MySQL,改成Kingbase时,我最终保留了spring.datasource.platform=mysql这个值。这一点可能很多人不理解,我解释一下:Nacos内部靠这个参数判断数据源类型,它认识的可选值只有mysql、derby、empty等,并没有kingbase这个选项。如果乱填一个,启动时会直接走默认的Derby模式,反而不是我们想要的效果。

所以实际落地时,数据源类型继续用mysql,但JDBC URL指向Kingbase。Nacos内部的数据访问逻辑仍按MySQL那一套走,配合Kingbase的MySQL兼容模式,整个链路是通的。具体配置如下:

spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:kingbase8://192.168.10.20:54321/nacos?characterEncoding=UTF8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true db.user.0=nacos_user db.password.0=你的密码

这里有几个细节需要特别注意。第一,Nacos 2.5.2默认开启鉴权,我只改数据源不补鉴权参数,控制台登录会报错。所以我在配置里额外加了三行:

nacos.core.auth.plugin.nacos.token.secret.key=请替换成一段足够长的随机字符串,建议大于32字节 nacos.core.auth.server.identity.key=serverIdentity nacos.core.auth.server.identity.value=security

token.secret.key的值要复制到所有Nacos节点保持一致,不然多节点之间鉴权信息不互通。第二,连接参数里不要写useSSL=false这类MySQL专属参数,Kingbase驱动不认,写了反而会报参数不支持。第三,connectTimeout和socketTimeout建议保留,默认值对生产环境偏宽松。万一数据库端有抖动,Nacos能更快感知连接异常,不至于一直挂在等待上。

4.2 驱动外挂方式与启动验证

配置改完以后,把kingbase8的驱动jar放到Nacos目录的lib文件夹中。Nacos的启动脚本会把lib下的所有jar加入classpath,所以这步做完驱动就能被识别。放完之后,不要急着直接启动Nacos,先做一个静态检查——确认lib目录下没有残留的旧版MySQL驱动,或者同名的旧版kingbase驱动。我遇到过因为旧驱动没清理,导致Nacos优先加载了错误类的情况,启动日志里全是奇怪的类转换异常,定位起来很费劲。

启动服务后,重点观察日志中数据源初始化的部分。如果看到连接成功或者数据源重置相关的日志,说明数据库已经连上。如果报Cannot load driver class: com.kingbase8.Driver,先检查jar是否真的在lib目录下,再确认驱动类名是否写对。有个小经验:千万别用org.postgresql.Driver去凑数。虽然Kingbase和PostgreSQL有渊源,但JDBC驱动类名是完全不同的。替换成PostgreSQL驱动哪怕能连上,后续的SQL方言适配也会出各种奇怪问题。

4.3 功能回归验证

数据库选型切换这种改动,最怕的是“能启动但功能有问题”。所以我做了一整套回归验证,覆盖Nacos最核心的几条链路,确保不是仅能启动而已。

第一,配置发布与读取。在控制台新建一个dataId为test-config.yaml的配置,内容随便写一段YAML,发布成功后用OpenAPI拉取一下:

curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=test-config.yaml&group=DEFAULT_GROUP"

返回内容与发布内容一致,说明config_info表的读写正常。第二步,把配置内容改一次,再改第二次,这时历史版本里应该出现两条记录,接着把旧版本回滚,确认回滚成功。这一步主要验证config_history表的时序写入是否正常。

第三,服务注册与发现。注册一个临时实例到Nacos:

curl -X POST "http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=demo-service&ip=127.0.0.1&port=8080"

然后在控制台的服务列表里能看到demo-service。点进详情,确认实例的IP和端口正确。这里重点看服务实例表的健康状态列,如果状态一直不对,多半是类型映射出了问题。

第四,命名空间与权限。新建一个命名空间,把某个配置放到新命名空间下,再用客户端切换上下文读取。另外创建一个小权限账号,用它登录控制台,确认只能看到授权范围内的配置。这一步是为了验证roles和permissions这两张鉴权相关表的查询逻辑。

全套回归跑下来,基本可以断定Nacos在Kingbase上的适配是完整的,而不是简单能启动。通过这套验证,我也敢放心把配置同步到其他两个节点的环境里。

5. 常见问题与排查实录

5.1 驱动加载失败与ClassNotFound

这个现象最常见,启动日志直接抛Cannot load driver class: com.kingbase8.Driver。排查顺序很简单:第一,确认jar确实在lib目录下;第二,确认application.properties里的驱动类名和jar包里的类名一致,注意不是org.postgresql.Driver;第三,查看lib下有没有冲突的旧版本驱动。我在测试环境遇到过一种特殊情况,jar包名完全正常,但里面的类被替换过,这种魔改驱动靠肉眼根本看不出来。所以如果怀疑驱动本身有问题,可以先写一个几十行的JDBC测试类直接连接数据库,把问题范围圈定到Nacos之外。

5.2 表名大小写导致relation not found

Nacos的部分SQL语句里,表名是大写或者带引号的。Kingbase默认行为是:不带引号的标识符折叠为小写,带引号的大写标识符严格区分大小写。如果建表时报建的是CONFIG_INFO,而Nacos查询语句用的是config_info,就会报relation "config_info" does not exist。遇到这类问题,统一的做法是全流程使用小写表名,并且在建表脚本里不要给表名加引号。建表脚本清洗阶段如果提前把这个处理好,后面能少掉一大半报错。我在第一轮建表时犯过这个错误,后来把所有表名统一成小写,重新执行一遍就好了。

5.3 主键自增与序列问题

用MySQL的AUTO_INCREMENT脚本直接建表以后,插入数据时很容易报Field 'id' doesn't have a default value。原因很简单,Kingbase里没有真正的AUTO_INCREMENT概念。我按前面说的改成GENERATED BY DEFAULT AS IDENTITY之后,自增能力就由数据库内部序列来承担,这个问题就消失了。

还有一个额外问题:如果脚本执行了一半,部分表已经建成功,后面再改再执行,序列的当前值可能已经跳了一截。因为之前部分成功时可能已经插入了若干行数据,序列被消费过,重新建表不能完全重置。所以正式验证时,最好把整个库清空,重新完整执行一遍脚本,确保所有表的序列状态干净。尤其是config_info和his_config_info这两张核心表,序列错乱对后续数据插入影响最明显。

5.4 批量语句和ON CONFLICT语法兼容

Nacos的初始化数据里有一批INSERT ... ON DUPLICATE KEY UPDATE语句。Kingbase的MySQL兼容模式通常能识别出来,但如果数据库初始化参数没有完全开启兼容特性,这条语句就会直接报语法错误。我建议在脚本清洗阶段就把这类语句统一改写,不要依赖兼容模式兜底。改写方式就是前面提到的转成ON CONFLICT语法,或者把初始化数据单独抽出来,在应用启动前通过独立任务插入一次,这样和Nacos自身的初始化逻辑完全解耦。后一种做法更适合需要反复初始化多套环境的场景,因为可以写成一个幂等的初始化脚本,每次执行结果都一样。

5.5 鉴权相关表初始化失败

Nacos 2.5.2默认开启鉴权,如果users、roles、permissions这几张表没有正确建起来,登录时会一直提示认证失败,甚至控制台页面都打不开。排查时先看数据库里这几张表是否存在,再确认users表里默认管理员账号的密码加密串有没有正确写入。如果表存在但登录依然失败,多半是application.properties里的token密钥没配置,或者配置的服务端身份标识不一致。这类问题从日志关键字入手比较快,启动日志里出现auth、token相关报错时,优先检查鉴权配置,不要钻进SQL兼容性的死胡同。

5.6 连接超时与参数不支持

还有一个高频问题,日志里出现connection timeout或者Unsupported connection property。前者通常是网络不通或者端口错误,需要回到最基本的网络检查。后者则是URL里带了MySQL专属参数,最常见的是useSSL=false和serverTimezone=UTC。Kingbase的JDBC驱动不认识这些参数,会把整个连接串判定为非法。我在配置里把URL精简成只有characterEncoding=UTF8、connectTimeout=1000、socketTimeout=3000、autoReconnect=true、useUnicode=true,多余参数一概不加,这样最稳。

6. 一些值得沉淀的经验

整个适配做下来,我最深的体会是:国产数据库适配这件事,很多坑其实不是数据库本身不够好,而是应用对MySQL的隐性依赖比想象中多。改连接串谁都会,但把隐藏的SQL方言问题一个个揪出来,才是真正花时间的地方。如果有朋友要复现这个项目,我会建议他把建表脚本清洗放在最前面,并且用一整套回归用例验证功能,而不是只看服务能不能启动。

版本问题一定要提前锁死。我用的是Nacos 2.5.2和Kingbase V8系列驱动,如果你手里的版本不一样,有些配置项名称或者鉴权默认行为可能会变。遇到问题时,先去翻对应版本的官方文档,不要盲目照搬网络上的旧经验。尤其是Nacos从2.4开始的鉴权默认策略变化,影响面其实比很多人预期的大。

最后再分享一个小技巧:把改写后的建表脚本纳入项目版本控制,和Nacos的配置文件放在一起管理。这样每次搭建新环境,只需要执行同一个脚本、复制同一份配置就能复现整套适配环境。第一次做适配时踩过的坑,也能固化在脚本和文档里,后面的人不用再走一遍弯路。我后来给另一个环境做同样部署时,全程只花了一个多小时,大部分时间还花在等驱动下载上,这就是沉淀的价值。

RELATED READING

延伸阅读

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