如需参考源码解析,请访问:https://gitee.com/lidishan/apollo-code-analysis
阅读前声明:本文不做相关用法说明,只解析Apollo源码

Apollo整体架构图

Apollo Server服务端解析_服务端

Apollo服务端的核心作用在于什么?配置变更和发现

配置变更与发现

配置的变更与发现基于一张表处理ReleaseMessage

Apollo Server服务端解析_java_02

实现步骤

  1. Admin Service发布配置后,插入数据到ReleaseMessage表
  1. 数据格式:AppId+Cluster(集群名称)+NameSpace(命名空间)
  2. 例子如下图
  1. Config Service开启线程每秒扫描一次ReleaseMessage表
  1. 位置:ReleaseMessageScanner#afterPropertiesSet()
  1. 扫描到新消息,会通知所有监听器(ReleaseMessageListener)
  1. 位置:如NotificationControllerV2
  1. 如NotificationControllerV2通知对应客户端
  2. Apollo Server服务端解析_java_03

针对上诉流程图分析源码

定期扫描ReleaseMessage变更

  1. 位置:ReleaseMessageScanner#afterPropertiesSet()
  2. 开启定时线程池每隔1秒扫描一次配置变更情况
  1. 第一步:获取 ReleaseMessage表 当前最大的ID
  2. 第二步:执行定时任务,定时扫描是否有变更,分批(500一页)取出并通知
public class ReleaseMessageScanner {
@Override
public void afterPropertiesSet() throws Exception {
// 获取定时请求时间
databaseScanInterval = bizConfig.releaseMessageScanIntervalInMilli();
// 第一步:获取 ReleaseMessage表 当前最大的ID
maxIdScanned = loadLargestMessageId();
// 执行定时任务
executorService.scheduleWithFixedDelay(() -> {
Transaction transaction = Tracer.newTransaction("Apollo.ReleaseMessageScanner", "scanMessage");
try {
// 第二步:执行定时任务,定时扫描是否有变更,分批(500一页)取出并通知
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 void scanMessages() {
boolean hasMoreMessages = true;
// 分批拉取,每批500个,会先判断是否有更多的数据
while (hasMoreMessages && !Thread.currentThread().isInterrupted()) {
// 扫描并发送消息
hasMoreMessages = scanAndSendMessages();
}
}
/** 扫描并发送消息 scan messages and send */
private boolean scanAndSendMessages() {
// 获取当前id获取后500条发布记录 current batch is 500
List<ReleaseMessage> releaseMessages =
releaseMessageRepository.findFirst500ByIdGreaterThanOrderByIdAsc(maxIdScanned);
if (CollectionUtils.isEmpty(releaseMessages)) {
return false;
}
// 通知监听者
fireMessageScanned(releaseMessages);
// 获取到的数量
int messageScanned = releaseMessages.size();
// 最后的一条id(也是最大的id)
maxIdScanned = releaseMessages.get(messageScanned - 1).getId();
return messageScanned == 500;
}
}

上面分析出怎么扫描出变更的消息并通知监听者。但实际上怎么通知监听者?怎么响应客户端?

通知监听者并响应客户端

继上面fireMessageScanned(releaseMessages)可追踪到通知监听者逻辑如下

  1. fireMessageScanned(releaseMessages)调用遍历listeners
    ,并通知调用到了NotificationControllerV2#handleMessage()
  2. 通过deferredResults.get(content)获取待通知clients,如果太多就走异步,没超过批量的默认值就走通知,
    然后设置到DeferredResultWrapper结果中,会触发tomcat的长轮询结果返回通知
    备注:deferredResutls是客户端请求服务端接口/notifications/v2/pollNotification塞进来的,并执行了onTimeout、onCompletion回调方法,
    只需要等待handleMessage处理完就会返回结果(这个原理需要是tomcat一个异步处理连接的机制,实现长轮询的方法)
public class NotificationControllerV2 {
@Override
public void handleMessage(ReleaseMessage message, String channel) {
String content = message.getMessage();
// ...省略....校验合法性....
//create a new list to avoid ConcurrentModificationException
List<DeferredResultWrapper> results = Lists.newArrayList(deferredResults.get(content));
ApolloConfigNotification configNotification = new ApolloConfigNotification(changedNamespace, message.getId());
configNotification.addMessage(content, message.getId());
// 客户端太多,开个线程走异步 do async notification if too many clients
if (results.size() > bizConfig.releaseMessageNotificationBatch()) {
largeNotificationBatchExecutorService.submit(() -> {
for (int i = 0; i < results.size(); i++) {
if (i > 0 && i % bizConfig.releaseMessageNotificationBatch() == 0) {
try {
TimeUnit.MILLISECONDS.sleep(bizConfig.releaseMessageNotificationBatchIntervalInMilli());
} catch (InterruptedException e) {}
}
results.get(i).setResult(configNotification);
}
});
return;
}
// 还行,走同步
for (DeferredResultWrapper result : results) {
result.setResult(configNotification);
}
}
}