Apollo源码-Adminservice发布ReleaseMessage消息

前言

在更新Spring Cloud Alibaba Nacos时,想到之前阅读过Apollo的源码,便在这插入记录了过来,后续更新Nacos Config源码

Apollo简介

fork 源码地址 apollo源码
参考apollo架构中心设计
主要分为 Config ServiceAdmin ServicePortalClient 四部分
上文介绍到ReleaseController#publish 新增了Release发布对象,本文来跟踪它做的第二件事

发送ReleaseMessage消息

messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
                     Topics.APOLLO_RELEASE_TOPIC);
return BeanUtils.transform(ReleaseDTO.class, release);
  • 参数 ① String messageReleaseMessageKeyGenerator#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;
}

分别初始化了 cleanExecutorServicecleanStoppedreleaseMessageRepository 接下来 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代码里发布配置后发布的事件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值