
简介这是一套面向 Apollo 配置中心使用者的 PostgreSQL 适配源码包解决 Apollo 1.4.0 官方仅默认支持 MySQL、无法直接接入 PostgreSQL 的问题适合需要将配置中心数据源切换为 PostgreSQL 11.4 的开发和运维人员。包内含 Apollo 1.4.0 全量工程及手工改造后的源码与编译产物共 2000 个文件以 Java 源码和 class 类文件为主同时包含 64 个 SQL 脚本、properties/yml/yaml 等配置文件以及前端页面所需的 HTML/JS/CSS 资源和部署运维相关的 Dockerfile、shell 脚本等压缩包整体约 11.46MB目录结构接近官方工程布局便于定位修改点。目前已有 4184 人学习下载。借助该资源可快速获得 PostgreSQL 11.4 驱动 42.2.5 的适配参考包括数据源调整、依赖引入、SQL 兼容性处理等关键改动思路对搭建 Apollo 私有环境或二次开发具有直接借鉴价值。1. Apollo 1.4.0 只认 MySQL换 PostgreSQL 11.4 的第一步是改源码Apollo 1.4.0 是我在某次跨部门数据库标准化改造里啃下的硬骨头配置中心本身用起来顺手但官方默认只给 MySQL 数据源。某公司要把生产环境统一到 PostgreSQL 11.4我第一反应是直接把 Apollo 的连接串改成 PG 地址——启动日志里瞬间刷出一串 syntax error那一刻才意识到官方源码把存储路径写死在 MySQL 方言上了。这份源码包的价值就是省掉从零排查源码的时间它把 Apollo 1.4.0 的存储层完整适配到 PostgreSQL 11.4PostgreSQL Maven 驱动锁定 42.2.5。需要把 Apollo 存储切到 PostgreSQL 的团队、正在做数据库选型统一的组以及想搞懂配置中心存储适配逻辑的开发者都能从这份资源里找到能直接动手的路径。2. 摸清 Apollo 的存储栈先定位要动的模块和 SQL 边界2.1 数据流向与模块划分三个服务两套库改造范围先圈定Apollo 1.4.0 的体系里真正落地运行的其实由三个服务组成configservice 承担客户端配置拉取adminservice 负责配置管理端的操作请求portal 是控制台 UI。这里面有一个容易忽略的设计点——configservice 和 adminservice 共用同一个 ConfigDBportal 则单独连 PortalDB。这种双库设计直接决定了改造范围如果你只部署了 configservice 和 adminservicePortalDB 的改动可以先不做如果连 portal 一起用两套库的初始化脚本和连接配置都要同步适配。我在实际拆分时把数据流画成一条链客户端请求 configserviceconfigservice 从 ConfigDB 查配置发布操作走 adminservice管理页面走 portal。链条上的每个环节只要有一处用了 MySQL 专有语法换库后就会在日志里露出马脚。所以改造之前先把每个服务对应的库、表、Mybatis 映射文件列个清单比拿到源码就闷头改要省事得多。官方默认不做 PostgreSQL 适配有人猜测是 MySQL 生态在配置中心这个场景下覆盖了大多数需求。但从实现角度看更直接的原因是数据源层把方言写得太死源码里到处都是反引号包裹的列名、ifnull全局替换、LIMIT ?,?分页写法这些在 PostgreSQL 里全部是语法错误。好消息是 Apollo 的业务 SQL 量其实不大主要集中在配置项读写、审计日志、发布历史几张表上改造时不需要面对几千条 sql 文件。2.2 改造前要确认的三个边界目标版本、驱动版本、影响面动手前我习惯先确认三个参数缺一个后面都会反复翻车。第一是目标 PostgreSQL 版本这份资源的适配版本是 11.4对应 PostgreSQL 发布于 2019 年的稳定大版本。11.4 和 12、13、14 的 JDBC 协议兼容性都不错但如果是 9.6 或更老的库部分分区表和 JSONB 操作的行为有差异需要额外回归。第二是 JDBC 驱动版本资源里锁定的是 42.2.5这个版本对应 PostgreSQL 11.x 是官方推荐的稳定序列能正常支持currentSchema参数和 PG 11 的新协议特性不建议随手升到 42.7.x 或降到 42.2.1后者在部分 Linux 环境下会触发时区解析异常。第三是影响面也就是你的部署拓扑里到底有几个服务要连 PostgreSQL。用一张表记录会比较直观服务模块连接的库是否必须改说明apollo-configserviceConfigDB是客户端拉取配置全程走这里apollo-adminserviceConfigDB是配置发布、回滚依赖这个服务apollo-portalPortalDB视情况没有控制台需求可暂缓我一般会在动源码之前先在目标 PG 库上用 JDBC 驱动跑一个探针小程序只做SELECT 1和一次简单的建表删表确认网络连通、schema 权限、驱动版本都没问题再回来改代码。这个动作能把一半的玄学问题排除在源码改造之外否则你可能在 pom 里改了半天驱动最后发现是防火墙没放行。2.3 不改造直接切库的后果三类故障提前预演有人会问既然 Apollo 的 SQL 量不大那是不是改个连接串就能跑我把这个问题的答案拆成三类故障写在这里都是实际迁移时验证过的现象。第一类故障是驱动不匹配MySQL 驱动类com.mysql.jdbc.Driver遇到 PG 连接串直接抛No suitable driver found这不是配置能纠正的必须替换依赖。第二类故障是建表脚本挂掉Apollo 初始化时要用AUTO_INCREMENT、ENGINEInnoDB、DEFAULT CHARSETutf8mb4这类 MySQL 专属建表语法PG 一概不认。第三类故障最隐蔽——运行时 SQL 出错比如配置项分页查询用到LIMIT ?,?PG 要求写成LIMIT ? OFFSET ?只有在页面翻到第二页时才会暴露。这三类故障在源码改造时对应三个改动点依赖替换、方言适配、初始化脚本重写。下一章我会按这个顺序把每个改动点拆到具体文件和代码块直接照着抄就能少踩一半坑。3. 动手改源码驱动、方言、初始化脚本三处关键替换3.1 替换 PostgreSQL 驱动依赖版本锁定 42.2.5并清理传递依赖Apollo 1.4.0 是一个多模块 Maven 工程直接改根 pom 里的依赖声明会让所有子模块一起生效但要注意有些模块是通过apollo-core间接引入 MySQL 驱动的只改根 pom 还不够得把传递依赖也排除掉。常见做法是先看apollo-core/pom.xml里有没有显式声明mysql-connector-java有的话直接替换没有的话定位到apollo-build-tools或父 pom 里的 dependencyManagement把 MySQL 驱动挪走再把 PostgreSQL 驱动加进去。我这里给出一段实际改过的 pom 依赖片段适用于 configservice 和 adminservice 这两个必须连 ConfigDB 的模块dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId version42.2.5/version /dependency如果根 pom 里仍然存在 MySQL 驱动且无法直接删除就用exclusion在需要的依赖上把它剥离dependency groupIdcom.ctrip.framework.apollo/groupId artifactIdapollo-core/artifactId version1.4.0/version exclusions exclusion groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /exclusion /exclusions /dependency这段配置里的org.postgresql:postgresql:42.2.5是 PostgreSQL 官方 JDBC 驱动的标准坐标groupId 与 MySQL 的mysql完全不同所以两个驱动理论上可以共存但实际上我在迁移时会把 MySQL 驱动彻底排除掉避免 classpath 里同时存在两个驱动导致某些工具类自动探测时选错。排除依赖后需要在编译日志里确认没有mysql-connector-java的踪迹我一般会执行mvn dependency:tree验证。3.2 方言与 SQL 语句适配反引号、分页、函数替换一个都不能少驱动替换只是让连接能建立真正让 Apollo 跑在 PostgreSQL 上的是 Mybatis 映射文件里的 SQL 语句改造。Apollo 1.4.0 的 mapper XML 存在各个服务模块的resources/mapper目录下改造点集中在三类语法的替换。第一类是列名包裹符。MySQL 用反引号包裹的列名在 PG 里会直接报语法错误需要改成 PG 的双引号并且大小写必须和建表时完全一致。举一个实际改过的分页查询例子-- MySQL 原始写法 SELECT NamespaceId, Key, Value FROM Property ORDER BY Id DESC LIMIT ?, ? -- PostgreSQL 适配写法 SELECT NamespaceId, Key, Value FROM Property ORDER BY Id DESC LIMIT ? OFFSET ?这段 SQL 里的LIMIT ? OFFSET ?是 PG 的分页标准写法LIMIT控制返回行数OFFSET控制跳过行数。Apollo 原来的LIMIT ?,?在 PG 里会被解析成两个独立参数但语义不匹配轻则查询结果错乱重则直接抛syntax error at or near ,。我改造时会把所有 mapper 文件里的LIMIT ?,?全局搜索出来统一替换成LIMIT ? OFFSET ?。第二类是函数替换。MySQL 的ifnull在 PG 里对应coalescedate_format对应to_charnow()两边的行为一致但 PG 建议写CURRENT_TIMESTAMP。改的时候不仅要改 mapper XML还要扫描 Java 代码里有没有拼接原生 SQL 的地方Apollo 1.4.0 的某些审计查询是直接用字符串拼接的这类代码最容易漏掉。我在改造时用一个简单的正则去扫整个源码目录ifnull|date_format|DEFAULT CHARSET|AUTO_INCREMENT把每一处命中都列出来逐个确认。第三类是自增主键和布尔类型。MySQL 建表时最常见的id INT NOT NULL AUTO_INCREMENT在 PG 里要改成id SERIAL PRIMARY KEY或者用更规范的id INT GENERATED BY DEFAULT AS IDENTITY。布尔字段方面MySQL 的tinyint(1)在 PG 里对应boolean类型但 Mybatis 的jdbcType映射要跟着调比如原来的jdbcTypeTINYINT在 PG 下可能拿不到值需要改成jdbcTypeBOOLEAN或直接去掉显式类型让驱动自动判断。3.3 初始化脚本改造建表语句和基础数据一次性迁移Apollo 1.4.0 默认的初始化脚本在scripts/mysql目录下整个目录要对应迁移成 PG 版本。我实际做的时候没有选择用工具自动转换因为那种转换经常把索引名、约束名生成得特别怪异不如手工过一遍稳妥。每个表的建表语句都要调整典型的改动点包括去ENGINEInnoDB、去DEFAULT CHARSETutf8mb4、AUTO_INCREMENT改SERIAL、datetime改timestamp。这里给一段改造前后的建表对比-- MySQL 原版建表语法片段 CREATE TABLE App ( Id INT NOT NULL AUTO_INCREMENT, Name VARCHAR(64) NOT NULL, CreateTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (Id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- PostgreSQL 适配后的建表语法片段 CREATE TABLE App ( Id SERIAL PRIMARY KEY, Name VARCHAR(64) NOT NULL, CreateTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP );初始化脚本里除了建表还有一批基础数据比如默认的权限数据、系统配置项。这些数据插入语句通常不涉及方言差异直接保留即可但要注意 PG 在事务外做批量插入时每个语句是隐式事务中途失败不会整体回滚所以执行前最好先建一个测试库跑一遍完整脚本确认无报错再往真实库上执行。我在迁移时是把建表脚本和种子数据拆成两个文件建表脚本执行完校验行数数据脚本再分批导入。文件清单里还有一个README.md.bak说明原始维护者把官方 README 备份了一份再改写的我自己也习惯这么做——保留原始文档作为对照方便回溯哪些说明文件被改过。至于build.bat是 Windows 环境的构建脚本里面封装的还是 Maven 命令在下一章编译时会具体讲。4. 编译、打包与部署把改造后的代码跑起来4.1 编译顺序与常见报错先跳过测试再逐个模块回归Apollo 1.4.0 作为多模块工程直接执行根目录的build.bat会触发全量编译打包但在 Windows 环境下要先确认JAVA_HOME和MAVEN_HOME都配置好。我更推荐在命令行手动走一遍 Maven 命令这样报错信息更直观mvn clean package -DskipTests -pl apollo-configservice,apollo-adminservice,apollo-portal -am这条命令里的-DskipTests是关键Apollo 源码里带了一批集成测试类比如文件清单中出现的NotificationControllerV2IntegrationTest.class、ConfigControllerTest.class它们默认用 H2 内存数据库模拟 MySQL 行为。一旦依赖换成了 PostgreSQL 驱动这些测试类的内存数据库配置会与驱动不匹配直接挂掉导致编译在测试阶段中断。我第一次编译时没加-DskipTests就被这个原因卡了十几分钟日志里全是 H2 初始化失败的堆栈。-pl指定要打包的模块列表-am表示同时构建这些模块依赖的其他模块。如果不确定哪些模块受数据库影响可以先把这三个核心服务都打上一次拿到全部 jar。编译通过后产物分别是apollo-configservice-1.4.0.jar、apollo-adminservice-1.4.0.jar和apollo-portal-1.4.0.jar对应的部署方式是把它们放进各自的发布目录。编译期间还容易出现两类报错。一类是 Mybatis 的 XML 校验失败提示某个 mapper 文件里有非法 SQL 片段这多半是反引号或者LIMIT ?,?漏改按错误信息定位到具体 mapper 文件重新替换即可。另一类是 Java 代码里用到了 MySQL 特有的com.mysql.jdbc.Driver字符串常量比如某些配置文件里硬编码了驱动类名需要全局搜索com.mysql关键字把配置值改成org.postgresql.Driver。4.2 部署时的数据库连接串与驱动类名配置编译产物只是把源码变成可执行 jar真正的运行配置在发布包的配置目录里。打开configservice和adminservice的application.properties重点改数据源这几行spring.datasource.urljdbc:postgresql://localhost:5432/ApolloConfigDB?currentSchemapublic spring.datasource.usernameapollo spring.datasource.passwordapollo spring.datasource.driver-class-nameorg.postgresql.Driver spring.datasource.tomcat.validation-querySELECT 1这里的currentSchemapublic是 PG JDBC 连接串里值得重视的参数它会告诉驱动默认搜索路径是哪个 schema。Apollo 的建表脚本如果不指定 schema默认建在 public 下这个参数加了基本不会出问题如果建在自定义 schema 里参数值要改成对应的 schema 名。driver-class-name必须写成org.postgresql.Driver和 pom 里引入的驱动一致。validation-querySELECT 1是给连接池做存活探测用的。MySQL 驱动在 Tomcat JDBC 连接池里默认的验证方式不一定适合 PG不配这句连接池空闲一段时间后可能误判连接失效出现偶发性的Connection is closed异常。我给计划任务里的其他服务配置 PG 连接串时都会顺手带上这一项算是数据库迁移到 PG 后的固定习惯。portal 服务单独连 PortalDB连接串写法完全一致只是库名从ApolloConfigDB换成ApolloPortalDB。如果在部署拓扑里只用了 configservice 和 adminserviceportal 的改造可以暂时不碰。另外要提醒一点Apollo 在启动时会对数据库做一次版本检查如果用的是旧库可能会提示 schema 版本不匹配这时需要把改造后的初始化脚本执行到对应库上再重启服务。5. PostgreSQL 适配避坑五条血泪记录5.1 反引号引发的启动崩溃现象配置好连接串后启动 configservice日志里反复出现PSQLException: ERROR: syntax error at or near active堆栈指向 Mybatis 初始化阶段。原因Apollo 的 mapper XML 里列名和表名大量使用了 MySQL 的反引号包裹比如App里的在 PG 语法里不是合法标识符驱动会把整个 SQL 当作字符串解析然后直接拒掉。解决全局搜索所有 mapper 文件里的反引号逐一替换成 PG 兼容的双引号并且保证大小写与建表语句一致。用 IDEA 的正则替换\(\w)改成$1 能快速完成大半剩下手工检查 Java 代码里拼接的 SQL。5.2 集成测试类在编译期挂掉现象执行mvn clean package不带-DskipTests构建在 test 阶段失败堆栈提示 H2 初始化或方言不兼容。原因Apollo 1.4.0 的集成测试类如NotificationControllerV2IntegrationTest默认加载 H2 内存数据库并模拟 MySQL 方言改了 PostgreSQL 驱动之后测试配置里的MODEMySQL与驱动行为冲突。解决编译打包阶段用-DskipTests跳过全部测试先保证产物能出。后续想跑测试可以在测试 resources 里把 H2 的连接模式改成MODEPostgreSQL但这需要逐个确认测试用例的 SQL 兼容性成本不低我当时的取舍是先用-DskipTests保交付。5.3 PG 大小写敏感导致查询无数据现象服务启动正常但控制台打开 App 列表是空的数据库里明明有数据。原因PG 对双引号包裹的标识符严格区分大小写未加引号时又会被折叠成小写。改造后的 SQL 里如果写了App而建表时用了app查询直接找不到表反过来写错了也会查不到数据。解决统一规范建表脚本和 mapper SQL 里的表名、列名都写成 PascalCase 并用双引号包裹保证两边一致。我后来写了一个小脚本把建表脚本里所有标识符提取出来再拿 mapper XML 里的标识符做差异比对能过滤掉 90% 的大小写坑。5.4 分页第二页翻车现象配置项列表第一页正常点击第二页或排序字段时页面报错日志提示LIMIT ? OFFSET ?参数占位符数量不对。原因Apollo 的分页参数在 MySQL 语法下是LIMIT offset, limit改造模糊时只改了部分 SQL另一处还是LIMIT ?,?PG 驱动把两个参数按limit和offset的顺序解析语义完全错位。解决把整个源码目录里所有LIMIT语句搜出来一句一句改成LIMIT ? OFFSET ?参数顺序对应为每页条数在前、偏移量在后。这个坑反复出现的最主要原因是有几处 SQL 是写在 Java 注解里的不在 mapper XML 中全局搜索时容易被漏掉。5.5 连接池空闲后偶发性断连现象服务运行一天后某次配置发布操作偶发报The connection attempt failed重启后恢复过一段时间又复现。原因Tomcat JDBC 连接池默认的验证查询不适用于 PG空闲连接被数据库端断开后连接池还持有旧连接下一次请求时才发现失效。解决在数据源配置里加上spring.datasource.tomcat.validation-querySELECT 1并设置合适的validation-interval让连接池定期探活。这个坑在 MySQL 下不明显因为 MySQL 驱动程序内部有更主动的失效检测机制换成 PG 后探活配置就是必需品而不是选配。6. 验证闭环从配置发布到客户端拉取的完整检查改完源码、部署完服务之后最重要的一件事是验证配置发布到客户端拉取的整条链路真的走通了。我的习惯是先在 PostgreSQL 里手动查一张表确认服务启动时没有把库表弄坏然后走一次真实的配置发布流程。具体做法是打开 Apollo 控制台在某个 App 下新增一个测试配置项比如把timeout设为3000点发布。过几秒后去 ConfigDB 的ReleaseMessage表查最新的消息记录SELECT Id, Message, DataChange_CreatedTime FROM ReleaseMessage ORDER BY Id DESC LIMIT 5;如果这一条查询能拿到刚发布的配置消息说明 adminservice 的写入链路已经正常。然后我在客户端代码里跑一个最简单的拉取逻辑用 Apollo 提供的ConfigService.getConfig读取刚才发布的 key打印出值和时间戳Config config ConfigService.getConfig(yourNamespace); String value config.getProperty(timeout, default); System.out.println(timeout value);这个步骤能验证两件事第一configservice 能从 PostgreSQL 里正确读出配置第二发布后的新值确实覆盖了客户端本地缓存。我在第一次适配后跑这个验证时发现 ReleaseMessage 表有数据但客户端拿到的还是旧值排查后确认是客户端的本地缓存默认只保留 60 秒把apollo.config-cache-duration调成 5 秒做测试才看到新值。从那以后我每次接到数据库迁移需求都强制自己先跑一遍探针连通性确认库本身没问题再动方言适配最后才编译部署拉通验证这个顺序帮我挡掉了不少启动阶段才暴露的坑。希望这次的适配思路和踩坑记录能让你的 PostgreSQL 迁移少走几段弯路。本文还有配套的精品资源点击获取