引言

前面有写过一篇《分布式配置中心apollo是如何实时感知配置被修改》,也就是客户端client是如何知道配置被修改了,有不少读者私信我你既然说了client端是如何感知的,那服务端又是如何知道配置被修改了。今天我们就一起来看看Apollo在Portal修改了配置文件,怎么通知到configService的。什么是portal和configService 建议可以看看这一篇文章篇《分布式配置中心apollo是如何实时感知配置被修改》,里面对这些模块都有简单的介绍,你如果实在不想看也行,我直接截个图过来

服务端如何感知更新

我们来看官网提供的一张图

1.用户在Portal操作配置发布

2.Portal调用Admin Service的接口操作发布

3.Admin Service发布配置后,发送ReleaseMessage给各个Config Service

4.Config Service收到ReleaseMessage后,通知对应的客户端

上面的流程就是从Portal到ConfigService主要流程,下面我们来看看具体的细节。要知道细节我们要自己动手去调试一把源码。

我们可以照着官网的文档,自己本地把项目run起来。文档写的还是很详细的,只要按照步骤来都能运行的起来。我们随便新建一个项目然后去编辑下key,然后打开浏览器的F12当我们点击提交按钮的时候我们就知道她到底调用了那些接口,有了接口我们就知道了入口剩下的就是打断点进行调试了。

portal 如何获取AdminService



根据这个方法我们是不是就可以定位到portal模块后端代码的controller。找到对应的controller打开看一看基本没有什么业务逻辑



然后portal紧接着就是去调用adminService了。



根据上图我们就可以的方法我们就可以找到对应的adminService了,portal是如何找到对应的adminService服务的,因为adminService 是可以部署多台机器,这里就要用到服务注册和发现了adminService只有被注册到服务中心,portal才可以通过服务注册中心来获取对应的adminService服务了。Apollo 默认是采用eureka来作为服务注册和发现,它也提供了nacos、consul来作为服务注册和发现,还提供了一种kubernetes不采用第三方来做服务注册和发现,直接把服务的地址配置在数据库。如果地址有多个可以在数据库逗号分隔。



它提供了四种获取服务列表的实现方式,如果我们使用的注册中心是eureka 我们是不是需要通过eureka的api去获取服务列表,如果我们的服务发现使用的是nacos我们是不是要通过nacos的API去获取服务列表。。。所以Apollo提供了一个MetaService 层,封装服务发现的细节,对Portal和Client而言,永远通过一个Http接口获取Admin Service和Config Service的服务信息,而不需要关心背后实际的服务注册和发现组件。就跟我们平时搬砖一样没有啥是通过增加一个中间层解决不了的问题,一个不行那就再加一个。所以MetaService提供了两个接口services/admin 和services/config 来分别获取Admin Service和Config Service的服务信息。那么Portal 是如何来调用services/admin这个接口的呢?在 apollo-portal 项目里面com.ctrip.framework.apollo.portal.component#AdminServiceAddressLocator 这个类里面,

  • 这个类在加载的时候会通过MetaService 提供的services/admin 接口获取adminService的服务地址进行缓存。
  @PostConstruct
public void init() {
allEnvs = portalSettings.getAllEnvs();
//init restTemplate
restTemplate = restTemplateFactory.getObject(); refreshServiceAddressService =
Executors.newScheduledThreadPool(1, ApolloThreadFactory.create("ServiceLocator", true));
// 创建延迟任务,1s后开始执行获取AdminService服务地址
refreshServiceAddressService.schedule(new RefreshAdminServerAddressTask(), 1, TimeUnit.MILLISECONDS);
}



上面要去MetaService 请求地址,那么MetaService的地址又是什么呢?这个又如何获取?com.ctrip.framework.apollo.portal.environment#DefaultPortalMetaServerProvider 这个类。

portal 这个模块说完了,我们接着回到adminService了。通过portal调用adminService的接口地址我们很快可以找到它的入口

AdminService 的实现也很简单


@PreAcquireNamespaceLock
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items")
public ItemDTO create(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @RequestBody ItemDTO dto) {
Item entity = BeanUtils.transform(Item.class, dto); ConfigChangeContentBuilder builder = new ConfigChangeContentBuilder();
Item managedEntity = itemService.findOne(appId, clusterName, namespaceName, entity.getKey());
if (managedEntity != null) {
throw new BadRequestException("item already exists");
}
entity = itemService.save(entity);
builder.createItem(entity);
dto = BeanUtils.transform(ItemDTO.class, entity); Commit commit = new Commit();
commit.setAppId(appId);
commit.setClusterName(clusterName);
commit.setNamespaceName(namespaceName);
commit.setChangeSets(builder.build());
commit.setDataChangeCreatedBy(dto.getDataChangeLastModifiedBy());
commit.setDataChangeLastModifiedBy(dto.getDataChangeLastModifiedBy());
commitService.save(commit); return dto;
}

PreAcquireNamespaceLock 注解

首先方法上有个@PreAcquireNamespaceLock 这个注解,这个根据名字都应该能够去猜一个大概就是去获取NameSpace的分布式锁,现在分布式锁比较常见的方式是采用redis和zookeeper。但是在这里apollo是采用数据库来实现的,具体怎么细节大家可以去看看源码应该都看的懂,无非就是加锁往DB里面插入一条数据,释放锁然后把这个数据进行删除。稍微有点不一样的就是如果获取锁失败,就直接返回失败了,不会在继续自旋或者休眠重新去获取锁。 因为获取锁失败说明已经有其他人在你之前修改了配置,只有这个人新增的配置被发布或者删除之后,其他人才能继续新增配置,这样的话就会导致一个NameSpace只能同时被一个人修改。这个限制是默认关闭的需要我们在数据库里面去配置(ApolloConfigDb的ServiceConfig表)

一般我们应用的配置修改应该是比较低频的,多人同时去修改的话情况会比较少,再说有些公司是开发提交配置,测试去发布配置,提交和修改不能是同一个人,这样的话新增配置冲突就更少了,应该没有必要去配置namespace.lock.switch=true一个namespace只能一个人去修改。

接下来的代码就非常简单明了,就是一个简单的参数判断然后执行入库操作了,把数据插入到Item表里面。这是我们新增的配置数据就已经保存了。效果如下



这时候新增的配置是不起作用的,不会推送给客户端的。只是单纯一个类似于草稿的状态。

发布配置

接下来我们要使上面新增的配置生效,并且推送给客户端。同样的我们点击发布按钮然后就能知道对应的后端方法入口



我们通过这个接口可以直接找到adminService的方法入口

 public ReleaseDTO publish(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName,
@RequestParam("name") String releaseName,
@RequestParam(name = "comment", required = false) String releaseComment,
@RequestParam("operator") String operator,
@RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) {
throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId,
clusterName, namespaceName));
}
Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish); //send release message
Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
String messageCluster;
if (parentNamespace != null) {
messageCluster = parentNamespace.getClusterName();
} else {
messageCluster = clusterName;
}
messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
return BeanUtils.transform(ReleaseDTO.class, release);
}
  • 上述代码就不仔细展开分析了,感兴趣的可以自己断点调试下我们重点看下releaseService.publish 这个方法,里面有一些灰度发布相关的逻辑,不过这个不是本文的重点,这个方法主要是往release表插入数据。
  • 接下来就是messageSender.sendMessage这个方法了,这个方法主要是往ReleaseMessage表里面插入一条记录。保存完ReleaseMessage这个表会得到相应的主键ID,然后把这个ID放入到一个队列里面。然后在加载DatabaseMessageSender的时候会默认起一个定时任务去获取上面队列里面放入的消息ID,然后找出比这这些ID小的消息删除掉。

    发布流程就完了,这里也没有说到服务端是怎么感知有配置修改了的。

Config Service 通知配置变化

apolloConfigService 在服务启动的时候ReleaseMessageScanner 会启动一个定时任务 每隔1s去去查询ReleaseMessage里面有没有最新的消息,如果有就会通知到所有的消息监听器比如NotificationControllerV2ConfigFileController等,这个消息监听器注册是在ConfigServiceAutoConfiguration里面注册的。

NotificationControllerV2 得到配置发布的 AppId+Cluster+Namespace 后,会通知对应的客户端,这样就从portal到configService 到 client 整个消息通知变化就串起来了。服务端通知客户端的具体细节可以看看《分布式配置中心apollo是如何实时感知配置被修改》

总结

这样服务端配置如何更新的流程就完了。

1.用户在Portal操作配置发布

2.Portal调用Admin Service的接口操作发布

3.Admin Service发布配置后,发送ReleaseMessage给各个Config Service

4.Config Service收到ReleaseMessage后,通知对应的客户端

apollo的源码相对于其他中间件来说还是相对于比较简单的,比较适合于想研究下中间件源码,又不知道如何下手的同学 。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。

站在巨人的肩膀

https://www.apolloconfig.com/#/zh/design/apollo-design?id=一、总体设计

https://www.iocoder.cn/Apollo/client-polling-config/

携程开源分布式配置系统Apollo服务端是如何实时更新配置的?的更多相关文章

  1. 分布式配置系统Apollo如何实时更新配置的?

    引言 记得我们那时候刚开始学习Java的时候都只是一个单体项目,项目里面的配置基本都是写在项目里面的properties文件中,比如数据库配置啥的,各种逻辑开关,一旦这些配置修改了,还需要重启项目这修 ...

  2. 开源分布式Job系统,调度与业务分离-HttpJob.Agent组件介绍以及如何使用

    项目介绍: Hangfire:是一个开源的job调度系统,支持分布式JOB!! Hangfire.HttpJob 是我针对Hangfire开发的一个组件,该组件和Hangfire本身是独立的.可以独立 ...

  3. 开源分布式Job系统,调度与业务分离-如何创建一个计划HttpJob任务

    项目介绍: Hangfire:是一个开源的job调度系统,支持分布式JOB!! Hangfire.HttpJob 是我针对Hangfire开发的一个组件,该组件和Hangfire本身是独立的.可以独立 ...

  4. 开源分布式Job系统,调度与业务分离-如何创建周期性的HttpJob任务

    项目介绍: Hangfire:是一个开源的job调度系统,支持分布式JOB!! Hangfire.HttpJob 是我针对Hangfire开发的一个组件,该组件和Hangfire本身是独立的.可以独立 ...

  5. centos 6.5环境利用iscsi搭建SAN网络存储服务及服务端target和客户端initiator配置详解

    一.简介 iSCSI(internet SCSI)技术由IBM公司研究开发,是一个供硬件设备使用的.可以在IP协议的上层运行的SCSI指令集,这种指令集合可以实现在IP网络上运行SCSI协议,使其能够 ...

  6. Android 服务端开发之开发环境配置

    Android 服务端开发之开发环境配置 这里是在Eclipse的基础上安装PhpEclipse插件方法,PHPEclipse是Eclipse的 一个用于开发PHP的插件.当然也可以采用Java开发a ...

  7. 基于开源SuperSocket实现客户端和服务端通信项目实战

    一.课程介绍 本期带给大家分享的是基于SuperSocket的项目实战,阿笨在实际工作中遇到的真实业务场景,请跟随阿笨的视角去如何实现打通B/S与C/S网络通讯,如果您对本期的<基于开源Supe ...

  8. 开源分布式追踪系统 — Jaeger介绍

    目录 一.Jaeger是什么 二.Jaeger架构 1. 术语 2. 架构图 三.关于采样率 四.部署与实践 一.Jaeger是什么 Uber开发的一个受Dapper和Zipkin启发的分布式跟踪系统 ...

  9. 基于SkyWalking的分布式跟踪系统 - 微服务监控

    上一篇文章我们搭建了基于SkyWalking分布式跟踪环境,今天聊聊使用SkyWalking监控我们的微服务(DUBBO) 服务案例 假设你有个订单微服务,包含以下组件 MySQL数据库分表分库(2台 ...

随机推荐

  1. 一个关于wait/notify与锁关系的探究

    wait/notify 机制是解决生产者消费者问题的良药.它的核心逻辑是基于条件变量的锁机制处理.所以,它们到底是什么关系?wait()时是否需要持有锁? notify()是否需要持有锁?先说答案:都 ...

  2. mysql innodb事务的ACID及其实现的保证机制

    MySQL事务的ACID,一致性是最终目的.保证一致性的措施有:A原子性:靠undo log来保证(异常或执行失败后进行回滚).D持久性:靠redo log来保证(保证当MySQL宕机或停电后,可以通 ...

  3. 分库分表之后分布式如何保证ID全局唯一性

    分库分表之后分布式如何保证ID全局唯一性 韩师学子--小倪 2018-07-21 23:35:38 8139 收藏 3分类专栏: Mysql版权                         分库分 ...

  4. 在 Java 中 CycliBarriar 和 CountdownLatch 有什么区别?

    CyclicBarrier 可以重复使用,而 CountdownLatch 不能重复使用. Java 的 concurrent 包里面的 CountDownLatch 其实可以把它看作一个计数器, 只 ...

  5. 在 Java 中,如何跳出当前的多重嵌套循环?

    在最外层循环前加一个标记如 A,然后用 break A;可以跳出多重循环.(Java 中 支持带标签的 break 和 continue 语句,作用有点类似于 C 和 C++中的 goto 语 句,但 ...

  6. sleep 方法和 wait 方法有什么区别?

    这个问题常问,sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点 在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器, wait 方法会放弃这个对象的 ...

  7. HTML 5中的输出元素是什么?

    当你需要计算两个输入的结果并将结果放到一个标签里的时候,就需要输出元素了.比如你有两个文本框(参见下图),你想要让这些文本框数字相加,然后输出给标签. 下面就是如何使用HTML 5代码输出元素. &l ...

  8. scanf()函数的原理

    最近使用scanf发现了自己对scanf函数还是不太了解,主要出现在无意中出现的一个错误: scanf正确的写法是,scanf中以什么格式输入变量,则变量的类型就应该是什么格式,如下面scanf输入到 ...

  9. kali Linux 渗透测试 | ARP 欺骗

    目录 ARP 欺骗及其原理 ARP 欺骗实施步骤 必备工具安装 nmap 工具 dsniff 工具 driftnet 工具 ettercap 工具 ARP 欺骗测试 ARP 断网攻击 ARP 欺骗(不 ...

  10. web页面性能优化之接口前置

    上个Q做了一波web性能优化,积累了一点点经验 记录分享一下. 先分享一个比较常用的接口前置 的优化方案吧 优化前首屏秒开大约在40%左右 首屏秒开大约提高了25% 先发一张优化成果图 前置原因 对于 ...