引言

前面有写过一篇《分布式配置中心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. 创建配置中心服务端(Spring Cloud Config)

    创建配置中心服务端 创建好项目后添加配置文件内容 server.port=9004 spring.application.name=spring-cloud-config-server-01 #git ...

  2. SpringBoot 整合携程Apollo配置管理中心

    携程官网对apollo的使用讲解了很多种方式的使用,但是感觉一些细节还是没讲全,特别是eureka配置中心地址的配置 这里对springboot整合apollo说一下 >SpringBoot启动 ...

  3. .Net配置中心-服务端/客户端

    服务端 管理应用,一个应用对应一个站点. 管理应用下的配置. 在保存配置的时候,会更新应用的版本号. 客服端 其他站点引用DLL,并在Global的App_Start中调用ConfigCenter的I ...

  4. C++20协程实例:携程化的IOCP服务端/客户端

    VC支持协程已经有一段时间了,之前一直想不明白协程的意义在哪里,前几天拉屎的时候突然灵光一闪: 以下是伪代码: task server() { for (;;) { sock_context s = ...

  5. Spring Boot 2.0 整合携程Apollo配置中心

    原文:https://www.jianshu.com/p/23d695af7e80 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够 ...

  6. 携程Apollo统一配置中心的搭建和使用

    原文链接:https://blog.csdn.net/luhong327/article/details/81453001 一.Apollo配置中心介绍 1.What is Apollo 1.1 Ap ...

  7. 携程apollo系列-客户端集成

    本文讲解如何在 Java 程序中集成 Apollo 配置, 主要涉及到一些基础用法. 对于一些高级用法, 比如如何加密/解密配置项 (可用于数据库密码配置), 如何动态切换数据源地址,如何动态切换日志 ...

  8. 携程开源分布式配置系统Apollo服务端是如何实时更新配置的?

    引言 前面有写过一篇<分布式配置中心apollo是如何实时感知配置被修改>,也就是客户端client是如何知道配置被修改了,有不少读者私信我你既然说了client端是如何感知的,那服务端又 ...

  9. 分布式配置中心 携程(apollo)

    1.传统配置文件与分布式配置文件区别 传统配置文件:如果修改了配置文件,需要重新打包发布,重新发布服务,而且每个环境的变更配置文件,比较繁琐. 分布式配置文件:将配置文件注册到配置中心上去,可以使用分 ...

随机推荐

  1. ES77

    PUT rr_bd202_chaos_20211220{ "aliases" : { "rr_bd202_chaos_pgold":{} }, "ma ...

  2. Gin 08 上传文件

    单文件上传 cat index.html <!DOCTYPE html> <html lang="en"> <head> <meta ch ...

  3. [SHA2017](web) writeup

    [SHA2017](web) writeup Bon Appétit (100) 打开页面查看源代码,发现如下 自然而然想到php伪协议,有个坑,看不了index.php,只能看 .htaccess ...

  4. Knife4j 注解详谈

    Controller层添加注解 @Api:用于类:表示标识这个类是swagger的资源 属性名称 数据类型   默认值  说明  value      String  ""  字段 ...

  5. 到底为什么不建议使用SELECT *?

    "不要使用SELECT *"几乎已经成为了MySQL使用的一条金科玉律,就连<阿里Java开发手册>也明确表示不得使用*作为查询的字段列表,更是让这条规则拥有了权威的加 ...

  6. 半吊子菜鸟学Web开发 -- PHP学习 4 --异常

    PHP异常处理 1 抛出一个异常 与Python的try except类似,PHP用try catch来捕获异常 基本语法 try{ //可能出现错误或异常的代码 //catch表示捕获,Except ...

  7. 解决Flash CS6初始化字体就自动退出

    系统win7 问题始于安装了同事的字体,启动Flash CS6时界面上显示开始初始化字体...然后 就自动退出了 尝试过进入pe系统删除字体,可是删除不了 网上搜索找到了这一个删除字体的工具FontF ...

  8. python数据分析与挖掘实战————银行分控模型(几种算法模型的比较)

    一.神经网络算法: 1 import pandas as pd 2 from keras.models import Sequential 3 from keras.layers.core impor ...

  9. 100行代码实现HarmonyOS“画图”应用,eTS开发走起!

    本期我们给大家带来的是"画图"应用开发者Rick的分享,希望能给你的HarmonyOS开发之旅带来启发~ 介绍 2021年的华为开发者大会(HDC2021)上,HarmonyOS ...

  10. java常见面试问题总结

    JDK1.7 并发的HashMap为什么会引起死循环? hashmap如何解决hash冲突,为什么hashmap中的链表需要转成红黑树? hashmap什么时候会触发扩容? jdk1.8之前并发操作h ...