前言
在更新Spring Cloud Alibaba Nacos时,想到之前阅读过Apollo的源码,便在这插入记录了过来,后续更新Nacos Config源码
Apollo简介
fork 源码地址 apollo源码
参考apollo架构中心设计
主要分为 Config Service、Admin Service、Portal、Client 四部分
上文介绍到ReleaseController#publish
新增了Release发布对象,本文来跟踪它做的第二件事
发送ReleaseMessage消息
messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
return BeanUtils.transform(ReleaseDTO.class, release);
- 参数 ①
String message
由ReleaseMessageKeyGenerator#generate
生成
public static String generate(String appId, String cluster, String namespace) {
return STRING_JOINER.join(appId, cluster, namespace);
}
即message为 appId+cluster+namespace
+号
拼接的字符串。
- 参数②
String channel
常量值 :apollo-release
进入到 DatabaseMessageSender#sendMessage
这个类比较重要,可能会有比较啰嗦的篇幅。首先该类实现了 MessageSender
接口,重写了 sendMessage
方法,先看该类的构造方法:
public DatabaseMessageSender(final ReleaseMessageRepository releaseMessageRepository) {
cleanExecutorService = Executors.newSingleThreadExecutor(ApolloThreadFactory.create("DatabaseMessageSender", true));
cleanStopped = new AtomicBoolean(false);
this.releaseMessageRepository = releaseMessageRepository;
}
分别初始化了 cleanExecutorService
、cleanStopped
、releaseMessageRepository
接下来 sendMessage
方法,重点看如下代码,其余是日志记录
try {
//新增ReleaseMessage对象,message为appId+cluster+namespace
ReleaseMessage newMessage = releaseMessageRepository.save(new ReleaseMessage(message));
//toClean是一个初始化大小为100的BlockingQueue阻塞队列
//将消息Id添加到队列尾部,如果队列已满,则直接返回false,不阻塞等待(关于LinkedBlockingQueue put、add、offer区别可以看下源码)
toClean.offer(newMessage.getId());
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
logger.error("Sending message to database failed", ex);
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
看到这里
问题①:也没有和configservice交互,发布消息,肯定是要有消费者的,那消费者呢?
问题②:新增消息和新增Release对象差不多,protal没点击一次发布,就以 appId+cluster+namespace
新增一条消息,那数据肯定是会越来越多,同一个namespace的历史消息记录如何维护?
带着这两个疑问,我们来看该类中被 @PostConstruct
修饰的 initialize
方法,该注解会在构造方法初始化之后调用
@PostConstruct
private void initialize() {
//cleanExecutorService、cleanStopped 我上文中看到是在构造方法初始化的
//提交一个线程任务到线程池
cleanExecutorService.submit(() -> {
//不停止和没有被打断就循环
while (!cleanStopped.get()&&!Thread.currentThread().isInterrupted(){
try {
//从队列头部取出消息id,也就是最大的一条,如果没有阻塞等待1s
Long rm = toClean.poll(1, TimeUnit.SECONDS);
if (rm != null) {
//从名字上来看是清理消息,对应上文的问题2
cleanMessage(rm);
} else {
TimeUnit.SECONDS.sleep(5);
}
} catch (Throwable ex) {
Tracer.logError(ex);
}
}
});
}
继续跟踪 cleanMessage
方法
private void cleanMessage(Long id) {
boolean hasMore = true;
//double check in case the release message is rolled back
ReleaseMessage releaseMessage = releaseMessageRepository.findById(id).orElse(null);
if (releaseMessage == null) {
return;
}
//message不少于100且线程没被打断
while (hasMore && !Thread.currentThread().isInterrupted()) {
//根据appId + cluster + namespace 查询小于id的100条数据并升序排序
List<ReleaseMessage> messages = releaseMessageRepository.findFirst100ByMessageAndIdLessThanOrderByIdAsc(
releaseMessage.getMessage(), releaseMessage.getId());
//删除掉老的消息
releaseMessageRepository.deleteAll(messages);
//拉取不足100条,本次不再循环,交给下次线程过来执行
hasMore = messages.size() == 100;
messages.forEach(toRemove -> Tracer.logEvent(
String.format("ReleaseMessage.Clean.%s", toRemove.getMessage()), String.valueOf(toRemove.getId())));
}
}
这段代码回答了上述提出的问题②,每新增一条ReleaseMessage就会往队列的尾部追加数据,然后会有后台线程不断的从头部取出,并清理小于releaseMessageId 100条数据。
配置发布代码adminservice端目前跟踪全部结束,还是没有回答问题①,我们也知道配置发布一定会去通知客户端进行热更新的,我们跟到这里,源码也没有找到答案,
那么我们继续看portal代码里发布配置后发布的事件。