Apollo实现原理
2026/1/15大约 7 分钟ApolloApollo原理分布式配置中心配置管理灰度发布动态配置
1. 整体架构
1.1 架构图
| 层级 | 组件 | 说明 |
|---|---|---|
| 用户层 | 用户/运维 | 通过 Portal 管理配置 |
| Portal层 | Apollo Portal + PortalDB | 配置管理界面,存储用户、权限等元数据 |
| 管理服务层 | Admin Service | 配置管理服务,处理配置的增删改 |
| 数据层 | ConfigDB | 存储配置数据 |
| 配置服务层 | Config Service | 配置读取服务,内置 Eureka |
| 客户端层 | Apollo Client | 应用客户端,获取和监听配置 |
调用链路:Portal → Admin Service → ConfigDB → Config Service → Client
1.2 核心模块职责
| 模块 | 职责 | 部署方式 |
|---|---|---|
| Portal | 配置管理界面,用户操作入口 | 单独部署,管理所有环境 |
| Admin Service | 配置管理服务,处理配置的增删改 | 每个环境独立部署 |
| Config Service | 配置读取服务,提供配置查询和推送 | 每个环境独立部署 |
| Client | 客户端 SDK,集成在应用中 | 随应用部署 |
2. 配置发布原理
2.1 发布流程
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | Portal → Admin Service | 用户发起发布请求 |
| 2 | Admin Service → ConfigDB | 写入配置到数据库 |
| 3 | Admin Service → Config Service | 发送发布消息通知 |
| 4 | Config Service → Client | 通过长轮询通知客户端 |
| 5 | Client → Config Service | 客户端拉取最新配置 |
2.2 数据库表设计
Release 表(发布记录)
CREATE TABLE `Release` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`ReleaseKey` varchar(64) NOT NULL, -- 发布Key
`Name` varchar(64) NOT NULL, -- 发布名称
`Comment` varchar(256) DEFAULT NULL, -- 发布说明
`AppId` varchar(500) NOT NULL, -- 应用ID
`ClusterName` varchar(500) NOT NULL, -- 集群名称
`NamespaceName` varchar(500) NOT NULL, -- Namespace名称
`Configurations` longtext NOT NULL, -- 配置内容(JSON)
`IsAbandoned` bit(1) NOT NULL DEFAULT b'0', -- 是否废弃
PRIMARY KEY (`Id`)
);ReleaseMessage 表(发布消息)
CREATE TABLE `ReleaseMessage` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Message` varchar(1024) NOT NULL, -- 消息内容(appId+cluster+namespace)
PRIMARY KEY (`Id`)
);3. 配置推送原理
3.1 长轮询机制
Apollo 使用 HTTP 长轮询实现配置的实时推送:
| 步骤 | Client | Config Service |
|---|---|---|
| 1 | 发起长轮询请求 GET /notifications/v2,timeout=90s | - |
| 2 | - | Hold 请求,等待配置变更或超时 |
| 3 | - | 配置变更时立即返回变更的 Namespace 列表 |
| 4 | 拉取最新配置 GET /configs/{appId}/{cluster}/{namespace} | - |
| 5 | - | 返回配置内容 |
| 6 | 继续下一轮长轮询 | - |
特点:
- 超时时间 90 秒
- 配置变更时立即返回,无需等待超时
- 超时后返回 304 Not Modified,客户端继续下一轮轮询
3.2 长轮询实现
Config Service 端实现:
@RestController
public class NotificationControllerV2 {
// 存储客户端的长轮询请求
private final Multimap<String, DeferredResultWrapper> deferredResults =
Multimaps.synchronizedSetMultimap(HashMultimap.create());
@GetMapping("/notifications/v2")
public DeferredResult<ResponseEntity<List<ApolloConfigNotification>>>
pollNotification(
@RequestParam String appId,
@RequestParam String cluster,
@RequestParam String notifications) {
// 创建 DeferredResult,超时时间 90 秒
DeferredResult<ResponseEntity<List<ApolloConfigNotification>>>
deferredResult = new DeferredResult<>(90000L);
// 解析客户端关注的 Namespace
List<ApolloConfigNotification> clientNotifications =
gson.fromJson(notifications, notificationType);
// 检查是否有配置变更
List<ApolloConfigNotification> newNotifications =
checkForUpdates(appId, cluster, clientNotifications);
if (!newNotifications.isEmpty()) {
// 有变更,立即返回
deferredResult.setResult(ResponseEntity.ok(newNotifications));
} else {
// 无变更,Hold 请求
DeferredResultWrapper wrapper = new DeferredResultWrapper(deferredResult);
for (ApolloConfigNotification notification : clientNotifications) {
String key = assembleKey(appId, cluster, notification.getNamespaceName());
deferredResults.put(key, wrapper);
}
}
return deferredResult;
}
// 配置发布时调用,通知所有等待的客户端
public void handleMessage(String message) {
String key = message; // appId+cluster+namespace
Collection<DeferredResultWrapper> results = deferredResults.get(key);
for (DeferredResultWrapper result : results) {
result.setResult(/* 变更通知 */);
}
}
}3.3 客户端实现
public class RemoteConfigLongPollService {
private final ScheduledExecutorService executor;
public void startLongPolling() {
executor.submit(() -> {
while (!stopped) {
try {
doLongPolling();
} catch (Exception e) {
// 异常后等待一段时间再重试
Thread.sleep(5000);
}
}
});
}
private void doLongPolling() {
// 构建请求 URL
String url = buildLongPollingUrl();
// 发起长轮询请求,超时时间 90 秒
HttpResponse response = httpClient.doGet(url, 90000);
if (response.getStatusCode() == 200) {
// 有配置变更
List<ApolloConfigNotification> notifications =
parseNotifications(response.getBody());
// 通知本地配置仓库拉取最新配置
for (ApolloConfigNotification notification : notifications) {
notifyConfigRepository(notification.getNamespaceName());
}
}
// 继续下一轮长轮询
}
}4. 客户端设计
4.1 客户端架构
| 组件 | 说明 |
|---|---|
| ConfigService | 配置获取入口,对外提供 API |
| ConfigManager | 配置管理器,管理多个 Config 实例 |
| ConfigRepository | 配置仓库,包含三种实现 |
| ├─ RemoteRepository | 从远程 Config Service 获取配置 |
| ├─ LocalRepository | 内存缓存,提供快速访问 |
| └─ LocalFileRepository | 本地文件缓存,容灾使用 |
| RemoteConfigLongPollService | 长轮询服务,监听配置变更 |
4.2 配置获取流程
public class DefaultConfig implements Config {
private final ConfigRepository configRepository;
private volatile Properties configProperties;
@Override
public String getProperty(String key, String defaultValue) {
// 1. 从内存缓存获取
String value = configProperties.getProperty(key);
if (value != null) {
return value;
}
// 2. 返回默认值
return defaultValue;
}
// 配置变更时更新内存缓存
@Override
public void onRepositoryChange(String namespace, Properties newProperties) {
Properties oldProperties = this.configProperties;
this.configProperties = newProperties;
// 计算变更的配置项
Map<String, ConfigChange> changes = calcChanges(oldProperties, newProperties);
// 通知监听器
fireConfigChange(new ConfigChangeEvent(namespace, changes));
}
}4.3 本地缓存机制
public class LocalFileConfigRepository implements ConfigRepository {
private final String localCacheDir;
// 从本地文件加载配置
private Properties loadFromLocalCacheFile() {
File file = new File(localCacheDir, assembleLocalCacheFileName());
if (!file.exists()) {
return null;
}
Properties properties = new Properties();
try (InputStream in = new FileInputStream(file)) {
properties.load(in);
}
return properties;
}
// 将配置持久化到本地文件
private void persistLocalCacheFile(Properties properties) {
File file = new File(localCacheDir, assembleLocalCacheFileName());
try (OutputStream out = new FileOutputStream(file)) {
properties.store(out, "Apollo Local Cache");
}
}
// 缓存文件命名:{appId}+{cluster}+{namespace}.properties
private String assembleLocalCacheFileName() {
return String.format("%s+%s+%s.properties", appId, cluster, namespace);
}
}4.4 容错机制
Apollo 客户端的容错设计:
| 场景 | 处理方式 |
|---|---|
| Config Service 不可用 | 从本地缓存读取配置 |
| 网络抖动 | 自动重试,指数退避 |
| 配置拉取失败 | 使用上次成功获取的配置 |
| 长轮询超时 | 自动重新发起长轮询 |
5. 服务发现机制
5.1 Meta Server
Meta Server 用于服务发现,客户端通过 Meta Server 获取 Config Service 地址:
| 调用方 | 方向 | 目标 | 说明 |
|---|---|---|---|
| Client | → | Meta Server | 客户端请求服务发现 |
| Meta Server | → | Config Service | 返回可用的 Config Service 地址 |
| Config Service | → | Eureka | 服务启动时注册到 Eureka |
| Meta Server | ← | Eureka | 从 Eureka 获取服务列表 |
5.2 服务注册
Config Service 和 Admin Service 启动时会注册到 Eureka:
@EnableEurekaClient
@SpringBootApplication
public class ConfigServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServiceApplication.class, args);
}
}5.3 客户端服务发现
public class ConfigServiceLocator {
private final String metaServerAddress;
public List<ServiceDTO> getConfigServices() {
// 从 Meta Server 获取 Config Service 列表
String url = metaServerAddress + "/services/config";
HttpResponse response = httpClient.doGet(url);
return parseServices(response.getBody());
}
}6. 灰度发布原理
6.1 灰度规则存储
CREATE TABLE `GrayReleaseRule` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`AppId` varchar(64) NOT NULL,
`ClusterName` varchar(32) NOT NULL,
`NamespaceName` varchar(32) NOT NULL,
`BranchName` varchar(32) NOT NULL, -- 灰度分支名
`Rules` varchar(16000) DEFAULT NULL, -- 灰度规则(JSON)
`ReleaseId` int(11) NOT NULL DEFAULT '0', -- 灰度发布ID
PRIMARY KEY (`Id`)
);6.2 灰度规则匹配
public class GrayReleaseRulesHolder {
public boolean hasGrayReleaseRule(String clientAppId, String clientIp, String clientLabel) {
GrayReleaseRule rule = getGrayReleaseRule();
if (rule == null) {
return false;
}
// 按 IP 匹配
if (rule.getClientIpList() != null &&
rule.getClientIpList().contains(clientIp)) {
return true;
}
// 按 Label 匹配
if (rule.getClientLabelList() != null &&
rule.getClientLabelList().contains(clientLabel)) {
return true;
}
return false;
}
}6.3 灰度配置获取
public class ConfigController {
@GetMapping("/configs/{appId}/{clusterName}/{namespace}")
public ApolloConfig queryConfig(
@PathVariable String appId,
@PathVariable String clusterName,
@PathVariable String namespace,
@RequestParam(required = false) String clientIp,
@RequestParam(required = false) String label) {
// 检查是否命中灰度规则
if (grayReleaseRulesHolder.hasGrayReleaseRule(appId, clientIp, label)) {
// 返回灰度配置
return loadGrayConfig(appId, clusterName, namespace);
}
// 返回正式配置
return loadConfig(appId, clusterName, namespace);
}
}7. 高可用设计
7.1 客户端高可用
7.2 服务端高可用
8. 总结
本节我们深入学习了 Apollo 的实现原理:
- 整体架构:Portal、Admin Service、Config Service、Client 的职责和交互
- 配置发布:从 Portal 到 Client 的完整发布流程
- 配置推送:基于 HTTP 长轮询的实时推送机制
- 客户端设计:配置获取、本地缓存、容错机制
- 服务发现:基于 Eureka 的服务注册与发现
- 灰度发布:灰度规则的存储和匹配
- 高可用设计:客户端和服务端的高可用保障
通过理解这些原理,可以更好地使用和运维 Apollo 配置中心。
