Nacos 3.x 最让我兴奋的变化,不是界面翻新,也不是那套新的控制台,而是它终于把数据库层做成了可插拔的架构。这意味着 Nacos 不再被 MySQL 和内置 Derby 锁死,我们可以通过插件直接把 PostgreSQL 接进来,而且这套玩法在集群模式下同样成立。我第一时间把测试环境从 MySQL 迁到了 PostgreSQL,顺手搭了一个三节点的 Nacos 集群,整个过程踩了不少坑,也摸清了很多细节。这篇就是我的实战记录,适合正在评估 Nacos 3.x、想用 PostgreSQL 替换存储层、或者准备上生产集群的同学参考。
1. 项目背景与设计思路
1.1 为什么 Nacos 需要数据库插件化
Nacos 从 1.x 到 2.x,存储层其实一直很“专一”,要么是内嵌的 Derby,要么是 MySQL。Derby 只适合单机体验,集群模式下几乎没法用;MySQL 虽然稳定,但很多公司内部已经有成熟的 PostgreSQL 体系,DBA 团队对 PostgreSQL 的运维经验更丰富,新建一套 MySQL 实例有时候比迁移业务还麻烦。所以社区里一直有人喊“什么时候支持 PostgreSQL”,而 Nacos 3.x 的插件化数据源设计,本质上就是把“我用什么数据库”这个决定权从框架手里交还给使用者。
从这个角度来看,插件化不是简单加一个驱动那么简单。Nacos 内部的配置存储、服务注册、健康检查、集群节点状态,全都依赖数据库表结构。不同的数据库在 SQL 语法、事务隔离级别、主键生成策略、批量更新能力上都有差异,如果仅仅靠 JDBC 驱动替换,SQL 很快就跑挂了。所以 Nacos 3.x 的做法是定义一套统一的数据源抽象层,把不同数据库的实现拆成独立的插件包,插件里不仅要写 SQL,还要处理方言差异、分页逻辑、甚至某些特殊的类型映射。
1.2 插件集成的整体选型逻辑
我最初其实犹豫过要不要直接上 Nacos 3.x,因为 3.x 毕竟比 2.x 年轻,稳定性还需要验证。但后来我算了一笔账:如果现在按 2.x 的方式用 MySQL 部署,将来迁移到 3.x 还是要折腾一遍;如果直接基于 3.x 的插件机制接 PostgreSQL,虽然初期要付出学习成本,但后续的数据库选型自由度就大了很多。而且 Nacos 3.x 的插件接口设计得比较干净,接入一个新数据库只需要实现几个核心接口,不需要改 Nacos 主流程的代码,这点对我来说非常有吸引力。
选 PostgreSQL 也是有原因的。一方面我们的业务库绝大部分都在 PostgreSQL 上,统一技术栈能减少运维压力;另一方面 PostgreSQL 在高并发下的 MVCC 控制、JSONB 能力、以及强大的索引策略,对我们后续做配置数据的复杂查询会有帮助。Nacos 集群模式下,所有节点共享同一个数据库,PostgreSQL 的主从复制和故障切换能力也相对成熟,这让集群的数据库侧有了更多保障。
1.3 理解 Nacos 集群的存储协作模式
在正式开始操作之前,我觉得有必要先理清 Nacos 集群和数据库之间的关系。Nacos 集群里的每个节点都是一个独立的 Nacos 服务进程,它们共同对外提供注册中心和配置中心的能力。节点之间需要协调数据,而这个协调动作在 Nacos 2.x/3.x 中主要依赖两个层面:一是节点之间通过 gRPC 通信做数据同步,二是所有节点的数据最终都会落到同一个共享数据库里。
这里有个容易混淆的点:Nacos 的配置数据和服务实例数据,最终是持久化到数据库的,但节点之间的实时状态同步并不完全依赖数据库。也就是说,数据库更像是一个“最终一致性”的存储底座,节点间的 Raft 协议负责选主和日志复制。当你启用外部数据库时,数据库里存的是配置历史、服务实例的持久化快照、以及一些元数据。所以,数据库的稳定性直接决定了整个集群的可靠性——数据库挂了,即使节点进程还活着,也无法正常读写数据。
2. 插件集成机制深度拆解
2.1 Nacos 3.x 数据源插件的工作方式
Nacos 3.x 的数据源插件基于 Java 的 SPI 机制实现。SPI 听上去很抽象,其实你可以把它理解成一个“接口登记处”:Nacos 主程序只依赖接口定义,比如DatasourcePlugin、DatabaseDialect这些抽象类,但至于用什么数据库、SQL 怎么写,主程序一概不管。当你把某个数据库插件的 jar 包放到 Nacos 的插件目录里,插件会在启动时通过 SPI 机制自动被加载进来。
这样一来,主程序和数据库实现就被彻底解耦了。比如我要接 PostgreSQL,只需要下载一个nacos-datasource-plugin-postgresql插件包,放到plugins目录,然后在配置里指定数据库类型为postgresql。Nacos 启动的时候会扫描插件目录,找到对应实现并初始化连接池。这个过程有点像手机装 SIM 卡——接口标准是固化的,但卡是哪个运营商的,换个卡就行。
2.2 核心接口和关键抽象
如果你以后想自己写一个扩展数据库的插件,或者想定制 PostgreSQL 插件的某些行为,有几个核心接口必须理解。
第一个是DatasourceCreator,它负责创建真实的数据源对象。这个接口会读取配置项,比如连接地址、用户名、密码、连接池参数,最终返回一个DataSource实例。Nacos 内部默认用的是 HikariCP 连接池,所以在 PostgreSQL 插件里你看到的还是 HikariCP 配置,只是 JDBC URL 换成了 PostgreSQL 的格式。
第二个是DatabaseDialect,这是最核心的抽象。它定义了一个数据库方言应该具备的所有能力,比如分页查询怎么写、主键冲突怎么处理、批量插入的语法是什么。我在实际看源码的时候发现,这个接口里包含几十个方法,可见不同数据库之间的差异比想象中要大得多。PostgreSQL 插件能跑起来,全靠这个实现类里一条一条把 SQL 语句翻译成了 PostgreSQL 方言。
第三个是TableInitializer,它在数据库里建表。Nacos 启动时会检查表是否存在,不存在就执行建表脚本。因为不同数据库的建表语法不同,所以每个插件都需要带上自己的建表 DDL。PostgreSQL 的插件包里就有一份完整的nacos-postgresql.sql脚本,里面有官方定义好的所有表结构。
2.3 连接池与事务语义的变化
切换数据库后,受影响的不只是 SQL 语法,还有事务行为和连接管理。MySQL 默认的 REPEATABLE READ 和 PostgreSQL 的默认隔离级别虽然相同,但在具体实现上,PostgreSQL 的快照机制、锁等待策略跟 MySQL 有明显差异。Nacos 的配置发布动作涉及事务操作:先写配置主表,再写历史表,最后更新哈希表。如果事务隔离级别处理不当,可能出现配置读取到旧版本的问题。
我建议大家在配置 PostgreSQL 连接的时候,把transaction-isolation明确设置为READ_COMMITTED。Nacos 的业务场景本身并不需要高隔离级别,READ_COMMITTED既能保证数据一致性,又能减少锁冲突。另外,连接池的连接超时时间要根据集群的负载适当调整。我在测试中发现,如果某个节点突然宕机,连接池里的空闲连接可能不会立刻被清理,新节点顶上来时如果连接池配置太紧,就可能出现连接获取超时。
3. 集群部署实操:PostgreSQL 版 Nacos 3.x
3.1 环境准备与版本匹配
先说我用的环境组合:操作系统是 CentOS 7.9,PostgreSQL 版本是 14.x,JDK 版本是 17,Nacos 3.x 我用的是社区当前最新的稳定预览版。这里有个非常重要的提醒:Nacos 3.x 对 JDK 版本有要求,官方推荐 JDK 17 以上,如果你还在用 JDK 8,建议先升级,否则可能会出现编译问题或者 gRPC 通信异常。
另外,安装 Nacos 之前,先把 PostgreSQL 准备好。我建议至少准备一个具有创建表权限的专用账号,不要用超级管理员跑业务账号。原因很简单:Nacos 启动时会执行建表、插入、更新等一系列操作,如果权限过大,万一某个安全漏洞被利用,整个数据库都暴露了。我在测试环境里特意建了一个nacos用户:
CREATE USER nacos WITH PASSWORD 'your_strong_password'; CREATE DATABASE nacos_config OWNER nacos; GRANT ALL PRIVILEGES ON DATABASE nacos_config TO nacos;3.2 插件包的获取与部署
Nacos 3.x 的插件包目前可以通过两个途径获得。一是直接下载官方或社区发布的预构建 jar 包,二是自己从源码编译。我推荐优先用预构建包,因为自己编译容易踩坑,特别是 maven 依赖版本和 JDK 版本不匹配的时候。下载好插件包之后,把它解压或者直接放到 Nacos 安装目录的plugins子目录下。要确认你的目录结构类似这样:
nacos/ ├── bin/ ├── conf/ ├── plugins/ │ └── nacos-datasource-plugin-postgresql-3.x.x.jar ├── target/这里有个细节:有些版本的插件包是 zip 格式,里面除了 jar 还有依赖的驱动包。如果是 zip,记得解压,把里面的所有 jar 都放到plugins目录。我一开始偷懒直接把 zip 扔进去,结果启动时提示找不到 PostgreSQL 驱动类。这个坑踩得有点冤枉,排错也花了不少时间。
3.3 配置文件的关键调整
Nacos 3.x 的配置还是集中在conf/application.properties。和 2.x 相比,数据库相关的配置有了一些变化,但本质上还是那几个核心参数。我贴一下我在三节点集群上用的配置片段:
### 数据源类型,支持 mysql、postgresql spring.datasource.platform=postgresql ### 数据库连接信息 db.num=1 db.url.0=jdbc:postgresql://192.168.1.10:5432/nacos_config?tcpKeepAlive=true&reWriteBatchedInserts=true db.user.0=nacos db.password.0=your_strong_password ### 连接池调优参数 db.pool.config.connectionTimeout=30000 db.pool.config.maximumPoolSize=50 db.pool.config.minimumIdle=10 db.pool.config.idleTimeout=600000这里有几个参数值得单独解释。db.url.0里的reWriteBatchedInserts=true是 PostgreSQL JDBC 驱动特有的参数,它可以优化批量插入性能。Nacos 在服务注册和配置发布时会有批量写操作,开启这个参数之后,驱动会把多条 INSERT 语句重写成带多值的单条语句,网络交互次数大幅减少,性能提升非常明显。我在测试中看到,注册 1000 个服务实例的耗时从 3 秒降到了 1 秒左右,效果立竿见影。
另一个要注意的是tcpKeepAlive=true。集群环境下,Nacos 节点和 PostgreSQL 之间的连接可能会因为网络设备空闲超时被断开,TCP 层的心跳机制可以尽量保持连接不被物理链路回收。如果是跨机房部署,网络稳定性一般,这个参数尤其重要。
3.4 启动集群节点与验证注册
配置好三台机器的application.properties之后,就可以启动集群了。Nacos 3.x 的集群模式不是通过配置文件里的 cluster.conf 来指定节点列表吗?其实在 3.x 中,节点发现方式已经发生了比较大的变化,它更多依赖 Raft 协议内部协商,而不是传统的主从写死。如果你是在同一个网段,一般只需要保证所有节点的配置里指向同一个数据库,它们就能自动组成集群。我习惯在启动时加上-Dnacos.standalone=false参数,确保进入集群模式。
启动很简单,进入 Nacos 的 bin 目录:
sh startup.sh -m cluster启动之后,最重要的就是看日志。日志文件在logs/nacos.log和logs/nacos-cluster.log。正常情况下,你会在日志里看到类似“Nacos started successfully in cluster mode”的提示。接下来,我通常会做两件事验证集群是否真的工作了:
第一,访问任意一个节点的控制台,用nacos/nacos登录,确认服务列表为空、配置列表为空,说明数据库连接正常、表已经建好。第二,在其中一个节点上注册一个测试服务,然后去另一个节点的控制台查看,如果也能看到同一份服务列表,说明节点间数据同步成功。
这里再补充一个细节:Nacos 3.x 的节点角色是动态选举出来的。你可以通过执行curl -X GET "http://127.0.0.1:8848/nacos/v1/console/health/liveness"来看节点的健康状态,不过更重要的是查看 Raft 日志,确认哪个节点成为了 Leader。Leader 节点负责处理写入请求,如果 Leader 挂了,集群会自动选举新节点,数据库此时扮演的是持久化存储的角色,不会参与选举过程。
3.5 初始化脚本的自动与手动执行
Nacos 3.x 在启动时会自动执行建表脚本,这点和 2.x 的方式不太一样。2.x 时期你需要手动执行 MySQL 的 SQL 脚本,3.x 则把初始化逻辑内置到了插件里。所以理论上,只要你给的账号有建表权限,启动后就能看到一串nacos_config数据库里自动生成的表。
不过我还是建议手动执行一遍官方脚本,原因很简单:自动建表时如果中途报错,比如某个字段类型不兼容,后面就很难定位是哪个表出问题。手动执行时,你可以逐条看报错信息。PostgreSQL 的表名默认是小写的,而 Nacos 源码里的表名多是驼峰命名,如果你看到类似relation "configInfo" already exists的报错,通常是因为脚本重复执行了。这个不影响正常使用,但如果是初次部署出现这样的报错,就要检查是不是数据库账号指向了错误的 schema。
4. 常见问题与排查技巧实录
4.1 节点启动成功但控制台无法登录
这个问题我在第一次部署时踩过,表现得很诡异:Nacos 进程起来了,日志显示成功,但控制台登录一直转圈,最后报“用户名密码错误”。排查下来发现,是数据库里的user表没有被正确初始化。因为user表里需要写入默认的 admin 账号信息,如果建表脚本只执行了部分,账号就不存在,自然登录不了。
解决办法很简单:手动执行插件包里的完整 SQL 脚本,然后重启 Nacos。如果中途还有报错,重点看是否存在外键依赖问题。PostgreSQL 的建表顺序会影响外键创建,如果你看到foreign key constraint相关的错误,说明某个依赖表还没有创建,按脚本顺序重新执行一遍就能解决。
4.2 gRPC 端口冲突导致集群节点无法互连
Nacos 每个节点默认会占用 8848(HTTP)和 9848(gRPC)两个端口。如果你在同一台机器上测试多节点集群,或者节点之间防火墙没有开放 gRPC 端口,就会出现一个奇怪的现象:每个节点都能独立启动,但永远无法形成有效的集群,日志里反复出现连接失败的告警。
我当时的排查思路是先看端口监听状态:
netstat -tunlp | grep 9848如果发现 gRPC 端口没有监听,说明启动参数配置有问题,比如端口被占用导致 gRPC 服务器没起来。如果端口正常,那就检查防火墙和安全组,比如:
firewall-cmd --list-all另外,Nacos 2.x 之后,节点之间的内部通信走的是 9848 这个 gRPC 端口,客户端 SDK 连接使用的也是 9848。所以不管是集群内部还是客户端访问,都要确保这个端口在防火墙上放行。
4.3 数据库连接池连接泄漏
PostgreSQL 插件环境下,有一个问题和 MySQL 环境下不太一样:长时间运行后,日志里出现Connection is not available, request timed out after 30000ms这类报错。这通常是连接池被耗尽导致的。Nacos 默认的maximumPoolSize是 50,但如果某个节点发生慢查询,或者数据库端触发了大量锁等待,连接会被长时间占用,最终耗尽连接池。
我的建议是先在 PostgreSQL 里查一下活跃连接数:
SELECT pid, usename, application_name, client_addr, state FROM pg_stat_activity WHERE datname = 'nacos_config';如果看到大量state为active的连接长期不变,就要检查是否出现了锁阻塞。Nacos 的配置发布是事务性操作,如果某个事务长时间未提交,后续的所有写操作都会被阻塞。这时候可以用pg_locks视图来定位锁冲突。从经验来看,最直接的办法是适当调大connectionTimeout,同时减小minimumIdle到 5 左右,避免空闲连接占用资源。
4.4 数据不一致的定位思路
集群模式下,偶尔会遇到某个节点的服务列表比另一个节点多或少的情况。首先不要慌,这不一定代表故障。Nacos 节点之间的数据同步是异步的,短时间内从不同节点查询到不同的视图是正常的。如果你持续观察几分钟,数据最终会收敛一致。但如果一直不一致,大概率是 Raft 日志同步出问题了。
这时候需要看logs/nacos-cluster.log里的 Raft 相关日志,确认当前 Leader 节点是谁,以及 Follower 节点是否正常接收日志。如果 Follower 的日志落后太多,可以适当调大 gRPC 的报文大小限制,有时候批量大的配置发布会产生较大的 Raft 日志条目,默认配置可能不够用。
我踩过的一个比较隐蔽的坑是,集群节点的时间没有同步。Nacos 的 Raft 实现依赖时间戳来判断日志的新旧,如果某个节点的时间比别的节点快了几秒,会被误判为 Leader 日志更旧,导致选举结果异常。所以集群部署前,务必把 NTP 时间同步做好。
4.5 快速问题排查速查表
为了方便你现场排查,我把几个高频问题整理成了表格,对照着处理会快很多。
| 现象 | 可能原因 | 排查命令/方案 |
|---|---|---|
| 节点启动失败,提示找不到驱动 | 插件包未解压或未放入 plugins 目录 | 检查 plugins 目录下是否有完整 jar 包 |
| 控制台登录失败 | user 表未初始化或账号缺失 | 手动执行 SQL 脚本并重启 |
| 节点间无法组成集群 | gRPC 端口未放行或端口被占用 | netstat、防火墙检查 |
| 连接池超时 | 数据库锁等待或连接池太小 | pg_stat_activity、调大连接池参数 |
| 数据长时间不一致 | Raft 日志同步异常或时钟漂移 | 检查 cluster 日志、同步 NTP |
| 配置页面显示空白 | 数据库连接配置错误 | 查看nacos.log中的 SQL 异常 |
4.6 部署前值得提前做的小优化
最后说三个我自己觉得特别值得提前做的事。第一个是提前把 PostgreSQL 的max_connections调大。Nacos 集群里每个节点会建立连接池,按 50 个连接来算,三节点就是 150 个连接,PostgreSQL 默认的 100 上限根本不够。第二个是开启statement_timeout,比如设成 30 秒,防止某些异常 SQL 卡住整个连接池。第三个是定期备份数据库,Nacos 的配置数据是核心资产,不能只依赖 Nacos 自带的持久化机制,外部数据库本就应该承担备份职责。
安装时把这些参数写进postgresql.conf:
max_connections = 300 statement_timeout = 30000改完之后记得重启 PostgreSQL。如果不方便重启,也可以动态调整:
ALTER SYSTEM SET max_connections = 300; SELECT pg_reload_conf();max_connections需要重启才能生效,这一点要留意。把这些基础工作做扎实,Nacos 3.x 加 PostgreSQL 的这套组合才能真正稳定跑起来。
我个人实际操作中的体会是,Nacos 3.x 的插件化设计让数据库选型回归了“业务需求驱动”的本质。不要因为某个数据库是默认选项就一直用下去,也不要因为追求新特性就盲目替换。PostgreSQL 和 Nacos 的结合,在配置中心这种读多写少的场景下表现足够出色,加上插件机制带来的灵活度,是一个值得长期投入的方向。最后再分享一个小技巧:在升级 Nacos 集群之前,先在测试环境用 PostgreSQL 跑一周的模拟流量,重点观察连接数曲线和慢查询日志,很多隐藏问题只有长时间运行才会暴露。