1、配置发布与推送总览 首先我们通过官方文档中的一张图片来描述配置发布与推送的过程。
整个的过程大致是:
(1)用户在管理台页面(portal)操作发布配置。
(2)admin-servece模块接受不了portal请求,将发布的配置保存到数据库中。
(3)config-service监听数据库的配置变化,读取变化的releaseMessage推送至client端。
2、portal端配置发布流程
用户在发布配置时,调用createRelease()进行配置发布,可以看到有一个publish()方法,往里跟。
配置发布入口
可以看到,#publish()方法中又调用了AdminServiceAPI#createRelease()方法请求adminService进行发布,接下来我们就看看admin-service都做了哪些事情?public ReleaseDTO publish(NamespaceReleaseModel model) {
Env env = model.getEnv();
boolean isEmergencyPublish = model.isEmergencyPublish();
String appId = model.getAppId();
String clusterName = model.getClusterName();
String namespaceName = model.getNamespaceName();
String releaseBy = StringUtils.isEmpty(model.getReleasedBy()) ?
userInfoHolder.getUser().getUserId() : model.getReleasedBy();
ReleaseDTO releaseDTO = releaseAPI.createRelease(appId, env, clusterName, namespaceName,
model.getReleaseTitle(), model.getReleaseComment(),
releaseBy, isEmergencyPublish);
Tracer.logEvent(TracerEventType.RELEASE_NAMESPACE,
String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
return releaseDTO;
}
public ReleaseDTO createRelease(String appId, Env env, String clusterName, String namespace,
String releaseName, String releaseComment, String operator,
boolean isEmergencyPublish) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("name", releaseName);
parameters.add("comment", releaseComment);
parameters.add("operator", operator);
parameters.add("isEmergencyPublish", String.valueOf(isEmergencyPublish));
HttpEntity<MultiValueMap<String, String>> entity =
new HttpEntity<>(parameters, headers);
ReleaseDTO response = restTemplate.post(
env, "apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", entity,
ReleaseDTO.class, appId, clusterName, namespace);
return response;
}
3、admin-service端配置发布流程 还是一样,我们先看下admin端的入口ReleaseController#publish(),方法中也调用了一个publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish )方法,但是在这个方法中并没有像portal端一样发起一个http请求,而是将收到的配置存到数据库中。
在publish方法中,先是校验了发布权限以及是否存在父namespace对象,若存在父对象,则当前的配置作为子对象灰度发布。否则的话,调用masterRelease()将配置保存到数据库中,代码如下:
private Release masterRelease(Namespace namespace, String releaseName, String releaseComment,
Map<String, String> configurations, String operator,
int releaseOperation, Map<String, Object> operationContext) {
// 获取上一次发布的配置,本次配置保存后,同时会保存一条记录,记录本次发布配置的发布id以及上一次发布配置的id信息
Release lastActiveRelease = findLatestActiveRelease(namespace);
long previousReleaseId = lastActiveRelease == null ? 0 : lastActiveRelease.getId();
// 保存本次发布的配置到Release表
Release release = createRelease(namespace, releaseName, releaseComment,
configurations, operator);
//保存上次发布的配置信息记录
releaseHistoryService.createReleaseHistory(namespace.getAppId(), namespace.getClusterName(),
namespace.getNamespaceName(), namespace.getClusterName(),
release.getId(), previousReleaseId, releaseOperation,
operationContext, operator);
return release;
}
4、config-service监听配置发布 上面讲到admin端会把发布的配置保存到release表中,而config端则是通过监听release表的变化来获取发布的配置的。 在config启动时,会自动装配ConfigServiceAutoConfiguration类,而这个类会向spring注入releaseMessagescanner这个bean,用来添加不同的监听器。可以看到,其中就包括notificationControllerV2和releaseMessageServiceWithCache,这也是apollo实现配置实时推送至client端的关键。
同时,再看看ReleaseMessagescanner这个类的构造函数,可以看到在初始化时会创建一个调度线程,这个线程的作用就是用来监听release表。
ReleaseMessagescanner类实现了InitializingBean接口,所以在服务启动时,调度线程开始扫描数据库,当数据库插入新的配置时,则读取新的配置通知监听器。
@Override
public void afterPropertiesSet() throws Exception {
// 获取线程调度的间隔时间大小,默认是1s
databaseScanInterval = bizConfig.releaseMessageScanIntervalInMilli();
maxIdScanned = loadLargestMessageId();
executorService.scheduleWithFixedDelay(() -> {
Transaction transaction = Tracer.newTransaction("Apollo.ReleaseMessageScanner", "scanMessage");
try {
// 扫描之前处理失败的配置,在上面的图片中可以看到这个bean维护了一个
// missingReleaseMessages的map,用来记录处理失败的release
scanMissingMessages();
// 扫描数据库中的配置,若存在新增的配置,则推送给监听器
scanMessages();
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
logger.error("Scan and send message failed", ex);
} finally {
transaction.complete();
}
}, databaseScanInterval, databaseScanInterval, TimeUnit.MILLISECONDS);
}
// 扫描并发送新发布的配置
private boolean scanAndSendMessages() {
//查询500条id最大的记录
List<ReleaseMessage> releaseMessages =
releaseMessageRepository.findFirst500ByIdGreaterThanOrderByIdAsc(maxIdScanned);
// 查不到记录则返回
if (CollectionUtils.isEmpty(releaseMessages)) {
return false;
}
// 发布配置
fireMessageScanned(releaseMessages);
//数据库返回的记录数
int messageScanned = releaseMessages.size();
// 获取最大的自增id
long newMaxIdScanned = releaseMessages.get(messageScanned - 1).getId();
// 若最大的自增id减去上次查到的最大自增id大于查到的数据库记录,则表示本次查询到的配置并非是全部的配置,存在未读到的发布配置。
if (newMaxIdScanned - maxIdScanned > messageScanned) {
// 将配置保存到missingReleaseMessages中
recordMissingReleaseMessageIds(releaseMessages, maxIdScanned);
}
maxIdScanned = newMaxIdScanned;
return messageScanned == 500;
}
// 推送扫描到的配置至监听器
private void fireMessageScanned(Iterable<ReleaseMessage> messages) {
for (ReleaseMessage message : messages) {
for (ReleaseMessageListener listener : listeners) {
try {
listener.handleMessage(message, Topics.APOLLO_RELEASE_TOPIC);
} catch (Throwable ex) {
Tracer.logError(ex);
logger.error("Failed to invoke message listener {}", listener.getClass(), ex);
}
}
}
}