2026/10/22 8:11:56

Apollo 1.4.0配置中心部署实战:三服务架构、客户端接入与热更新指南

Apollo 1.4.0配置中心部署实战:三服务架构、客户端接入与热更新指南 简介面向需要为 Apollo 配置中心接入 PostgreSQL 数据源的开发者apollo-1.4.0.zip 是基于 Apollo 1.4.0 源码改造后的完整工程核心解决官方仅支持 MySQL、无法直接使用 PostgreSQL 11.4 的问题。作者已完成源码级适配将 PostgreSQL 的 Maven 依赖调整为 42.2.5并同步修改数据访问层、连接配置及相关脚本包含驱动更换、方言适配、初始化表结构等关键改动便于读者快速理解数据源迁移的取舍和排错思路。包内共 2000 个文件、约 11.46MB以 Java 源码与编译后的 class 文件为主同时包含 SQL、properties、xml、yml、yaml 等数据库脚本和运行配置以及 png、js、html 等前端展示资源既能直接导入工程阅读改造细节也便于部署运行或二次修改。目前已有 4184 人学习下载适合具备 Apollo 或 Spring Cloud 基础、需要将配置中心数据源扩展为 PostgreSQL 的工程师参考资源整体目录结构清晰可配合 SQL 与配置快速定位改动位置。1. apollo-1.4.0.zip 不是源码压缩包那么简单拿到一个命名规整、但看不出内容的 zip第一反应应该是确认它是什么而不是急着解压。apollo-1.4.0.zip 这个文件名指向的是配置中心项目的一个发布版本包。这个项目解决的是微服务环境下配置散落、修改靠发布、回滚靠记忆的问题把配置集中管理支持实时推送、环境隔离、灰度发布。如果你所在团队正在为配置管理头疼或者刚接手一个用了这个配置中心的旧系统这个包值得你花半小时搞清楚它。适合读这篇文章的人是准备部署或已经部署但遇到疑难杂症的开发者不需要预先了解配置中心的内部实现但最好有基本的 Spring Boot 和 MySQL 使用经验。2. 先把 1.4.0 拆开看三个服务、两个库和一次能成功的启动2.1 为什么配置中心要拆成三个服务从 1.4.0 开始标准部署形态就是三个服务ConfigService、AdminService、Portal。很多第一次接触的人会问搞一个服务把所有事做了不行吗答案是职责不同流量特征不同拆开才能各自扩容。ConfigService 面向客户端负责配置读取是客户端长轮询的连接入口承担所有业务实例的请求流量。AdminService 面向管理端和 Portal负责配置的写入、发布、回滚等管理操作。Portal 则是给开发和运维用的 Web 控制台不直接访问数据库也不直接操作配置只调用 AdminService。这个拆分带来的一个直接好处配置读取和管理操作的扩容策略可以完全独立。比如线上读配置的实例有几百个而管理操作可能就是几个人在点鼠标这时候只需要扩 ConfigService。理解这一点后面部署时就不会出现只部署一个 Portal 就以为能用的误会。除了三个服务还有两个数据库ApolloConfigDB 和 ApolloPortalDB。前者存的是环境相关的配置数据后者存的是 Portal 的账号、权限、审计日志。1.4.0 中一个 Portal 可以管理多套环境每套环境对应一套 ConfigService 和 AdminService以及一个独立的 ApolloConfigDB。2.2 解压建库先让 MySQL 把两张表结构认下来先假设你已经拿到了 apollo-1.4.0.zip 这个发布包。解压后里面一般会包含 scripts 目录数据库初始化脚本就放在 scripts/sql 下。先别急着启动服务数据库是第一步。我用 MySQL 5.7 为例5.7 是 1.4.0 那批项目比较稳妥的选择。先建库再导入表结构和初始数据# 解压发布包注意保持目录结构 unzip apollo-1.4.0.zip -d apollo-1.4.0 cd apollo-1.4.0/scripts/sql # 创建两个库注意使用 utf8mb4避免配置里存 emoji 或者特殊符号时报错 mysql -uroot -p -e CREATE DATABASE ApolloConfigDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p -e CREATE DATABASE ApolloPortalDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 导入表结构两个 sql 文件对应两个库 mysql -uroot -p ApolloConfigDB apolloconfigdb.sql mysql -uroot -p ApolloPortalDB apolloportaldb.sql # 验证表是否导入完整 mysql -uroot -p -e USE ApolloConfigDB; SHOW TABLES; mysql -uroot -p -e USE ApolloPortalDB; SHOW TABLES;这里的逻辑是先建库、指定字符集再执行 sql 脚本。有几个细节值得注意字符集一定要在建库时指定如果默认库是 latin1表结构导入时虽然不一定报错但后面配置中文内容可能出现乱码。Collation 用 general_ci 就够不用刻意选 utf8mb4_unicode_ci1.4.0 时代这两个排序规则在绝大多数场景下没有性能差异。导入后建议顺手看一下ServerConfig表里的初始数据。ApolloPortalDB 下的ServerConfig表里存了 Portal 访问各环境时要用的 meta 地址这个表后面会被反复用到先混个眼熟。2.3 用同一份 zip 启动 ConfigService、AdminService、Portal数据库准备好之后三个服务就可以启动了。1.4.0 的发布包每个服务是一个可执行 jar启动方式基本一致。关键点是每个服务要连接的数据库不同连接信息通过spring_datasource_url等环境变量传入。先看 ConfigService 的启动方式# 进入解压后的 configservice 目录不同打包方式的目录名可能不一样以实际解压结果为准 cd apollo-1.4.0/config-service java -jar apollo-configservice-1.4.0.jar \ -Dspring_datasource_urljdbc:mysql://localhost:3306/ApolloConfigDB?characterEncodingutf8mb4useSSLfalse \ -Dspring_datasource_usernameroot \ -Dspring_datasource_passwordyour_password这段命令的要点有三个-Dspring_datasource_url指向 ApolloConfigDB不是 PortalDBcharacterEncodingutf8mb4要显式声明否则 JDBC 驱动可能用系统默认编码useSSLfalse是本地和内网环境的常规配置如果线上启用了 SSL按实际配置改。AdminService 的启动命令几乎一样唯一需要注意的是数据库同样指向 ApolloConfigDBcd ../admin-service java -jar apollo-adminservice-1.4.0.jar \ -Dspring_datasource_urljdbc:mysql://localhost:3306/ApolloConfigDB?characterEncodingutf8mb4useSSLfalse \ -Dspring_datasource_usernameroot \ -Dspring_datasource_passwordyour_passwordPortal 是三个服务里最容易配错的一个因为它连接的是 ApolloPortalDB但又要知道各环境的 ConfigService 地址才能从 ConfigService 的注册中心里找到 AdminService。1.4.0 里 Portal 通过-Dapollo_portal_meta_servers或者数据库里的ServerConfig表拿到环境地址常见的写法是这样cd ../portal java -jar apollo-portal-1.4.0.jar \ -Dspring_datasource_urljdbc:mysql://localhost:3306/ApolloPortalDB?characterEncodingutf8mb4useSSLfalse \ -Dspring_datasource_usernameroot \ -Dspring_datasource_passwordyour_password启动完成后按默认端口访问ConfigService 在 8080AdminService 在 8090Portal 在 8070。1.4.0 的默认端口基本是这三个如果端口被占用需要在启动时增加-Dserver.port参数修改。一个常见的误解是 Portal 起来以后就能直接发布配置了。实际第一次启动时Portal 需要从数据库里读取环境地址再通过环境地址访问 ConfigService再通过 ConfigService 内置的注册中心找到 AdminService链路比较长。所以 Portal 启动成功不等于是可用的要在 Portal 里看到环境列表是正常的才算真的通。3. 客户端接入让一个 Spring Boot 应用从 Apollo 读配置并热更新3.1 引入依赖与三个必须的启动参数服务端部署好之后业务系统接入是下一步。这里以一个普通的 Spring Boot 应用为例先说依赖。客户端包的坐标在 1.4.0 时代就是标准的 apollo-client在pom.xml里引入dependency groupIdcom.xxx.framework.apollo/groupId artifactIdapollo-client/artifactId version1.4.0/version /dependencygroupId 我做了脱敏处理实际使用的时候换成项目官方发布坐标即可。引入依赖后应用启动时必须带三个参数app.id标识应用身份env指定环境apollo.meta或dev.meta指定 ConfigService 地址。以 DEV 环境、本机启动为例java -jar your-app.jar \ -Dapp.idyour-app-name \ -DenvDEV \ -Ddev.metahttp://localhost:8080三个参数分别解决三件事app.id决定去 Apollo 里读哪个应用的配置env决定读哪套环境的配置dev.meta告诉客户端 ConfigService 的地址。如果dev.meta不配客户端会尝试从本机找默认地址开发机上通常是找不到的于是出现启动时连接 ConfigService 超时的报错。3.2 配置读取与热更新代码怎么写才不翻车依赖配好、参数带上之后在代码里读取配置有几种方式最简单的是直接Value注入。但直接用Value只能读到启动时的值Apollo 里配置改了以后这个字段不会自动更新这点是最容易翻车的地方。推荐的做法是使用 Apollo 提供的监听器Component public class SampleConfig { Value(${timeout:1000}) private int timeout; ApolloConfigChangeListener(application) public void onChange(ConfigChangeEvent changeEvent) { for (String key : changeEvent.changedKeys()) { ConfigChange change changeEvent.getChange(key); System.out.println(配置变更: key 从 change.getOldValue() 改为 change.getNewValue()); } // 如果希望 timeout 自动更新需要重新读取配置 Config config ConfigService.getAppConfig(); timeout config.getIntProperty(timeout, 1000); } }这段代码的逻辑是先监听application这个命名空间的配置变更事件当配置发生变化时遍历所有变更的 key打印变更详情然后手动从ConfigService重新读取一次配置把新值赋给本地字段。为什么不用 Spring Cloud 里那种RefreshScope因为 1.4.0 的客户端本身不依赖 Spring CloudRefreshScope是 Spring Cloud 的机制它只对标注了RefreshScope的 Bean 生效而且触发方式通常是再发一次 refresh 请求。Apollo 的长轮询机制是服务端主动推送监听到变更后自己决定刷新逻辑反而更直接。这里的timeout变量初始值是 1000来自application命名空间里同名的配置。如果 Apollo 里没有配这个 key就用本地默认值 1000这是Value语法里的默认值兜底。3.3 从发布到客户端收到配置的整条链路整个链路从 Portal 页面点“发布”开始到业务应用日志打印出“配置变更”结束。把链路里每一步发生了什么搞清楚排错时就能快速定位是服务端的问题还是客户端的问题。Portal 先把发布请求发给 AdminServiceAdminService 把配置写入 ApolloConfigDB并触发一个发布事件。ConfigService 里运行着一批长轮询连接客户端每隔一段时间会发起一个长轮询请求挂在 ConfigService 上ConfigService 发现配置有变更后立即把新配置推给客户端。客户端收到推送后把配置写入本地文件缓存的目录同时触发之前注册的监听器执行配置更新逻辑。本地缓存这个点尤其重要。1.4.0 客户端默认会把拉取到的配置写到一个本地文件路径通常包含app.id。这个设计是为了在 ConfigService 完全不可用的时候应用启动依然能用缓存里的旧配置。排错时要注意区分拿到新值说明推送链路是好的拿不到新值但能启动可能走的是缓存。4. 从单机到小集群扩容前要改的 5 个参数和 1 组 JVM 配置4.1 单机部署的瓶颈到底在哪最省事的部署是把 ConfigService、AdminService、Portal 三个服务都放在一台机器上这也是 1.4.0 提供一体化启动脚本的思路。这种部署适合开发测试环境但对生产环境来说瓶颈会先出现在两个地方数据库连接和长轮询连接。ConfigService 承担客户端的长轮询连接一个实例可能挂几千个连接单机撑到一定量级后线程数和文件句柄数会先报警。AdminService 承担配置的发布操作流量通常不大但它是和数据库打交道最频繁的服务。Portal 是纯管理端资源占用最小。所以扩容的顺序建议是先扩 ConfigService再考虑把 Portal 和 AdminService 分开部署数据库优先考虑独立主机和主从。不要一上来就把三个服务各扩一台实际上 ConfigService 的瓶颈远早于另外两个服务出现。4.2 扩容时先改服务端Eureka、连接池、注册地址在 1.4.0 中ConfigService 内置了 Eureka 作为服务注册中心AdminService 启动后会注册到 ConfigService 的 Eureka 上。客户端也是从 ConfigService 的 Eureka 信息里获取所有可用的 ConfigService 实例列表。这意味着扩容 ConfigService 时除了把新实例启动起来要保证所有 ConfigService 实例的 Eureka 配置指向同一个注册中心。用一个实例作为注册中心其他实例指向它。常见配置是在启动参数里增加 Eureka 相关地址java -jar apollo-configservice-1.4.0.jar \ -Dspring_datasource_urljdbc:mysql://localhost:3306/ApolloConfigDB?characterEncodingutf8mb4useSSLfalse \ -Dspring_datasource_usernameroot \ -Dspring_datasource_passwordyour_password \ -Deureka.instance.homePageUrlhttp://your-ip:8080 \ -Deureka.instance.healthCheckUrlhttp://your-ip:8080/health \ -Deureka.serviceUrl.defaulthttp://your-ip:8080/eureka/这里的eureka.serviceUrl.default指向注册中心地址eureka.instance.homePageUrl是当前实例自身的地址。第一次扩容时最常犯的错是把所有实例的homePageUrl都配成同一个 IP这样注册中心里所有实例都指向同一台机器流量全部打到一台等于没有扩容。AdminService 扩容相对简单它注册到同一个 Eureka 上就行。Portal 不需要扩容但需要在 Portal 的配置里更新它能访问到的环境地址列表确保 Portal 能通过 ConfigService 找到新增的 AdminService 实例。配置连接池方面1.4.0 使用 Tomcat JDBC 连接池可以在启动参数里显式指定-Dspring.datasource.tomcat.max-active50 \ -Dspring.datasource.tomcat.max-idle10max-active是最大活跃连接数max-idle是空闲连接数。这两个值不是越大越好数据库默认最大连接数只有 150 左右一台机器上三个服务的连接池加一起超过数据库上限时最先报错的反而是数据库。4.3 客户端一侧参数跟着改服务端扩到多实例后客户端也要改一处关键的参数apollo.meta不再只是单台机器地址而应该指向一个负载均衡入口或者配置为集群中多个 ConfigService 的地址。客户端有一个参数apollo.meta也可以配置成逗号分隔的多个地址。实际生产环境更推荐用一个负载均衡地址比如域名这样后端 ConfigService 扩容缩容时客户端无感知。java -jar your-app.jar \ -Dapp.idyour-app-name \ -DenvPRO \ -Dapollo.metahttp://apollo-config.example.com如果继续使用单机 IP 地址那么 ConfigService 缩容时客户端会有一段时间连接失败日志里会反复出现连接超时。这是 1.4.0 客户端的一个明显短板它不像后来的版本那样能通过配置中心自身广播机制及时感知所有实例变化所以负载均衡入口是更稳的选择。另外建议在客户端加上缓存目录参数避免日志目录和缓存目录混在一起-Dapollo.cacheDir/data/apollo-cache/${app.id}缓存目录独立出来之后清理缓存、排查本地缓存文件都方便很多。否则默认情况下缓存会写在用户目录或者系统临时目录不容易找到。5. Apollo 1.4.0 部署避坑五个高频率翻车现场5.1 发布成功但客户端永远拿不到新值现象Portal 上点击发布页面提示发布成功版本号也更新了但业务应用里Value注入的值始终不变。原因最常见的是客户端配置的apollo.meta地址根本连不到 ConfigService客户端启动时拉取配置失败走了本地缓存。发布成功只说明配置写入了数据库不等于推送到了客户端。解决先在业务应用所在机器上直接 curl ConfigService 的地址确认网络通。再检查启动参数里的apollo.meta或者dev.meta是否写对注意环境变量的优先级高于 application.properties。如果线上用域名访问优先在客户端机器上验证域名能解析。确认网络没问题后删除客户端本地缓存目录重启应用看是否加载到新值。5.2 Portal 点发布按钮一直报 admin service 不可用现象Portal 能登录能看到配置列表但每次点发布或者回滚页面都报管理服务不可用。原因Portal 访问 AdminService 不是直接配置地址而是先访问 ConfigService从 Eureka 获取 AdminService 实例列表。常见原因有两个Portal 数据库 ServerConfig 表里配置的 meta 地址或者 AdminService 没注册成功或者注册到了另一个 Eureka。解决先看 AdminService 的日志里是否打印了注册成功的信息。再看 Portal 连接的 ApolloPortalDB 里ServerConfig表确认各环境的地址配置正确。1.4.0 里 Portal 会缓存配置改完数据库需要重启 Portal。5.3 建库脚本执行到一半报字符集错误现象导入 apolloconfigdb.sql 时报错错误信息里带Incorrect string value或者Unknown character set一类的关键信息位置通常在某个包含初始配置的 insert 语句附近。原因建库时没有指定字符集MySQL 默认字符集可能是 latin1而脚本里包含中文初始数据。解决删掉建错的库重新建建库语句必须显式指定CREATE DATABASE ApolloConfigDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE DATABASE ApolloPortalDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;如果线下已经写入过数据不想删库可以用ALTER DATABASE和ALTER TABLE改字符集但 1.4.0 部署初期数据都不多删库重建最省事。5.4 DEV 环境读到了 PRO 环境的配置现象本机运行的应用配置值显示的是生产环境的值而且修改 DEV 环境的配置没有任何反应。原因启动参数里同时配了envDEV和apollo.metahttp://prod-config-address客户端会根据apollo.meta连接 ConfigService然后从 ConfigService 的环境信息里识别环境。如果 meta 地址指向的是 PRO 的 ConfigService即使env写 DEV也会读到 PRO 的配置。解决DEV 环境必须显式配置dev.metaPRO 环境配apollo.meta。不要两个参数都配也不要依赖程序自动推导环境本地开发用dev.meta指向本地启动的 ConfigService 即可。5.5 服务进程还在Eureka 上却看不到实例现象ConfigService 和 AdminService 的 java 进程都在端口也在监听但 Portal 或者客户端报找不到服务实例。原因1.4.0 的服务注册依赖 Eureka注册是有心跳的。如果服务器时钟偏差超过阈值Eureka 会判定实例失联。另一个常见原因是 Eureka 实例列表缓存刷新慢修改配置后没有等足够时间就手动重启多个服务导致注册信息互相覆盖。解决先检查各服务器时间是否一致确保时区同步。生产环境可以用 NTP 同步时间。如果时间没问题单独重启一个 AdminService观察 Eureka 控制台里实例列表是否出现不要同时重启多个实例否则注册信息互相覆盖。6. 收尾上线前把配置变更当成代码变更来管部署和接入都跑通之后真正让这个配置中心发挥价值的是日常的使用习惯。我最终坚持下来的有三个习惯分享给你参考。第一个习惯是配置文件的命名空间按用途拆而不是一个 application 走天下。默认的 application 命名空间适合放最基础的业务配置公共配置放到公共命名空间里敏感配置单独建一个命名空间并做权限隔离。这样做的好处是只有一个应用出现问题的时候回滚粒度是命名空间级别不会把其他模块的配置一起回滚。第二个习惯是发布前先看变更内容再选择发布范围。1.4.0 支持灰度发布小范围验证通过后再全量发布。我的习惯是先在灰度环境改配置观察日志和监控确认没有异常再点全量发布。这个配置中心有回滚按钮但它不是后悔药回滚只能恢复到上一个版本期间其他配置变更也会一起受影响。第三个习惯是把自己的代码写得更稳妥一点。在业务代码里读取配置时不要只依赖Value关键配置加上监听器更新逻辑并且打印变更前后的值。这样一次配置变更日志里就能看到这个 key 的完整链路排错时不用翻三个系统。我最深的一个教训是有一次发布配置前没看灰度直接在 Portal 上点了全量发布当时没有日志输出验证五分钟后线上系统开始报错。好在配置中心有回滚但业务已经受了影响。从那以后任何配置变更我都至少看一眼变更内容再确认影响范围。希望帮到你。本文还有配套的精品资源点击获取