Apollo 7 — ConfigService 消息扫描设计实现
目录
- 设计
- 代码实现
- 总结
1.设计
Apollo 为了减少依赖,将本来 MQ 的职责转移到了 Mysql 中。具体表现为 Mysql 中的 ReleaseMessage 表。
具体官方文档可见:发送ReleaseMessage的实现方式
用张图简单的来表示一下 :

有人肯定要问了,为什么 Admin Service 和 Config Service 不放在一起呢?我曾提过 issue 问过作者,大概的答案是:两者职责不同,部署的实例数也不同,通常 Admin 会少一些,因为只服务于 Portal,而 Config 则要部署的多一些,因为需要服务于 Client。
第二则是两者的开发节奏也是不一样,Config Service 的更新会影响客户端,而 Admin 的更新则是影响 Portal,所以,分开他们,对服务的部署可以更加细粒度。
关于这个 issue:为什么 admin 和 config 不合在一起呢
高可用的话,可参考下图或链接 可用性考虑:

扯远了。
回到我们之前说的,之所以要使用 Mysql,是因为要减少依赖,为了替代 MQ,而之所以要使用 MQ,是为了解耦 Config 和 Admin,而之所以要使用 Config 和 Admin,则是因为设计,部署,开发节奏等原因。
那么,基于 Mysql 的消息的实现是怎么弄的呢?

上面是 apollo 文档对于发送ReleaseMessage的实现方式的描述。
我们来看看代码实现.
2. 代码实现
在 com.ctrip.framework.apollo.biz.message 包下,有关于消息的实现,

麻雀虽小,五脏俱全。
MessageSender 接口定义了一个方法:
void sendMessage(String message, String channel);
这个 channel 就是 topic 了。 message 就是消息的具体内容了。目前只有一个实现类
DatabaseMessageSender, 基于数据库的消息发送。就是把消息保存到 ReleaseMessage 表中。Topics 定义消息主题,目前只有一个:
apollo-release。ReleaseMessageListener 消息监听器接口,定义了一个方法:
void handleMessage(ReleaseMessage message, String channel),需要实现此方法并注册到扫描器中,当有新的消息时,便会通知监听器。ReleaseMessageScanner 消息扫描器,用于扫描数据库的 ReleaseMessage 表,如果有新数据,则通知监听器。
目前监听器有以下实现:

从左到右:
- ReleaseMessageServiceWithCache,ReleaseMessage 的缓存,用于长轮询判断是否以后新的消息。
- GrayReleaseRulesHolder,灰度规则变化监听器。
- ConfigService,分为缓存和默认,默认的 handleMessage 方法什么都不做,缓存实现则会对 cache 热身。
- NotificationControllerV2 监听器,当客户端被长连接 Hold 住时,消息如果更新则唤醒客户端立即返回,保证及时性。
- NotificationController 已废弃,不谈了。
所以,每当 ReleaseMessageScanner 得到新的消息,都会触发这些监听器。这些监听器在哪里被添加的呢?
位置:com.ctrip.framework.apollo.configservice.ConfigServiceAutoConfiguration.java

ReleaseMessageScanner 的 afterPropertiesSet 方法会启动一个间隔 1 秒定时任务,执行 scanMessages 方法。
具体方法如下:
private boolean scanAndSendMessages() {
//current batch is 500 批处理 500 条
// 根据 maxIdScanned 找到比这个 id 大的 500 条数据,
List<ReleaseMessage> releaseMessages =
releaseMessageRepository.findFirst500ByIdGreaterThanOrderByIdAsc(maxIdScanned);
if (CollectionUtils.isEmpty(releaseMessages)) {
return false;
}
// 开始通知 handleMessage 监听器
fireMessageScanned(releaseMessages);
int messageScanned = releaseMessages.size();// 消息数量
maxIdScanned = releaseMessages.get(messageScanned - 1).getId();// 更新最大 id
return messageScanned == 500;// 如果不足 500, 说明没有新消息了
}
方法很简单,首先根据最大的扫描 Id 找到 500 条消息,对这 500 条消息进行批处理,触发监听器。最后更新最大扫描 Id。如果此次取出的数据量超过 500 条,则认为还有数据,就继续处理。
很明显,每次处理这么多消息肯定很耗时,那么假设处理这些消息要 5 秒,那么定时任务的间隔是多少了呢?答:6 秒。因为定时任务的模式是 scheduleWithFixedDelay 模式,固定的间隔,以当前任务结束时间 + period 时间作为下一个任务的开始时间。
那么,admin 什么时候会向数据库发送消息(保存 ReleaseMessage)呢?
那么就要查看 sendMessage 方法被哪些地方调用就好了。

NamespaceBranchController
1.1 更新灰度规则updateBranchGrayRules
1.2 删除灰度分支deleteBranchNamespaceService
2.1 删除命名空间NamespaceController#deleteNamespace
2.2 删除集群ClusterController#delete
2.3 删除灰度NamespaceBranchController#deleteBranch
2.4 删除命名空间分支NamespaceService#deleteNamespaceReleaseController
3.1 主/灰版本发布publish
3.2 全量发布updateAndPublish
3.3 回滚rollback
这些地方被触发的时候,都会发送消息到数据库。因此,可能会重复触发,导致 ConfigService 重复消费。。。例如在放弃灰度和全量发布的时候,就会重复发送消息。
笔者就这个问题提了 issue,期待官方 fix 这个 bug。
3. 总结
本文重点分析了 apollo 关系消息这块的设计: 每当有 app, cluster,namespace, Release 等操作的时候,都会发送消息到数据库,ConfigService 会定时扫描数据库,有新消息了,就会立即通知各个监听器,确保配置实时推送到客户端。
同时,我们也发现了一个 bug,就是会重复发送消息,引起重复消费。
迫于种种限制,apollo 使用了这种设计,获取,如果可以使用 MQ 的话,一切会更加的简单。
Apollo 7 — ConfigService 消息扫描设计实现的更多相关文章
- Apollo 8 — ConfigService 异步轮询接口的实现
源码 Apollo 长轮询的实现,是通过客户端轮询 /notifications/v2 接口实现的.具体代码在 com.ctrip.framework.apollo.configservice.con ...
- Apollo 6 — ConfigService 获取配置接口
大纲 看本文之前,建议看看 apollo 的官方文档,特别是数据库设计文档. 主流程分析 2.1 聊聊细节 2.2 loadConfig() 加载配置 2.3 auditReleases() 方法记录 ...
- spring boot2.1读取 apollo 配置中心1
第一篇:搭建apollo配置中心 为什么选择apollo,我做了一些对比: Diamond Disconf Apollo Spring Cloud Config 数据持久性 mysql mysql ...
- .NET Core + K8S + Apollo 玩转配置中心
1.引言 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管理 ...
- 开源 VS 商业,消息中间件你不知道的那些事
11月23日,新炬网络中间件技术专家刘拓老师在DBA+社群中间件用户组进行了一次主题为“开源 VS 商业,消息中间件你不知道的那些事”的线上分享.小编特别整理出其中精华内容,供大家学习交流. 嘉宾简介 ...
- SpringCloud之Nacos服务发现(十七)
一 Nacos简介 Nacos是以服务为主要服务对象的中间件,Nacos支持所有主流的服务发现.配置和管理. Nacos主要提供以下四大功能: 服务发现与服务健康检查 Nacos使服务更容易注册自己并 ...
- centos6.5下apollo1.7.1的搭建
前言:apollo MQ作为消息队列中间件,在需要消息列表的应用程序环境中,需要使用该服务器中间件 1.准备工作 2.搭建 3.测试 1.准备工作 第一步:linux系统中配置好java环境 A.卸载 ...
- apollo实现c#与android消息推送(三)
3 实现c#消息推送服务 c#实现消息推送必须引入M2Mqtt.dll,源码 a 连接apache apollo代理服务器的代码.需要引入using uPLibrary.Networking.M2Mq ...
- apollo实现c#与android消息推送(一)
之前做了c#推送消息到手机端,限于网络要求,不能使用百度等现成的推送,查了许多资料,七拼八凑终于凑齐,记录下来,即是复习也是希望对来者有所帮助. 我开发的环境是windows,使用java开发的Apa ...
随机推荐
- forms组件
昨日回顾: 批量插入数据: -queryset的方法:bulk_create(对象列表,数字(一次插入多少)) 分页器: from django.core.paginator import Pagin ...
- CSS3——:nth-child选择器基本用法简述
注:n 是从1开始的 :nth-child 是 CSS3 提供的一个好用的选择器,因为在项目中经常用到,所以简单总结了它的常用方法 下面示例代码截图用的是同一个例子,p元素的父元素都是body p ...
- 一些能体现个人水平的SQL语句[总结篇]
作为一名小小的开发人员,刚入门的时候觉得很难,过了一段时间之后,发现很简单,很快就可以搞定很bug了.然而这并不能说明你就已经很牛掰了,只能说,你不了解其他太多的东西.应该说,数据库有几个共同的命令, ...
- Web发展简史(精编故事版,贤来给你讲故事)
Web发展简史 一. Web发展简史之隔壁老王的故事 有一个人叫隔壁老王,老王有一个爱好就是爱看电影.有一天,这个隔壁老王想看一部电影,可是电脑里面存储的电影太多了,他费了老大劲才从里面找到,觉得很不 ...
- python中numpy.ndarray.shape的用法
今天用到了shape,就顺便学习一下,这个shape的作用就是要把矩阵进行行列转换,请看下面的几个例子就明白了: >>> import numpy as np >>> ...
- 解读JavaScript 之引擎、运行时和堆栈调用
转载自开源中国 译者:Tocy, 凉凉_, 亚林瓜子, 离诌 原文链接 英文原文:How JavaScript works: an overview of the engine, the runtim ...
- hbaes之createTable执行流程
hbase的客户端代码并不想hive一样用java编写,shell调用,而是使用ruby编写. 在admin.rb文件中方法create,其中接受两个参数,其中第二个参数类型为变长参数. 而在crea ...
- 基于 Consul 实现 MagicOnion(GRpc) 服务注册与发现
0.简介 0.1 什么是 Consul Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置. 这里所谓的服务,不仅仅包括常用的 Api 这些服务,也包括软件开发过程 ...
- jQuery源码——.html()方法原理解析
在将字符串转化为html碎片时,一般会将字符串作为容器的innerHTML属性赋值.但innerHTML有很多局限性,比如我们想转化的字符串中有<script>标签并且包含一个立即执行的函 ...
- RabbitMQ访问控制
Access Control (Authentication, Authorisation) in RabbitMQ 认证和授权这两个概念经常容易被混淆,甚至被互换使用.在RabbitMQ中这是错的, ...