写点什么

阿里二面:携程配置中心 Apollo 服务端是如何感知配置变化的

作者:java金融
  • 2022 年 4 月 10 日
  • 本文字数:4555 字

    阅读完需:约 15 分钟

引言

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


服务端如何感知更新

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



1.用户在 Portal 操作配置发布 2.Portal 调用 Admin Service 的接口操作发布 3.Admin Service 发布配置后,发送 ReleaseMessage 给各个 Config Service4.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 Service4.Config Service 收到 ReleaseMessage 后,通知对应的客户端


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

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。

  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。

  • 感谢您的阅读,十分欢迎并感谢您的关注。


站在巨人的肩膀https://www.apolloconfig.com/#/zh/design/apollo-design?id=%e4%b8%80%e3%80%81%e6%80%bb%e4%bd%93%e8%ae%be%e8%ae%a1https://www.iocoder.cn/Apollo/client-polling-config/

发布于: 刚刚阅读数: 3
用户头像

java金融

关注

公众号:【java金融】 2018.05.15 加入

专注 Java 领域相关技术:java基础、 Java SE、Java 并发、JVM、分布式、中间件、微服务、Spring、mysql、oracle等技术。

评论

发布
暂无评论
阿里二面:携程配置中心Apollo服务端是如何感知配置变化的_java金融_InfoQ写作平台