Apollo 配置中心入门:一所"小学校"读懂 Portal、Meta Server、Config Service
写在前面:最近在做配置中心联调时,发现团队里最容易混淆的不是"怎么配",而是
三个组件分别存了什么、谁能写、谁只能读 。这篇用一个"小学校"的比喻 + 真实
链路拆解,把这几个概念一次讲透。文中所有地址均已脱敏。
一、先认识三件套
Apollo 由三个核心角色组成,用一所学校来比喻(想象课程表 = 我们要管理的配置):
| 角色 | 比喻 | 干什么的 |
|---|---|---|
| Portal | 🏢 教务处(校长办公室) | 管理控制台。改配置必须来这:出示工作证、登记、盖章发布 |
| Meta Server | 🪧 校门口的指示牌 | 新同学问"公告栏在哪",它负责指路 |
| Config Service | 📋 楼层公告栏 | 贴的都是教务处正式盖章发布的课表,大家只来看 |
一张表记住区别
| 存什么 | 能改配置吗 | |
|---|---|---|
| Portal | 应用/权限/草稿/发布历史(管理态) | ✅ 全系统唯一能写的 |
| Meta Server | 当前活着的 Config Service 节点名单 | ❌ 只指路,不存配置 |
| Config Service | 已发布版本的缓存(发布态) | ❌ 只给读 |
冷知识:Meta 和 Config 是"一家人"
两者其实是同一个程序(apollo-configservice)扮演的两个角色,部署在一起------
- 走
/services/config这个门 → 它是 Meta Server(指路模式) - 走
/configs、/notifications这些门 → 它是 Config Service(发配置模式)
所以别纠结"Meta 和 Config 的机器有什么区别"------通常没有区别,区别在接口角色。
二、各自"手里"拿的是什么(最容易糊涂的点)
| Meta Server 手里 | Config Service 手里 | |
|---|---|---|
| 存的内容 | 值班表:活着的节点名单(IP:端口) | 课程表:已发布配置的键值 + 版本号 |
| 数据会变吗 | 会!节点上线添一行、宕机划一行(动态) | 发布一次换一版(版本号递增) |
| 正本在哪 | 它自己内存(名单即正本) | 数据库 (正本),它只是加了缓存的只读窗口 |
一句话:Meta 存"人名名单",Config 存"配置内容",配置的正本永远在数据库。
三、架构总览(两条链路)
══════════════ 发布链路(写)═══════════════
配置变更发起
│
▼
┌───────────────────────────┐
│ 发布服务 │
│ ① 查询当前配置 │
│ ② 修改配置项 │
│ ③ 发布生效 │
└────────────┬──────────────┘
│ 每步携带 token
▼
┌───────────────────────────┐
│ Portal(唯一写入口) │
│ 鉴权 → 落库 → 生成版本 │
└────────────┬──────────────┘
▼
┌───────────────────────────┐
│ Config Service(只读分发)│
└───────────────────────────┘
══════════════ 消费链路(读)═══════════════
业务服务启动
│
▼
① 问路
┌───────────────────────────┐
│ Meta Server │
│ 【存】活着的节点名单 │
└──────┬────────────────────┘
│ 返回节点列表
▼
② 看板 + 登记(长轮询)
┌───────────────────────────┐
│ Config Service │
│ GET /configs/{appId}/{ns} │
└──────┬────────────────────┘
▼
③ 配置进本地缓存 → 业务生效
(此后有新发布,秒级自动推送,无需重启)
一句话记法:写的找 Portal,读的找 Meta;appId 定谁家,namespace 定哪份。
四、为什么"发布走 Portal、消费走 Meta"?
这是 Apollo 的架构分工决定的------写的入口只有一个:
- 发布一个配置 = 改 item → 落库 → 生成 release → 记录审计 → 通知各 Config Service 刷新。
这套事务性流程只有 Portal 能执行;token 鉴权、修改人、灰度也全挂在 Portal 侧。 - Meta Server 和 Config Service 在架构上就是只读角色 ,从它们"提交"配置,
接口根本不存在。 - 绕过 Portal 直写数据库是反模式------破坏审计、权限与刷新通知机制。
对应到配置文件上:
消费服务 yaml: 发布服务 yaml:
app.id: <应用ID> ← 读谁家 portal-url: <Portal 地址> ← 写入口
apollo.meta: <meta 地址> ← 去哪找 token: <开放授权 token> ← 工作证
apollo.bootstrap.namespaces: ... app-id / namespace ← 写到哪
--
补充:Portal 为什么是图形化页面?是 Apollo 的"必然"吗?
不是必然,而是定位决定的。
- Portal 是 Apollo 的管理控制台(Web UI),面向人做配置的增删改、发布、权限与审计,所以天然做成浏览器可访问的图形界面。
- 图形化只是 Portal 的前端表现 ,底层仍是 HTTP 接口;发布服务走的就是 Portal 的 Open API(
/openapi/v1/...),并不依赖页面。 - 反过来,Meta Server 和 Config Service 是纯服务端只读角色 ,只提供 HTTP 接口(
/services/config、/configs、/notifications),没有也不需要图形界面。
所以结论是:Portal 图形化是"管理控制台"这一定位的自然结果,而不是 Apollo 架构上的强制要求------你完全可以不用页面,只用 Open API 完成同样的发布操作。
五、真实链路走一遍(数据已脱敏)
🕗 8:00 --- 消费服务启动
第 1 步:问指示牌(Meta Server 只干这一件事)
http
GET http://<meta地址>/services/config
返回"值班表":
json
[
{"appName":"apollo-configservice","instanceId":"10.x.x.1:8080","port":8080},
{"appName":"apollo-configservice","instanceId":"10.x.x.2:8080","port":8080},
{"appName":"apollo-configservice","instanceId":"10.x.x.3:8080","port":8080}
]
第 2 步:找公告栏要配置
http
GET http://10.x.x.1:8080/configs/<appId>/default/<namespace>
json
{
"appId": "<appId>",
"namespaceName": "<namespace>",
"configurations": {
"some.config.key": "{ ... 业务配置 JSON ... }"
}
}
第 3 步:登记"有更新喊我"(长轮询)
http
GET /notifications/v2?appId=<appId>&namespace=<ns>¬ificationId=38
这个请求挂住不立即返回 ;一旦有新发布,立刻吐回 notificationId=39,
客户端重新拉一次配置,本地缓存更新。
🕑 14:00 --- 改配置(发布服务三步 Open API)
http
① GET /openapi/v1/envs/{env}/apps/{appId}/clusters/{cluster}/namespaces/{ns}/items/{key}
→ 教务处先把现有底稿给你看
② PUT 同路径 Body: {"value":"新值","comment":"变更说明"}
→ 改底稿(此刻只是草稿,公告栏还是旧的!)
③ POST /openapi/v1/envs/{env}/apps/{appId}/clusters/{cluster}/namespaces/{ns}/releases
→ 盖章发布(生效开关)
Portal 落库后的两张表(这就是"教务处存什么"):
Item 表(草稿态) Release 表(发布快照)
┌──────────┬──────────┐ ┌──────────────┬─────────────┐
│ key │ value │ │ releaseTitle │ 配置快照 │
│ some.key │ {..v3..} │ │ rel-v3 │ {some.key: │
└──────────┴──────────┘ └──────────────┘ {..v3..}} │
⚡ 14:00:01 --- 自动生效
Portal 发布 → Config Service 发现新快照 → 缓存换新
→ 挂着的长轮询立刻返回新 notificationId
→ 消费服务重新 GET /configs → 拿到新值 → 下一个请求即生效
六、联调踩过的坑(附排查方法论)
坑 1:只改 item,不点发布
② 修改 之后 ③ 发布 没执行------Item 表是 v3、Release 表还是 v2,
而 Config Service 只认 Release 快照 → 消费方永远读到旧值。
发布三步不是仪式感,第 ③ 步是生效开关。
坑 2:发布服务的 Portal 地址配错
三个故障形态,一条命令即可判别(把地址换成待测的):
bash
curl -is "http://<待测地址>/openapi/v1/envs/{env}/apps/{appId}/clusters/{cluster}/namespaces/{ns}/items/{key}"
| 返回 | 结论 |
|---|---|
| 401 + JSON | ✅ 路由正确(没带 token 被 Portal 鉴权拦截,正是我们要的) |
| 404 + Spring 默认错误体(message 为空) | ❌ 该地址没有路由 Open API ------Portal 对任何 /openapi/* 请求都会先鉴权后 404,没带 token 就拿到 404 说明请求根本没到 Portal |
| 连接被拒 / 超时 | ❌ 端口上没服务或网络不通 |
经验:"管理入口域名"≠"Open API 入口域名" 。一个网关后可能同时挂着
Web 控制台、服务发现、Open API 三类路由,域名对不代表路径通。务必实测。
坑 3:bootstrap.namespaces 与按需读取分不清
apollo.bootstrap.namespaces:启动即加载 进 Spring(供@Value注入用)- 代码里
ConfigService.getConfig(namespace):按需读取,首次调用才拉取并注册长轮询
业务代码按需读时不需要 挤进 bootstrap;只有要用 @Value 直接注入时才加。
坑 4:跨应用读取
配置放在别的应用名下时,消费方需要显式声明目标(如 appId:namespace 形态或
客户端双参 API)。但优先考虑把配置放到消费方自己的应用下 ,发布方经
Open API 跨应用写入------权限边界更清晰。
七、端口速查表(8080 / 8070 / 8090 ...分别是谁)
Apollo 各组件默认端口容易记混,先给结论:
| 端口 | 组件 | 说明 |
|---|---|---|
| 8070 | Portal | 管理控制台(Web UI)+ Open API 写入口,浏览器访问 http://<portal地址>:8070 |
| 8080 | Config Service | 配置读取接口 /configs、长轮询 /notifications/v2 |
| 8090 | Admin Service | Portal 与数据库之间的管理接口,供 Portal 调用,业务方一般不直连 |
| 8080(同一进程) | Meta Server | 与 Config Service 同进程,走 /services/config 指路接口 |
几个容易踩的点:
- Meta Server 没有独立端口 :它就是 Config Service 进程里的
/services/config接口,所以「Meta 的端口」= Config Service 的端口(默认 8080)。 - Portal 是 8070,不是 8080 :很多人把 Portal 和 Config Service 的端口记反,结果
portal-url配成 8080,请求打到 Config Service 上自然 404。 - Admin Service 8090 不用暴露给业务方:它只服务于 Portal,业务侧只需要 Portal(8070)和 Config Service(8080)。
- 以上都是默认值 ,实际部署常被改(如容器化后统一走网关),以
apollo-configservice、apollo-adminservice、apollo-portal各自application.yml里的server.port为准。
一句话记法:8070 写(Portal)、8080 读(Config/Meta)、8090 内部(Admin)。
七、配置键速查表
| 配置键 | 配在哪 | 作用 |
|---|---|---|
app.id |
消费服务 yaml | 读谁家(默认读自己应用名下的 namespace) |
apollo.meta |
消费服务 yaml | 服务发现地址(只找节点,不读配置) |
apollo.bootstrap.namespaces |
消费服务 yaml | 启动即加载的 namespace 列表 |
portal-url |
发布服务配置 | Open API 写入口(必须是Portal 的地址) |
token |
发布服务配置(环境变量注入) | Portal 开放授权的工作证,按 appId+namespace 授权 |
app-id / namespace |
发布服务配置 | 写入目标(建议支持按环境/站点覆盖) |
env |
双方 | 环境标识(如 DEV/UAT/PRO,以 Portal 实际注册为准) |
八、总结
- 改课程表去教务处,看课程表先看指示牌再去公告栏------指示牌自己可没有课程表
- 写入口全系统唯一(Portal + Open API + token),读路径全靠"Meta 找节点 +
Config 只读分发 + 长轮询自动刷新" - 排查三板斧:401 说明路通了、404 先怀疑路由、refused 先怀疑端口
- 记住"草稿 vs 快照":改了不发布 = 抽屉里改了、公告栏没换
有问题欢迎交流。文中比喻纯属帮助记忆,生产请以 Apollo 官方文档为准。