引言

前面有写过一篇《分布式配置中心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. webapi_2 今天全是大经典案例

    今天的案例又大又经典 我想想怎么搞呢因为要用到外联样式之类的了 写入内联也太大了 1. 先来一个单独小页面的吧 一个仿淘宝右侧侧边栏的案例 不多说都在注释里了 <!DOCTYPE html> ...

  2. 一图学Python

    网上有这样一张图片,信息量很大,通常会被配上标题"一张图让你学会Python": 这张图流传甚广,但我没有找到明确的出处,图片上附带了 UliPad 的作者 Limodou 的信息 ...

  3. RabbitMQ 的集群?

    镜像集群模式 你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,然后每次你写消息到 queue 的时候,都会自动把消息到多个实例的 queue 里进行消息同步. 好处在于 ...

  4. JdbcTemplate ?

    JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据 类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处 理.

  5. java-字节流-字符流

    I/O叙述 FileOutputStream类字节输出流的介绍: 写入数据的原理 java程序-->JVM(java虚拟机)--->OS(操作系统)---->OS调用写数据的方法-- ...

  6. (转载)JSON对象使用变量作为键名

    转载链接:https://blog.csdn.net/lihefei_coder/article/details/82499520 //第一种方式 var key = 'name'; var json ...

  7. 转载:23种常用设计模式的UML类图

    转载至:https://www.cnblogs.com/zytrue/p/8484806.html 23种常用设计模式的UML类图 本文UML类图参考<Head First 设计模式>(源 ...

  8. Numpy使用Matplotlib实现可视化绘图

    Numpy使用Matplotlib实现可视化绘图 可以直接将Numpy的数组传给Matplotlib实现可视化绘图: 曲线图 饼图 柱状图 直方图 1. 绘制正弦曲线 2. 绘制饼图 3. 柱状图 4 ...

  9. Sentry前端部署拓展篇(sourcemap关联、issue关联、release控制)

    原文首发于我的个人博客: https://lonhon.top/ 之前的<基础篇>主要介绍了Sentry和基本部署流程,在实际使用过程中你会发现Sentry受欢迎的原因:除了单纯的监控异常 ...

  10. SpringBoot快速入门(解析+入门案例源码实现)

    这里写目录标题 SpringBoot入门 一.SpringBoot 概念 二.JavaConfig 入门 1. JavaConfig 概念 2. 项目准备 三.常用注解 四.SpringBoot 入门 ...