ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nacos 接入 PostgreSQL 数据源插件:SPI 机制与配置实践

Nacos 接入 PostgreSQL 数据源插件:SPI 机制与配置实践 简介面向使用 Nacos 220 及以上版本的开发者这份源码插件用于将配置中心数据源切换到 PostgreSQL基于 SPI 机制扩展无需改动 Nacos 核心代码仅替换 springdatasource.zip 即可完成适配。压缩包共 22 个文件、约 53KB主体包括 13 个 Java 源文件、SQL 初始化脚本、XML/POM 配置、YML 样例及 MD/TXT 说明另附 docx 文档介绍安装步骤、注意事项和常见问题目录结构清晰方便按模块理解数据源插件的加载与实现逻辑。当前已有 267 人学习下载。除了解决生产环境中的 PostgreSQL 接入问题该插件还适合作为研究 Nacos 扩展机制、阅读 SPI 源码以及准备相关技术面试的参考素材对中间件开发者和微服务运维人员尤其实用。1. 为什么 Nacos 2.2.0 要自备 PostgreSQL 数据源不碰源码只丢一个插件包把 Nacos 装起来不是问题真正的问题是装上之后它默认只认 Derby 和 MySQL。你如果公司里 PostgreSQL 已经是事实标准拿官方包一启动配置中心和注册中心会一直报连不上库大多数人这时候要么改源码重新编译要么把 PG 数据再同步一份到 MySQL两头维护版本升级一次痛一次。Nacos 2.2.0 之后引入的 SPI 数据源插件机制正是用来终结这种局面的不用碰 Nacos 源码把编译好的 PostgreSQL 数据源插件一般整成一个springdatasource.zip丢进plugins目录再改application.properties里几行spring.datasource.*配置重启即可。适合谁已有 PG 基础设施、做信创选型需要接 PG/达梦/金仓的团队以及不想长期维护 Nacos fork 的人。后面全部按「原理 → 实现 → 配置 → 踩坑」往下走。2. 读懂 Nacos 数据源插件的 SPI 机制DataSourceProvider 与 spring.datasource.platform 的匹配关系2.1 Nacos 启动时是怎么决定用哪个数据库的Nacos 2.x 的存储层不是写死在代码里的。它对外暴露了一个spring.datasource.platform配置项启动流程大概是这样的Spring Boot启动时读取该配置根据值去加载对应名字的数据源插件插件负责创建DataSource然后 Nacos 再用这个DataSource执行初始化脚本、跑后续所有 SQL。官方发行包内置了derby和mysql两个平台的实现所以单机默认走 Derby集群外置存储走 MySQL。你写spring.datasource.platformpostgresql之后启动器会去找一个叫postgresql的数据源提供者。找不到就抛异常找到了就用它。名字对不上、SPI 文件没注册、驱动没打进 jar最后都会在启动日志里以「找不到」的形式翻车。这里有个容易混淆的点很多人以为换数据库只要改驱动和 URL 就行。实际上 Nacos 内部 mapper 里有不少 MySQL 方言的写法比如分页语句的LIMIT offset,size、函数IFNULL、NOW()、建表脚本里的AUTO_INCREMENT和反引号。只换驱动不换方言服务能启动但翻几个接口就会在分页查询上暴露出语法错误。所以 PostgreSQL 插件不是「一个驱动包」而是「一个数据源 SPI 一套方言适配 一份 PG 建表脚本」的合体。2.2 SPI 三件套连接工厂、方言/分页、建表脚本我一般把 Nacos 数据源插件拆成三个部分来理解排查问题也是按这三层去查职责官方 MySQL 实现做的事PostgreSQL 插件要补的事连接管理按db.url.0、db.user.0创建 Hikari 连接池换成org.postgresql.Driver设置currentSchema、TimeZone等 PG 专属参数SQL 方言MySQL 原生语法、MySQL 风格分页覆盖 mapper 里的分页和函数差异至少让LIMIT/OFFSET能正确执行初始化脚本conf/mysql-schema.sql提供一份postgresql-schema.sql建表和索引全部用 PG 语法三个部分缺哪个启动或运行期都会出问题。连接层出问题最直接启动就报方言层出问题最隐蔽通常是你第一次查配置列表或分页拉取时长列表的时候才爆建表脚本出问题轻则表建不齐重则初始化 SQL 执行到一半中断。SPI 注册方式在不同小版本里还有差异。2.2.x 阶段我习惯用META-INF/services文件手动注册路径是META-INF/services/com.alibaba.nacos.plugin.datasource.provider.DataSourceProvider文件里写一行实现类全限定名。2.3 之后有些版本支持注解扫描但为了让一个插件包能同时兼容 2.2 到 2.5.x我建议两种方式都保留注解加上META-INF/services文件也放一份。反正不会冲突但兼容面会宽很多。2.3 为什么「一个 zip 加几行配置」能成立官方发行包解压后有个plugins目录至少 2.2.0 之后的版本是预留了的。Nacos 启动时会把plugins目录下的 jar 加载进 classpath让 SPI 机制能扫到这些外部实现。所以插件理论上不需要拆进主程序只要目录和 jar 名字不被启动脚本排除就行。标题里提到的springdatasource.zip不是一个官方固定的文件名而是我们打包时约定的交付物。常见的做法是把它做成这样springdatasource.zip ├── nacos-datasource-plugin-postgresql-1.0.0.jar │ ├── com/example/nacos/plugin/postgresql/PostgresqlDataSourceProvider.class │ └── META-INF/services/com.alibaba.nacos.plugin.datasource.provider.DataSourceProvider ├── conf/ │ └── postgresql-schema.sql └── README.mdjar 负责提供 SPI 实现conf/postgresql-schema.sql负责在 Nacos 首次启动时建表。你也可以把 schema 脚本直接打进 jar 的META-INF或类路径根目录下这样部署时只需丢一个 jar。spring.sql.init.schema-locations配置项可以显式指定脚本位置后面讲配置时会再回到这里。这个结构的好处是升级 Nacos 版本时不用重编主服务只要确认插件接口兼容重新丢一个 jar 就行。3. 把 PostgreSQL 数据源插件从零做出来工程结构与可复现命令3.1 Maven 工程与最小依赖清单先建一个标准的 Maven 工程建议用maven-shade-plugin把 PostgreSQL 驱动和 HikariCP 相关依赖打成一个 fat jar。这样部署时不需要再单独找驱动 jar血泪经验驱动丢在外置目录里最容易漏版本。project xmlnshttp://maven.apache.org/POM/4.0.0 modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdnacos-datasource-plugin-postgresql/artifactId version1.0.0/version packagingjar/packaging properties !-- 与你实际部署的 Nacos 服务端版本保持一致 -- nacos.version2.5.0/nacos.version maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target /properties dependencies !-- Nacos 数据源插件 SPI 接口 -- dependency groupIdcom.alibaba.nacos/groupId artifactIdnacos-datasource-plugin/artifactId version${nacos.version}/version scopeprovided/scope /dependency !-- PostgreSQL 驱动 -- dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId version42.6.0/version /dependency /dependencies build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.4.1/version executions execution phasepackage/phase goalsgoalshade/goal/goals configuration createDependencyReducedPomfalse/createDependencyReducedPom filters filter artifact*:*/artifact excludes excludeMETA-INF/*.SF/exclude excludeMETA-INF/*.DSA/exclude excludeMETA-INF/*.RSA/exclude /excludes /filter /filters /configuration /execution /executions /plugin /plugins /build /project依赖里我把nacos-datasource-plugin设成provided这是因为 Nacos 服务端运行时会自带这些接口不需要打进插件包也避免两个 jar 里出现重复类。org.postgresql:postgresql的版本不用强行追新选一个和你 PG 服务端版本匹配的稳定版即可42.x 系列都能用。shade 插件里过滤签名文件那几行不是玄学是把 fat jar 常见的SecurityException提前挡掉。3.2 核心实现类与 SPI 注册文件实现类的核心逻辑实现 Nacos 的DataSourceProvider接口返回平台名postgresql然后按DataSourceConfig里的 URL、用户、密码构造一个 Hikari 数据源。不同 Nacos 小版本接口方法名略有差异写代码前先打开依赖 jar 看一眼或直接mvn dependency:sources拉源码确认。package com.example.nacos.plugin.postgresql; import com.alibaba.nacos.plugin.datasource.provider.DataSourceProvider; import com.alibaba.nacos.plugin.datasource.mapper.Mapper; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; /** * Nacos PostgreSQL 数据源 Provider。 * getDataSourceProviderName 的返回值必须和 * spring.datasource.platform 配置完全一致。 */ public class PostgresqlDataSourceProvider implements DataSourceProvider { Override public String getDataSourceProviderName() { return postgresql; } Override public DataSource getDataSource(DataSourceConfig config) throws Exception { HikariConfig hikariConfig new HikariConfig(); hikariConfig.setJdbcUrl(config.getUrl()); hikariConfig.setUsername(config.getUsername()); hikariConfig.setPassword(config.getPassword()); hikariConfig.setDriverClassName(org.postgresql.Driver); // PG 连接参数显式指定 schema 避免连错 search_path hikariConfig.addDataSourceProperty(currentSchema, public); // 时区统一走业务时区避免 last_modified 等字段差 8 小时 hikariConfig.addDataSourceProperty(TimeZone, Asia/Shanghai); hikariConfig.setConnectionTimeout(30000); hikariConfig.setMaximumPoolSize(20); return new HikariDataSource(hikariConfig); } Override public Class? extends Mapper getMapperClass() { return PostgresqlMapper.class; } }getDataSourceProviderName()返回的字符串是匹配关键和application.properties里spring.datasource.platformpostgresql对不上SPI 加载到了也不会用。getMapperClass()是指向方言适配 Mapper 的入口PostgreSQL 插件需要写一个PostgresqlMapper把 Nacos 内置 SQL 里的 MySQL 分页写法覆盖成 PG 的LIMIT ? OFFSET ?。如果只做数据源不覆盖 Mapper很多列表查询会在运行期报错。注册文件路径是src/main/resources/META-INF/services/com.alibaba.nacos.plugin.datasource.provider.DataSourceProvider内容只有一行com.example.nacos.plugin.postgresql.PostgresqlDataSourceProvider注意文件名的接口全限定名如果拼错SPI 加载静默失败日志不报错但启动时就是找不到 provider这种「黑匣子」问题最容易让人在配置上反复折腾。为了兼容 2.3 之后的注解扫描可以在类上再标一个Service注解但建议保留 services 文件作为兜底。3.3 移植建表脚本MySQL 语法改写成 PostgreSQL 的关键差异Nacos 官方发行包的conf/mysql-schema.sql不能直接在 PG 里执行至少这几类语法要改反引号要去掉bigint(20) AUTO_INCREMENT改成BIGSERIAL或IDENTITYKEY idx_name(col)索引定义改成CREATE INDEXTIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP里的ON UPDATE部分 PG 不支持需要删掉或改写COLLATE utf8_bin直接去掉。以config_info表为例MySQL 原文大致是CREATE TABLE config_info ( id bigint(20) NOT NULL AUTO_INCREMENT, data_id varchar(255) NOT NULL, group_id varchar(128) DEFAULT NULL, content longtext NOT NULL, md5 varchar(32) DEFAULT NULL, 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, tenant_id varchar(128) DEFAULT , PRIMARY KEY (id), KEY uk_configinfo_datagrouptenant (data_id,group_id,tenant_id) );PostgreSQL 版本改写后CREATE TABLE config_info ( id BIGSERIAL PRIMARY KEY, data_id VARCHAR(255) NOT NULL, group_id VARCHAR(128) DEFAULT NULL, content TEXT NOT NULL, md5 VARCHAR(32) DEFAULT NULL, gmt_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gmt_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP, src_user TEXT, src_ip VARCHAR(50) DEFAULT NULL, tenant_id VARCHAR(128) DEFAULT ); CREATE UNIQUE INDEX uk_configinfo_datagrouptenant ON config_info (data_id, group_id, tenant_id);bigint 带不带长度其实 PG 无所谓但为了少踩坑统一不带。longtext在 PG 里用TEXT或JSONB看场景Nacos 配置内容存的就是纯文本TEXT够用。gmt_create的默认值用CURRENT_TIMESTAMP没问题但注意如果脚本里还带ON UPDATE CURRENT_TIMESTAMPPG 不认建表会失败。history_config_info、config_tags_relation这些表同理逐张改一遍。3.4 打包、解压、落位到 plugins 目录# 拉依赖、编译、打 fat jar mvn -DskipTests clean package # 进入 Nacos 发行包根目录 cd /opt/nacos # plugins 目录不存在就创建 mkdir -p plugins # 把插件 jar 放进去 cp /path/to/your/build/nacos-datasource-plugin-postgresql-1.0.0.jar plugins/ # 启动默认单机模式 bin/startup.sh -m standalone我一般不会直接把整个springdatasource.zip解压到plugins根目录而是只放 jarschema 脚本放conf/下再用spring.sql.init.schema-locations显式指定。这样目录里只有一个成品 jar出错时排查面小。如果按 zip 交付给运维解压到 Nacos 根目录后检查plugins目录下是「jar 文件」而不是「嵌套的文件夹」SPI 不会递归扫描文件夹里的 jar。4. 修改 application.properties最小改动清单与必调参数4.1 从 MySQL 模板改到 PostgreSQL四个关键配置Nacos 的application.properties里如果之前配过 MySQL那改了数据库只需要动这四行# 数据源平台标识必须与插件 getDataSourceProviderName() 对应 spring.datasource.platformpostgresql # 数据库实例数量单库固定 1 db.num1 # 连接地址jdbc:postgresql://host:port/database db.url.0jdbc:postgresql://127.0.0.1:5432/nacos?currentSchemapublicconnectTimeout5000socketTimeout60000TimeZoneAsia/Shanghai db.user.0nacos db.password.0你的密码db.url.0末尾的currentSchemapublic是 PG 连接串里少踩坑的关键。如果 Nacos 账号默认search_path不是public建表和后续查询容易跑到别的 schema 下。connectTimeout5000单位是秒PG 驱动这个参数和 MySQL 的connectTimeout含义类似但单位不同PG 是秒别直接照搬 MySQL 的毫秒值。socketTimeout60000让慢 SQL 不会无限挂起。4.2 连接池与初始化脚本参数表下面是连接池和初始化相关的核心参数按「需要改 / 可选 / 注意」三档列一下配置项默认值PostgreSQL 建议值说明spring.datasource.platformmysqlpostgresql和插件 Provider 名字强绑定db.num11多数据源时才需要改db.url.0无PG 连接串带currentSchema和时区db.user.0无Nacos 专用账号不建议用postgres超级用户spring.sql.init.schema-locations无file:/opt/nacos/conf/postgresql-schema.sql脚本未随 jar 提供时显式指定db.pool.config.connectionTimeout3000030000连不上时快速失败db.pool.config.maximumPoolSize2020和 PGmax_connections联动db.pool.config.minimumIdle105测试环境可以再调小连接池参数里maximumPoolSize是最容易踩的坑。PG 默认max_connections通常 100如果一台机器上部署了多个 Nacos 节点每个节点连接池开 50三个节点直接挤爆 PG。建议先算好总预算节点数乘连接池上限再留 20% 余量给运维工具和手动查询。4.3 首次启动验证日志关键字与建表检查改完配置、丢好 jar第一次启动不要直接点控制台先盯日志# 前台启动直接看滚动日志 cd /opt/nacos/bin sh startup.sh -m standalone # 另开一个终端跟踪关键日志 tail -f /opt/nacos/logs/nacos.log日志里按顺序确认三个信号一是出现Nacos started successfully说明服务框架起来了二是看 SQL 初始化是否执行没有报postgresql-schema.sql找不到三是进 PG 里查表psql -h 127.0.0.1 -U nacos -d nacos -c \dt正常情况下能看到config_info、config_info_beta、config_info_tag、config_tags_relation、group_capacity、his_config_info、tenant_info、users、roles、permissions这些表。如果一张表都没有说明初始化 SQL 没执行服务可能还是起起来了但后续读写全挂在空表上控制台能开配的配置全不落盘。验证读写最直接的动作控制台新建一条配置再执行select * from config_info;能看到刚写入的行配置中心和注册中心的数据链路才算真正通了。5. Nacos 接 PostgreSQL 的常见坑与排查从启动失败到数据错乱5.1 启动报错Schema file not found 或者 postgresql-schema.sql 找不到现象日志里初始化数据源时抛Schema file not found或者提示找不到postgresql-schema.sql服务启动失败。原因Nacos 按spring.datasource.platform去类路径找同名 schema 脚本但插件 jar 里没放脚本spring.sql.init.schema-locations也没配置于是完全找不到初始化 SQL。解决把 schema 脚本放到 Nacos 的conf目录并在application.properties里显式指定spring.sql.init.schema-locationsfile:/opt/nacos/conf/postgresql-schema.sql。也可以把脚本打进 jar 的类路径根目录二选一即可不要两处都放不同的版本否则初始化行为不可预期。5.2 启动报错No DataSourceProvider found for platform postgresql现象日志明确说No DataSourceProvider found但 jar 确实已经丢进了plugins目录。原因大概率是META-INF/services里文件名写错或内容不是实现类的全限定名也有可能是 jar 没真正被 Nacos 加载比如放在plugins下嵌套子目录里或者压缩包解压后少了层目录。解决先确认 jar 内容unzip -l nacos-datasource-plugin-postgresql-1.0.0.jar | grep -i services检查是否包含META-INF/services/com.alibaba.nacos.plugin.datasource.provider.DataSourceProvider再用javap或反编译工具确认返回的平台名字是postgresql而不是PostgreSQL。警惕Nacos 的平台名匹配区分大小写返回值必须全小写。5.3 运行期 SQL 报错syntax error at or near LIMIT 或分页结果错乱现象服务能启动控制台也能打开但配置列表翻到第二页就报错或者列表接口报 PG 语法错误。原因Mapper 方言没适配。Nacos 内置 SQL 在 MySQL 下用的是LIMIT offset,sizePG 不支持这种写法必须写成LIMIT size OFFSET offset。只实现了 DataSourceProvider 没实现 MapperOverride等于三层只补了一层方言层必然翻车。解决插件里补PostgresqlMapper类覆盖涉及分页查询的方法把 SQL 改写成 PG 风格。参考第 3.2 小节里getMapperClass()的返回值确认它指向的 Mapper 类真正存在于插件 jar 中。如果用的是社区版本插件先确认它声明支持的 Nacos 版本范围2.2.0 的插件直接用在 2.5.x 上Mapper 方法签名对不上的情况很常见别硬上。5.4 数据时间差 8 小时last_modified 和 gmt_create 对不上现象控制台显示的修改时间和数据库里的时间不一致日志时间和业务时间错位。原因PG 的timestamp类型不带时区写入时用的是数据库会话时区Nacos 服务端和应用端默认走 JVM 时区连接参数里没显式指定TimeZone时驱动会取 JVM 系统时区一旦服务器时区是 UTC自然差 8 小时。解决连接串或数据源参数里固定TimeZoneAsia/Shanghai同时确认 PG 服务端timezone参数也设置了Asia/ShanghaiALTER DATABASE nacos SET timezone TO Asia/Shanghai;两边统一后重启 Nacos 再写一条配置验证。注意现有脏数据不会自动修正上线前用UPDATE校正一次。5.5 集群模式下连接数被打满checkpointer、vacuum 与 max_connections 的摩擦现象Nacos 集群部署后PG 日志大量出现sorry, too many clients already伴随checkpointer进程频繁唤醒整体吞吐下降。原因每个 Nacos 节点的连接池都按单机经验配置比如maximumPoolSize50三个节点就 150 个连接超过 PG 默认 100 的上限另外shared_buffers偏小时checkpoint 频繁刷盘加剧锁竞争和 I/O 延迟。解决连接池上限严格按「总连接数 节点数 × 单节点池大小 20 余量」反推。PG 侧同时调整max_connections200、shared_buffers1GB按内存 1/4 留余量并打开huge_pagestry减少 TLB 压力。这种调优不是玄学核心是让连接数和内存参数配套先调连接池再动 PG 参数每次只改一个变量。5.6 安全坑默认 JWT 密钥与未授权访问不能留给生产环境现象Nacos 控制台能直接被外部访问且社区通报过的默认密钥问题相关于 CNVD-2023-17316 的默认密钥身份认证绕过在未改配置的实例上可被利用namespaces接口存在未授权访问风险。原因Nacos 默认配置里nacos.core.auth.plugin.nacos.token.secret.key是内置值知道默认值就能伪造登录态同时 8848 端口如果对公网开放管理接口就暴露了。解决生产环境至少做三件事把nacos.core.auth.enabledtrue打开把nacos.core.auth.plugin.nacos.token.secret.key换成自己生成的 64 位以上随机字符串并且通过防火墙或安全组限制 8848/9848 端口只允许内网访问。PostgreSQL 账号也要单独建一个最小权限账号只授nacos库的增删改查不给superuser。6. 把插件推广到达梦与人大金仓SPI 机制的可迁移用法PostgreSQL 插件跑通后同一套 SPI 套路可以平移到达梦、人大金仓这类国产数据库。Nacos 2.5.x 就有人拿这个机制接达梦做法和我上面写的几乎一样换驱动类、换平台名、换 schema 脚本。唯一的额外工作是处理数据库方言达梦更接近 Oracle 语法金仓则高度兼容 PG所以金仓的 Mapper 适配成本最低。数据库spring.datasource.platformJDBC 驱动类schema 脚本命名PostgreSQLpostgresqlorg.postgresql.Driverpostgresql-schema.sql达梦damengdm.jdbc.driver.DmDriverdameng-schema.sql人大金仓kingbasecom.kingbase8.Driverkingbase-schema.sql迁移时最省力的做法是复制整个插件工程只改getDataSourceProviderName()返回值、驱动类、连接参数和 schema 脚本。验证技巧我一直用三步先在测试环境起一个独立 PG/金仓实例用最小配置跑通 Nacos 启动再通过控制台写入、修改、删除各一条配置对比config_info表数据变化最后模拟 50 个客户端同时拉配置观察连接池和 PG 慢查询日志。这个习惯帮我躲过好几轮升级翻车尤其是 Nacos 从 2.2.0 跳到 2.5.x 时插件接口有变化我会保留每个 Nacos 版本对应的插件源码分支和 schema 脚本副本升级前先编译再验证不直接上生产。希望帮到你。本文还有配套的精品资源点击获取
返回列表