【知其然,知其所以然】配置中心 Apollo源码剖析

种地  金牌会员 | 2022-6-24 08:11:44 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 933|帖子 933|积分 2799

第2章 Apollo源码剖析

能力目标


  • 能够基于Git导入Apollo源码
  • 能够基于IDEA实现DEBUG分析APP创建
  • 掌握Namespace创建过程
  • 掌握Item创建过程
  • 掌握灰度发布创建过程
1:namespace创建、灰度发布配置、Item创建作为自学
2:客户端剖析
​          通信->Http、轮询机制
​          配置文件优先级、缓存、关联关系
​          刷新机制【注解解析】
1 Apollo源码搭建

在上一章我们已经学习了Apollo项目实战,为了更进一步学习Apollo、掌握Apollo工作原理,我们开始学习Apollo源码,所以我们先搭建Apollo源码环境。
1.1 源码下载

我们从github上 https://github.com/ctripcorp/apollo  下载源码,下载后的源码如下:

版本切换至v1.7.1(课程中使用的是1.7.0),如下操作:

1.2 导入数据库

在项目根路径下有scripts/sql目录,下面有2个sql脚本,我们将该脚本导入到数据库中。

如下图,在本地mysql上执行这两个脚本:

1.3 apollo-assembly启动服务

我们启动Apollo服务,需要同时启动configservice、adminservice,如果手动启动比较慢,Apollo帮我们封装了一个工程apollo-assembly,可以基于该工程同时启动 apollo-adminservice 和 apollo-configservice 项目。
修改apollo-configservice的核心配置文件bootstrap.yml添加Eureka不注册Eureka数据也不获取Eureka数据,配置如下:

完整代码如下:
  1. eureka:
  2.   instance:
  3.     hostname: ${hostname:localhost}
  4.     preferIpAddress: true
  5.     status-page-url-path: /info
  6.     health-check-url-path: /health
  7.   server:
  8.     peerEurekaNodesUpdateIntervalMs: 60000
  9.     enableSelfPreservation: false
  10.   client:
  11.     serviceUrl:
  12.       # This setting will be overridden by eureka.service.url setting from ApolloConfigDB.ServerConfig or System Property
  13.       # see com.ctrip.framework.apollo.biz.eureka.ApolloEurekaClientConfig
  14.       defaultZone: http://${eureka.instance.hostname}:8080/eureka/
  15.     healthcheck:
  16.       enabled: true
  17.     eurekaServiceUrlPollIntervalSeconds: 60
  18.     fetch-registry: false
  19.     register-with-eureka: false
复制代码
我们先配置该工程,如下图:

这里的VM optins:
  1. -Dapollo_profile=github
  2. -Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloConfigDB?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
  3. -Dspring.datasource.username=root
  4. -Dspring.datasource.password=123456
  5. -Dlogging.file=D:/project/xc-apollo/apollo-assembly.log
复制代码
参数Program arguments中的两个参数分别表示启动configservice和adminservice服务。
启动完成后,我们请求Eureka http://localhost:8080/

PortalService启动
apollo-portal工程需要单独启动,启动的时候我们也需要配置密码和日志输出文件,如下图:

VM options配置如下:
  1. -Dapollo_profile=github,auth
  2. -Ddev_meta=http://localhost:8080/
  3. -Dserver.port=8070
  4. -Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloPortalDB?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
  5. -Dspring.datasource.username=root
  6. -Dspring.datasource.password=123456
  7. -Dlogging.file=D:/project/xc-apollo/apollo-portal.log
复制代码
启动完成后,我们接下来访问控制台 http://localhost:8070 效果如下:

1.4 服务测试

我们可以先创建一个项目并且app.id=100004458,如下图:


在该项目的application.properties中添加一个username参数,如下图:

Apollo提供了内置的测试服务,该服务会访问Apollo服务app.id=100004458的项目,我们可以在该工程启动时配置VM options参数指定Apollo注册中心地址,如下图:

VM options参数配置如下:
  1. -Denv=dev
  2. -Ddev_meta=http://localhost:8080
复制代码
启动程序,我们输入username回车,可以看到对应数据,如下输出结果:
  1. Apollo Config Demo. Please input key to get the value. Input quit to exit.
  2. > username
  3. > [apollo-demo][main] INFO  [com.ctrip.framework.apollo.demo.api.SimpleApolloConfigDemo] Loading key : username with value: 张三
复制代码
2 Portal创建APP

Apollo创建App的过程如果基于控制台操作是很简单的,但是Apollo是如何实现的呢,我们接下来进行相关源码剖析。

创建APP的流程如上图:
  1. 1:用户在后台执行创建app,会将请求发送到Portal Service
  2. 2:Portal Service将数据保存到Portal DB中
  3. 3:Portal Service同时将数据同步到Admin Service中,这个过程是异步的
  4. 4:Admin Service将数据保存到Config DB中
复制代码
2.1 创建APP

创建APP由Portal Service执行,我们从它的JavaBean、Controller、Service、Dao一步一步分析。
2.1.1 实体Bean

1)Table
APP对应的表结构如下:
  1. CREATE TABLE `App` (
  2.   `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  3.   `AppId` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'AppID',
  4.   `Name` varchar(500) NOT NULL DEFAULT 'default' COMMENT '应用名',
  5.   `OrgId` varchar(32) NOT NULL DEFAULT 'default' COMMENT '部门Id',
  6.   `OrgName` varchar(64) NOT NULL DEFAULT 'default' COMMENT '部门名字',
  7.   `OwnerName` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'ownerName',
  8.   `OwnerEmail` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'ownerEmail',
  9.   `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
  10.   `DataChange_CreatedBy` varchar(32) NOT NULL DEFAULT 'default' COMMENT '创建人邮箱前缀',
  11.   `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  12.   `DataChange_LastModifiedBy` varchar(32) DEFAULT '' COMMENT '最后修改人邮箱前缀',
  13.   `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
  14.   PRIMARY KEY (`Id`),
  15.   KEY `AppId` (`AppId`(191)),
  16.   KEY `DataChange_LastTime` (`DataChange_LastTime`),
  17.   KEY `IX_Name` (`Name`(191))
  18. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='应用表';
复制代码
2)App(Bean)
在 apollo-common 项目中, com.ctrip.framework.apollo.common.entity.App ,继承 BaseEntity 抽象类,应用信息实体。代码如下:
  1. @Entity
  2. @Table(name = "App")
  3. @SQLDelete(sql = "Update App set isDeleted = 1 where id = ?")
  4. @Where(clause = "isDeleted = 0")
  5. public class App extends BaseEntity {
  6.   /**
  7.    * App名字
  8.    */
  9.   @NotBlank(message = "Name cannot be blank")
  10.   @Column(name = "Name", nullable = false)
  11.   private String name;
  12.   /**
  13.    * App.id
  14.    */
  15.   @NotBlank(message = "AppId cannot be blank")
  16.   @Pattern(
  17.       regexp = InputValidator.CLUSTER_NAMESPACE_VALIDATOR,
  18.       message = InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE
  19.   )
  20.   @Column(name = "AppId", nullable = false)
  21.   private String appId;
  22.   /**
  23.    * 部门编号
  24.    */
  25.   @Column(name = "OrgId", nullable = false)
  26.   private String orgId;
  27.   /**
  28.    * 部门名
  29.    */
  30.   @Column(name = "OrgName", nullable = false)
  31.   private String orgName;
  32.   /***
  33.    * 拥有人名
  34.    * 例如在 Portal 系统中,使用系统的管理员账号,即 UserPO.username 字段
  35.    */
  36.   @NotBlank(message = "OwnerName cannot be blank")
  37.   @Column(name = "OwnerName", nullable = false)
  38.   private String ownerName;
  39.   /***
  40.    * 拥有人邮箱
  41.    */
  42.   @NotBlank(message = "OwnerEmail cannot be blank")
  43.   @Column(name = "OwnerEmail", nullable = false)
  44.   private String ownerEmail;
  45.   //...get set 略
  46.   
  47. }
复制代码

  • ORM 选用 Hibernate 框架。
  • @SQLDelete(...) + @Where(...) 注解,配合 BaseEntity.extends 字段,实现 App 的逻辑删除
  • 字段比较简单。
3)BaseEntity(Bean)
com.ctrip.framework.apollo.common.entity.BaseEntity ,是基础实体抽象类。代码如下:
  1. @MappedSuperclass
  2. @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  3. public abstract class BaseEntity {
  4.   /**
  5.    * 编号
  6.    */
  7.   @Id
  8.   @GeneratedValue(strategy = GenerationType.IDENTITY)
  9.   @Column(name = "Id")
  10.   private long id;
  11.   /**
  12.    * 是否删除
  13.    */
  14.   @Column(name = "IsDeleted", columnDefinition = "Bit default '0'")
  15.   protected boolean isDeleted = false;
  16.   /***
  17.    * 数据创建人
  18.    * 例如在 Portal 系统中,使用系统的管理员账号,即 UserPO.username 字段
  19.    */
  20.   @Column(name = "DataChange_CreatedBy", nullable = false)
  21.   private String dataChangeCreatedBy;
  22.   /**
  23.    * 数据创建时间
  24.    */
  25.   @Column(name = "DataChange_CreatedTime", nullable = false)
  26.   private Date dataChangeCreatedTime;
  27.   /**
  28.    * 数据最后更新人
  29.    * 例如在 Portal 系统中,使用系统的管理员账号,即 UserPO.username 字段
  30.    */
  31.   @Column(name = "DataChange_LastModifiedBy")
  32.   private String dataChangeLastModifiedBy;
  33.   /**
  34.    * 数据最后更新时间
  35.    */
  36.   @Column(name = "DataChange_LastTime")
  37.   private Date dataChangeLastModifiedTime;
  38.   /**
  39.    * 保存前置方法
  40.    */
  41.   @PrePersist
  42.   protected void prePersist() {
  43.     if (this.dataChangeCreatedTime == null) {
  44.         dataChangeCreatedTime = new Date();
  45.     }
  46.     if (this.dataChangeLastModifiedTime == null) {
  47.         dataChangeLastModifiedTime = new Date();
  48.     }
  49.   }
  50.   /**
  51.    * 更新前置方法
  52.    */
  53.   @PreUpdate
  54.   protected void preUpdate() {
  55.     this.dataChangeLastModifiedTime = new Date();
  56.   }
  57.   /**
  58.    * 删除前置方法
  59.    */
  60.   @PreRemove
  61.   protected void preRemove() {
  62.     this.dataChangeLastModifiedTime = new Date();
  63.   }
  64.   
  65.   //get set toString...略
  66. }
复制代码
部分注解和方法我们说明一下:

  • id 字段,编号,Long 型,全局自增。
  • isDeleted 字段,是否删除,用于逻辑删除的功能。
  • dataChangeCreatedBy 和 dataChangeCreatedTime 字段,实现数据的创建人和时间的记录,方便追踪。
  • dataChangeLastModifiedBy 和 dataChangeLastModifiedTime 字段,实现数据的更新人和时间的记录,方便追踪。
  • @PrePersist、@PreUpdate、@PreRemove 注解,CRD 操作前,设置对应的时间字段
  • 在 Apollo 中,所有实体都会继承 BaseEntity ,实现公用字段的统一定义。这种设计值得借鉴,特别是创建时间和更新时间这两个字段,特别适合线上追踪问题和数据同步。
数据为什么要同步呢?
在文初的流程图中,我们看到 App 创建时,在 Portal Service 存储完成后,会异步同步到 Admin Service 中,这是为什么呢?
在 Apollo 的架构中,一个环境( Env ) 对应一套 Admin Service 和 Config Service 。
而 Portal Service 会管理所有环境( Env ) 。因此,每次创建 App 后,需要进行同步。
或者说,App 在 Portal Service 中,表示需要管理的 App 。而在 Admin Service 和 Config Service 中,表示存在的 App 。
2.1.2 业务执行流程

1)Controller
在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.controller.AppController ,提供 App 的 API
创建项目的界面中,点击【提交】按钮,调用创建 App 的 API

处理请求的方法如下:
  1. /***
  2. * 创建App
  3. * @param appModel AppModel 对象
  4. * @return
  5. */
  6. @PreAuthorize(value = "@permissionValidator.hasCreateApplicationPermission()")
  7. @PostMapping
  8. public App create(@Valid @RequestBody AppModel appModel) {
  9.   // 将 AppModel 转换成 App 对象
  10.   App app = transformToApp(appModel);
  11.   // 保存 App 对象到数据库
  12.   App createdApp = appService.createAppInLocal(app);
  13.   // 发布 AppCreationEvent 创建事件
  14.   publisher.publishEvent(new AppCreationEvent(createdApp));
  15.   // 授予 App 管理员的角色
  16.   Set<String> admins = appModel.getAdmins();
  17.   if (!CollectionUtils.isEmpty(admins)) {
  18.     rolePermissionService
  19.         .assignRoleToUsers(RoleUtils.buildAppMasterRoleName(createdApp.getAppId()),
  20.             admins, userInfoHolder.getUser().getUserId());
  21.   }
  22.   // 返回 App 对象
  23.   return createdApp;
  24. }
复制代码
关于创建app请求操作我们做一下说明:
  1. 1:POST apps 接口,Request Body 传递 JSON 对象。
  2. 2:com.ctrip.framework.apollo.portal.entity.model.AppModel ,App Model 。在 com.ctrip.framework.apollo.portal.entity.model 包下,负责接收来自 Portal 界面的复杂请求对象。例如,AppModel 一方面带有创建 App 对象需要的属性,另外也带有需要授权管理员的编号集合 admins ,即存在跨模块的情况。
  3. 3:调用 #transformToApp(AppModel) 方法,将 AppModel 转换成 App 对象。转换方法很简单,点击方法,直接查看。
  4. 4:调用 AppService#createAppInLocal(App) 方法,保存 App 对象到 Portal DB 数据库。在 「3.2 AppService」 中,详细解析。
  5. 5:调用 ApplicationEventPublisher#publishEvent(AppCreationEvent) 方法,发布 com.ctrip.framework.apollo.portal.listener.AppCreationEvent 事件。
  6. 6:授予 App 管理员的角色。详细解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。
  7. 7:返回创建的 App 对象。
复制代码
2)Service
在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.service.AppService ,提供 App 的 Service逻辑。
#createAppInLocal(App) 方法,保存 App 对象到 Portal DB 数库。代码如下:
  1. @Transactional
  2. public App createAppInLocal(App app) {
  3.   String appId = app.getAppId();
  4.   // 判断 `appId` 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。
  5.   App managedApp = appRepository.findByAppId(appId);
  6.   if (managedApp != null) {
  7.     throw new BadRequestException(String.format("App already exists. AppId = %s", appId));
  8.   }
  9.   // 获得 UserInfo 对象。若不存在,抛出 BadRequestException 异常
  10.   UserInfo owner = userService.findByUserId(app.getOwnerName());
  11.   if (owner == null) {
  12.     throw new BadRequestException("Application's owner not exist.");
  13.   }
  14.   // Email
  15.   app.setOwnerEmail(owner.getEmail());
  16.   // 设置 App 的创建和修改人
  17.   String operator = userInfoHolder.getUser().getUserId();
  18.   app.setDataChangeCreatedBy(operator);
  19.   app.setDataChangeLastModifiedBy(operator);
  20.   // 保存 App 对象到数据库
  21.   App createdApp = appRepository.save(app);
  22.   // 创建 App 的默认命名空间 "application"
  23.   appNamespaceService.createDefaultAppNamespace(appId);
  24.   // 初始化 App 角色
  25.   roleInitializationService.initAppRoles(createdApp);
  26.   // Tracer 日志
  27.   Tracer.logEvent(TracerEventType.CREATE_APP, appId);
  28.   return createdApp;
  29. }
复制代码
所有代码执行过程,我们已经在代码中标注了,大家可以按执行流程查看。
3)AppRepository
在 apollo-portal 项目中,com.ctrip.framework.apollo.common.entity.App.AppRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 App 的数据访问,即 DAO 。
代码如下:
  1. public interface AppRepository extends PagingAndSortingRepository<App, Long> {
  2.   App findByAppId(String appId);
  3.   List<App> findByOwnerName(String ownerName, Pageable page);
  4.   List<App> findByAppIdIn(Set<String> appIds);
  5.   List<App> findByAppIdIn(Set<String> appIds, Pageable pageable);
  6.   Page<App> findByAppIdContainingOrNameContaining(String appId, String name, Pageable pageable);
  7.   @Modifying
  8.   @Query("UPDATE App SET IsDeleted=1,DataChange_LastModifiedBy = ?2 WHERE AppId=?1")
  9.   int deleteApp(String appId, String operator);
  10. }
复制代码
持久层是基于 Spring Data JPA 框架,使用 Hibernate 实现。
2.2 数据同步

在前面流程图中我们说过会调用Admin Service执行同步,同步过程是如何同步的呢,其实这里采用了观察者模式进行了监听操作,我们一起来分析一下。
2.2.1 观察者模式

定义:
  1. 对象之间存在一对多或者一对一依赖,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
  2. MQ其实就属于一种观察者模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。
复制代码
优点:
  1. 1:观察者和被观察者是抽象耦合的。
  2. 2:建立一套触发机制。
复制代码
缺点:
  1. 1:如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  2. 2:如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
复制代码
Spring观察者模式
ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。
如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。这种事件机制都必须需要程序显示的触发。
其中spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听ContextRefreshedEvent事件,当所有的bean都初始化完成并被成功装载后会触发该事件,实现ApplicationListener接口可以收到监听动作,然后可以写自己的逻辑。
同样事件可以自定义、监听也可以自定义,完全根据自己的业务逻辑来处理。
2.2.2 事件监听

在Portal Service创建APP的controller中会创建时间监听,代码如下:

事件监听创建后,Portal Service中有一个监听创建监听对象,在该监听对象中会监听创建事件信息,并根据创建的APP进行同步调用,主要调用的是AppAPI,而AppAPI是执行远程操作,代码如下:
  1. @Component
  2. public class CreationListener {
  3.   private final AdminServiceAPI.AppAPI appAPI;
  4.   /***
  5.    * 监听
  6.    * @param event
  7.    */
  8.   @EventListener
  9.   public void onAppCreationEvent(AppCreationEvent event) {
  10.     // 将 App 转成 AppDTO 对象
  11.     AppDTO appDTO = BeanUtils.transform(AppDTO.class, event.getApp());
  12.     // 获得有效的 Env 数组
  13.     List<Env> envs = portalSettings.getActiveEnvs();
  14.     // 循环 Env 数组,调用对应的 Admin Service 的 API ,创建 App 对象。
  15.     for (Env env : envs) {
  16.       try {
  17.         appAPI.createApp(env, appDTO);
  18.       } catch (Throwable e) {
  19.         logger.error("Create app failed. appId = {}, env = {})", appDTO.getAppId(), env, e);
  20.         Tracer.logError(String.format("Create app failed. appId = %s, env = %s", appDTO.getAppId(), env), e);
  21.       }
  22.     }
  23.   }
  24. }
复制代码
AppAPI使用了RestTemplate执行远程操作,代码如下:

2.2.3 同步业务执行流程

在 apollo-adminservice 项目中, com.ctrip.framework.apollo.adminservice.controller.AppController ,提供 App 的 API
#create(AppDTO) 方法,创建 App 。代码如下:
  1. /***
  2. * 创建App
  3. * @param dto
  4. * @return
  5. */
  6. @PostMapping("/apps")
  7. public AppDTO create(@Valid @RequestBody AppDTO dto) {
  8.   // 将 AppDTO 转换成 App 对象
  9.   App entity = BeanUtils.transform(App.class, dto);
  10.   App managedEntity = appService.findOne(entity.getAppId());
  11.   // 判断 `appId` 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。
  12.   if (managedEntity != null) {
  13.     throw new BadRequestException("app already exist.");
  14.   }
  15.   // 保存 App 对象到数据库
  16.   entity = adminService.createNewApp(entity);
  17.   // 将保存的 App 对象,转换成 AppDTO 返回
  18.   return BeanUtils.transform(AppDTO.class, entity);
  19. }
复制代码
com.ctrip.framework.apollo.biz.service.AdminService , #createNewApp(App) 方法,代码如下:
  1. @Transactional
  2. public App createNewApp(App app) {
  3.   // 保存 App 对象到数据库
  4.   String createBy = app.getDataChangeCreatedBy();
  5.   App createdApp = appService.save(app);
  6.   String appId = createdApp.getAppId();
  7.   // 创建 App 的默认命名空间 "application"
  8.   appNamespaceService.createDefaultAppNamespace(appId, createBy);
  9.   // 创建 App 的默认集群 "default"
  10.   clusterService.createDefaultCluster(appId, createBy);
  11.   // 创建 Cluster 的默认命名空间
  12.   namespaceService.instanceOfAppNamespaces(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, createBy);
  13.   return app;
  14. }
复制代码
在 apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.AppService ,提供 App 的 Service 逻辑给 Admin Service 和 Config Service 。
#save(App) 方法,保存 App 对象到数据库中。代码如下:
  1. @Transactional
  2. public App save(App entity) {
  3.   // 判断是否已经存在。若是,抛出 ServiceException 异常。
  4.   if (!isAppIdUnique(entity.getAppId())) {
  5.     throw new ServiceException("appId not unique");
  6.   }
  7.   // 保护代码,避免 App 对象中,已经有 id 属性。
  8.   entity.setId(0);//protection
  9.   App app = appRepository.save(entity);
  10.   // 记录 Audit 到数据库中
  11.   auditService.audit(App.class.getSimpleName(), app.getId(), Audit.OP.INSERT,
  12.       app.getDataChangeCreatedBy());
  13.   return app;
  14. }
复制代码
至于Dao还是JPA操作,我们不再过多讲解了。
3 Namespace创建


namespace创建的流程也是先经过Portal Service,再同步到Admin Service中,执行流程我们先来一起分析一下:

这里我们发现有AppNamespace和Namespace,他们有一定区别:
  1. 数据流向如下:
  2.            在App下创建 AppNamespace 后,自动给 App 下每个 Cluster 创建 Namespace 。
  3.         在App下创建 Cluster 后,根据 App 下 每个 AppNamespace 创建 Namespace 。
  4.         可删除 Cluster 下的 Namespace 。
  5. 总结来说:
  6.         AppNamespace 是 App 下的每个 Cluster 默认创建的 Namespace 。
  7.         Namespace 是 每个 Cluster 实际拥有的 Namespace 。
复制代码
Namespace 类型有三种:
  1. 1:私有类型:私有类型的 Namespace 具有 private 权限。
  2. 2:公共类型:公共类型的 Namespace 具有 public 权限。公共类型的 Namespace 相当于游离于应用之外的配置,且通过 Namespace 的名称去标识公共 Namespace ,所以公共的 Namespace 的名称必须全局唯一。
  3. 3:关联类型:关联类型又可称为继承类型,关联类型具有 private 权限。关联类型的Namespace 继承于公共类型的Namespace,用于覆盖公共 Namespace 的某些配置。
复制代码
我们接下来对该执行流程的源码进行剖析。
3.1 创建AppNamespace

AppNamespace创建由Portal Service发起,我们先来分析该工程。
3.1.1 实体Bean

1)Table
AppNamespace对应表表结构如下:
  1. CREATE TABLE `AppNamespace` (
  2.   `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  3.   `Name` varchar(32) NOT NULL DEFAULT '' COMMENT 'namespace名字,注意,需要全局唯一',
  4.   `AppId` varchar(32) NOT NULL DEFAULT '' COMMENT 'app id',
  5.   `Format` varchar(32) NOT NULL DEFAULT 'properties' COMMENT 'namespace的format类型',
  6.   `IsPublic` bit(1) NOT NULL DEFAULT b'0' COMMENT 'namespace是否为公共',
  7.   `Comment` varchar(64) NOT NULL DEFAULT '' COMMENT '注释',
  8.   `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
  9.   `DataChange_CreatedBy` varchar(32) NOT NULL DEFAULT '' COMMENT '创建人邮箱前缀',
  10.   `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  11.   `DataChange_LastModifiedBy` varchar(32) DEFAULT '' COMMENT '最后修改人邮箱前缀',
  12.   `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
  13.   PRIMARY KEY (`Id`),
  14.   KEY `IX_AppId` (`AppId`),
  15.   KEY `Name_AppId` (`Name`,`AppId`),
  16.   KEY `DataChange_LastTime` (`DataChange_LastTime`)
  17. ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='应用namespace定义';
复制代码
Namespace表结构如下:
  1. CREATE TABLE `Namespace` (
  2.   `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  3.   `AppId` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'AppID',
  4.   `ClusterName` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'Cluster Name',
  5.   `NamespaceName` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'Namespace Name',
  6.   `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
  7.   `DataChange_CreatedBy` varchar(32) NOT NULL DEFAULT 'default' COMMENT '创建人邮箱前缀',
  8.   `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  9.   `DataChange_LastModifiedBy` varchar(32) DEFAULT '' COMMENT '最后修改人邮箱前缀',
  10.   `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
  11.   PRIMARY KEY (`Id`),
  12.   KEY `AppId_ClusterName_NamespaceName` (`AppId`(191),`ClusterName`(191),`NamespaceName`(191)),
  13.   KEY `DataChange_LastTime` (`DataChange_LastTime`),
  14.   KEY `IX_NamespaceName` (`NamespaceName`(191))
  15. ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='命名空间';
复制代码
2)实体Bean
在 apollo-common 项目中,com.ctrip.framework.apollo.common.entity.AppNamespace ,继承 BaseEntity 抽象类,App Namespace 实体。代码如下:
  1. @Entity
  2. @Table(name = "AppNamespace")
  3. @SQLDelete(sql = "Update AppNamespace set isDeleted = 1 where id = ?")
  4. @Where(clause = "isDeleted = 0")
  5. public class AppNamespace extends BaseEntity {
  6.   /**
  7.    * AppNamespace 名
  8.    */
  9.   @NotBlank(message = "AppNamespace Name cannot be blank")
  10.   @Pattern(
  11.       regexp = InputValidator.CLUSTER_NAMESPACE_VALIDATOR,
  12.       message = "Invalid Namespace format: " + InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & " + InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE
  13.   )
  14.   @Column(name = "Name", nullable = false)
  15.   private String name;
  16.   /**
  17.    * App 编号
  18.    */
  19.   @NotBlank(message = "AppId cannot be blank")
  20.   @Column(name = "AppId", nullable = false)
  21.   private String appId;
  22.   /**
  23.    * 格式
  24.    * 参见 {@link ConfigFileFormat}
  25.    */
  26.   @Column(name = "Format", nullable = false)
  27.   private String format;
  28.   /**
  29.    * 是否公用的
  30.    */
  31.   @Column(name = "IsPublic", columnDefinition = "Bit default '0'")
  32.   private boolean isPublic = false;
  33.   /**
  34.    * 备注
  35.    */
  36.   @Column(name = "Comment")
  37.   private String comment;
  38.   
  39.   //get set toString...略
  40.   
  41. }
复制代码

  • appId 字段,App 编号,指向对应的 App 。App : AppNamespace = 1 : N 。
  • format 字段,格式。在 com.ctrip.framework.apollo.core.enums.ConfigFileFormat 枚举类中,定义了6种类型:Properties("properties"), XML("xml"), JSON("json"), YML("yml"), YAML("yaml"), TXT("txt");
  • 字段,是否公用的
  • Namespace的获取权限分为两种:

    • private (私有的):private 权限的 Namespace ,只能被所属的应用获取到。一个应用尝试获取其它应用 private 的 Namespace ,Apollo 会报 “404” 异常。
    • public (公共的):public 权限的 Namespace ,能被任何应用获取。

在 apollo-biz 项目中, com.ctrip.framework.apollo.biz.entity.Namespace ,继承 BaseEntity 抽象类,Cluster Namespace 实体,是配置项的集合,类似于一个配置文件的概念。代码如下:
  1. @Entity
  2. @Table(name = "Namespace")
  3. @SQLDelete(sql = "Update Namespace set isDeleted = 1 where id = ?")
  4. @Where(clause = "isDeleted = 0")
  5. public class Namespace extends BaseEntity {
  6.   /**
  7.    * App 编号 {@link com.ctrip.framework.apollo.common.entity.App#appId}
  8.    */
  9.   @Column(name = "appId", nullable = false)
  10.   private String appId;
  11.   /**
  12.    * Cluster 名 {@link Cluster#name}
  13.    */
  14.   @Column(name = "ClusterName", nullable = false)
  15.   private String clusterName;
  16.   /**
  17.    * AppNamespace 名 {@link com.ctrip.framework.apollo.common.entity.AppNamespace#name}
  18.    */
  19.   @Column(name = "NamespaceName", nullable = false)
  20.   private String namespaceName;
  21.   
  22.   //get ..set ..toString..略
  23. }
复制代码
3.1.2 业务执行流程

1)Controller
提交业务请求会调用apollo-portal的com.ctrip.framework.apollo.portal.controller.NamespaceController,Portal Service提供了提供 AppNamespace 和 Namespace 的 API
com.ctrip.framework.apollo.portal.controller.NamespaceController创建AppNamespace方法源码如下:
  1. @PreAuthorize(value = "@permissionValidator.hasCreateAppNamespacePermission(#appId, #appNamespace)")
  2. @PostMapping("/apps/{appId}/appnamespaces")
  3. public AppNamespace createAppNamespace(@PathVariable String appId,
  4.     @RequestParam(defaultValue = "true") boolean appendNamespacePrefix,
  5.     @Valid @RequestBody AppNamespace appNamespace) {
  6.   // 校验 AppNamespace 的 `name` 非空。
  7.   if (!InputValidator.isValidAppNamespace(appNamespace.getName())) {
  8.     throw new BadRequestException(String.format("Invalid Namespace format: %s",
  9.         InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & " + InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE));
  10.   }
  11.   // 保存 AppNamespace 对象到数据库
  12.   AppNamespace createdAppNamespace = appNamespaceService.createAppNamespaceInLocal(appNamespace, appendNamespacePrefix);
  13.   // 赋予权限,若满足如下任一条件:
  14.   // 1. 公开类型的 AppNamespace 。
  15.   // 2. 私有类型的 AppNamespace ,并且允许 App 管理员创建私有类型的 AppNamespace 。
  16.   if (portalConfig.canAppAdminCreatePrivateNamespace() || createdAppNamespace.isPublic()) {
  17.     // 授予 Namespace Role
  18.     namespaceService.assignNamespaceRoleToOperator(appId, appNamespace.getName(),
  19.         userInfoHolder.getUser().getUserId());
  20.   }
  21.   // 发布 AppNamespaceCreationEvent 创建事件
  22.   publisher.publishEvent(new AppNamespaceCreationEvent(createdAppNamespace));
  23.   // 返回创建的 AppNamespace 对象
  24.   return createdAppNamespace;
  25. }
复制代码
在这里我们不难发现它又创建了监听,所以肯定也会涉及数据同步。
2)Service
在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.service.AppNamespaceService ,提供 AppNamespace 的 Service 逻辑。
#createAppNamespaceInLocal(AppNamespace) 方法,保存 AppNamespace 对象到 Portal DB 数据库。代码如下:
  1. @Transactional
  2. public AppNamespace createAppNamespaceInLocal(AppNamespace appNamespace, boolean appendNamespacePrefix) {
  3.   String appId = appNamespace.getAppId();
  4.   // 校验对应的 App 是否存在。若不存在,抛出 BadRequestException 异常
  5.   //add app org id as prefix
  6.   App app = appService.load(appId);
  7.   if (app == null) {
  8.     throw new BadRequestException("App not exist. AppId = " + appId);
  9.   }
  10.   // public namespaces only allow properties format
  11.   if (appNamespace.isPublic()) {
  12.     appNamespace.setFormat(ConfigFileFormat.Properties.getValue());
  13.   }
  14.   // 拼接 AppNamespace 的 `name` 属性。
  15.   StringBuilder appNamespaceName = new StringBuilder();
  16.   //add prefix postfix
  17.   appNamespaceName
  18.       .append(appNamespace.isPublic() && appendNamespacePrefix ? app.getOrgId() + "." : "")
  19.       .append(appNamespace.getName())
  20.       .append(appNamespace.formatAsEnum() == ConfigFileFormat.Properties ? "" : "." + appNamespace.getFormat());
  21.   appNamespace.setName(appNamespaceName.toString());
  22.   // 设置 AppNamespace 的 `comment` 属性为空串,若为 null 。
  23.   if (appNamespace.getComment() == null) {
  24.     appNamespace.setComment("");
  25.   }
  26.   // 校验 AppNamespace 的 `format` 是否合法
  27.   if (!ConfigFileFormat.isValidFormat(appNamespace.getFormat())) {
  28.    throw new BadRequestException("Invalid namespace format. format must be properties、json、yaml、yml、xml");
  29.   }
  30.   // 设置 AppNamespace 的创建和修改人
  31.   String operator = appNamespace.getDataChangeCreatedBy();
  32.   if (StringUtils.isEmpty(operator)) {
  33.     operator = userInfoHolder.getUser().getUserId();
  34.     appNamespace.setDataChangeCreatedBy(operator);
  35.   }
  36.   appNamespace.setDataChangeLastModifiedBy(operator);
  37.   //公用类型,校验 `name` 在全局唯一
  38.   // globally uniqueness check for public app namespace
  39.   if (appNamespace.isPublic()) {
  40.     checkAppNamespaceGlobalUniqueness(appNamespace);
  41.   } else {
  42.     // 私有类型,校验 `name` 在 App 下唯一
  43.     // check private app namespace
  44.     if (appNamespaceRepository.findByAppIdAndName(appNamespace.getAppId(), appNamespace.getName()) != null) {
  45.       throw new BadRequestException("Private AppNamespace " + appNamespace.getName() + " already exists!");
  46.     }
  47.     // should not have the same with public app namespace
  48.     checkPublicAppNamespaceGlobalUniqueness(appNamespace);
  49.   }
  50.   // 保存 AppNamespace 到数据库
  51.   AppNamespace createdAppNamespace = appNamespaceRepository.save(appNamespace);
  52.   roleInitializationService.initNamespaceRoles(appNamespace.getAppId(), appNamespace.getName(), operator);
  53.   roleInitializationService.initNamespaceEnvRoles(appNamespace.getAppId(), appNamespace.getName(), operator);
  54.   return createdAppNamespace;
  55. }
复制代码
关于Dao我们就不做分析了。
3.2 数据同步

3.2.1 事件监听

com.ctrip.framework.apollo.portal.listener.CreationListener ,对象创建监听器,目前监听 AppCreationEvent 和 AppNamespaceCreationEvent 事件。
我们看看com.ctrip.framework.apollo.portal.listener.CreationListener#onAppNamespaceCreationEvent代码如下:
  1. @EventListener
  2. public void onAppNamespaceCreationEvent(AppNamespaceCreationEvent event) {
  3.   // 将 AppNamespace 转成 AppNamespaceDTO 对象
  4.   AppNamespaceDTO appNamespace = BeanUtils.transform(AppNamespaceDTO.class, event.getAppNamespace());
  5.   // 获得有效的 Env 数组
  6.   List<Env> envs = portalSettings.getActiveEnvs();
  7.   // 循环 Env 数组,调用对应的 Admin Service 的 API ,创建 AppNamespace 对象。
  8.   for (Env env : envs) {
  9.     try {
  10.       namespaceAPI.createAppNamespace(env, appNamespace);
  11.     } catch (Throwable e) {
  12.       logger.error("Create appNamespace failed. appId = {}, env = {}", appNamespace.getAppId(), env, e);
  13.       Tracer.logError(String.format("Create appNamespace failed. appId = %s, env = %s", appNamespace.getAppId(), env), e);
  14.     }
  15.   }
  16. }
复制代码
上面监听仍然会调用远程服务,使用了namespaceAPI执行了远程调用,部分源码如下:

3.2.2 同步业务执行流程

1)Controller
在 apollo-adminservice 项目中, com.ctrip.framework.apollo.adminservice.controller.AppNamespaceController ,提供 AppNamespace 的 API
#create(AppNamespaceDTO) 方法,创建 AppNamespace 。代码如下:
  1. /**
  2. * 创建 AppNamespace
  3. * @param appNamespace
  4. * @param silentCreation
  5. * @return
  6. */
  7. @PostMapping("/apps/{appId}/appnamespaces")
  8. public AppNamespaceDTO create(@RequestBody AppNamespaceDTO appNamespace,
  9.                               @RequestParam(defaultValue = "false") boolean silentCreation) {
  10.   // 将 AppNamespaceDTO 转换成 AppNamespace 对象
  11.   AppNamespace entity = BeanUtils.transform(AppNamespace.class, appNamespace);
  12.   // 判断 `name` 在 App 下是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 BadRequestException 异常。
  13.   AppNamespace managedEntity = appNamespaceService.findOne(entity.getAppId(), entity.getName());
  14.   if (managedEntity == null) {
  15.     if (StringUtils.isEmpty(entity.getFormat())){
  16.       entity.setFormat(ConfigFileFormat.Properties.getValue());
  17.     }
  18.     //不存在,就添加AppNamespace
  19.     entity = appNamespaceService.createAppNamespace(entity);
  20.   } else if (silentCreation) {
  21.     appNamespaceService.createNamespaceForAppNamespaceInAllCluster(appNamespace.getAppId(), appNamespace.getName(),
  22.         appNamespace.getDataChangeCreatedBy());
  23.     entity = managedEntity;
  24.   } else {
  25.     throw new BadRequestException("app namespaces already exist.");
  26.   }
  27.   return BeanUtils.transform(AppNamespaceDTO.class, entity);
  28. }
复制代码
2)Service
在 apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.AppNamespaceService ,提供 AppNamespace 的 Service 逻辑给 Admin Service 和 Config Service 。
#save(AppNamespace) 方法,保存 AppNamespace 对象到数据库中。代码如下:
  1. @Transactional
  2. public AppNamespace createAppNamespace(AppNamespace appNamespace) {
  3.   // 判断 `name` 在 App 下是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 ServiceException 异常。
  4.   String createBy = appNamespace.getDataChangeCreatedBy();
  5.   if (!isAppNamespaceNameUnique(appNamespace.getAppId(), appNamespace.getName())) {
  6.     throw new ServiceException("appnamespace not unique");
  7.   }
  8.   // 保护代码,避免 App 对象中,已经有 id 属性。
  9.   appNamespace.setId(0);//protection
  10.   appNamespace.setDataChangeCreatedBy(createBy);
  11.   appNamespace.setDataChangeLastModifiedBy(createBy);
  12.   // 保存 AppNamespace 到数据库
  13.   appNamespace = appNamespaceRepository.save(appNamespace);
  14.   // 创建 AppNamespace 在 App 下,每个 Cluster 的 Namespace 对象。
  15.   createNamespaceForAppNamespaceInAllCluster(appNamespace.getAppId(), appNamespace.getName(), createBy);
  16.   // 记录 Audit 到数据库中
  17.   auditService.audit(AppNamespace.class.getSimpleName(), appNamespace.getId(), Audit.OP.INSERT, createBy);
  18.   return appNamespace;
  19. }
复制代码
调用 #instanceOfAppNamespaceInAllCluster(appId, namespaceName, createBy) 方法,创建 AppNamespace 在 App 下,每个 Cluster 的 Namespace 对象。代码如下:

注意这里每次都调用了namespaceService.save()方法,该方法会保存Namespace。
在 apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.NamespaceService ,提供 Namespace 的 Service 逻辑给 Admin Service 和 Config Service 。
#save(Namespace) 方法,保存 Namespace 对象到数据库中。代码如下:
  1. @Transactional
  2. public Namespace save(Namespace entity) {
  3.   // 判断是否已经存在。若是,抛出 ServiceException 异常。
  4.   if (!isNamespaceUnique(entity.getAppId(), entity.getClusterName(), entity.getNamespaceName())) {
  5.     throw new ServiceException("namespace not unique");
  6.   }
  7.   // 保护代码,避免 Namespace 对象中,已经有 id 属性。
  8.   entity.setId(0);//protection
  9.   // 保存 Namespace 到数据库
  10.   Namespace namespace = namespaceRepository.save(entity);
  11.   // 记录 Audit 到数据库中
  12.   auditService.audit(Namespace.class.getSimpleName(), namespace.getId(), Audit.OP.INSERT,
  13.                      namespace.getDataChangeCreatedBy());
  14.   return namespace;
  15. }
复制代码
4 Apollo客户端

我们接下来分析一下Apollo客户端是如何获取Apollo配置信息的。
4.1 Spring扩展

我们要想实现Apollo和Spring无缝整合,需要在Spring容器刷新之前,从Apollo服务器拉取配置文件,并注入到Spring容器指定变量中,此时可以利用ApplicationContextInitializer对象。
ConfigurableApplicationContext:可以操作配置文件信息,代码如下:
  1. public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
  2.     /**
  3.      * 应用上下文配置时,这些符号用于分割多个配置路径
  4.      */
  5.     String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
  6.     /**
  7.      * Environment类在容器中实例的名字
  8.      */
  9.     String ENVIRONMENT_BEAN_NAME = "environment";
  10.     /**
  11.      * System系统变量在容器中对应的Bean的名字
  12.      */
  13.     String SYSTEM_PROPERTIES_BEAN_NAME = "systemProperties";
  14.     /**
  15.      * System 环境变量在容器中对应的Bean的名字
  16.      */
  17.     String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment";
  18.     /**
  19.      * 设置容器的Environment变量:可以利用当前对象ConfigurableApplicationContext实现对变量的配置
  20.      */
  21.     void setEnvironment(ConfigurableEnvironment environment);
  22.     /**
  23.      * 此方法一般在读取应用上下文配置的时候调用,用以向此容器中增加BeanFactoryPostProcessor。
  24.      * 增加的Processor会在容器refresh的时候使用。
  25.      */
  26.     void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
  27.     /**
  28.      * 向容器增加一个ApplicationListener,增加的Listener用于发布上下文事件如refresh和shutdown等
  29.      * 需要注意的是,如果此上下文还没有启动,那么在此注册的Listener将会在上下文refresh的时候,全部被调用
  30.      * 如果上下文已经是active状态的了,就会在multicaster中使用
  31.      */
  32.     void addApplicationListener(ApplicationListener<?> listener);
  33.     /**
  34.      * 加载资源配置文件(XML、properties,Whatever)。
  35.      * 由于此方法是一个初始化方法,因此如果调用此方法失败的情况下,要将其已经创建的Bean销毁。
  36.      * 换句话说,调用此方法以后,要么所有的Bean都实例化好了,要么就一个都没有实例化
  37.      */
  38.     void refresh() throws BeansException, IllegalStateException;
  39. }
复制代码
ApplicationContextInitializer是Spring框架原有的东西,这个类的主要作用就是在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext做refresh之前,允许我们对ConfiurableApplicationContext的实例做进一步的设置和处理。
ApplicationContextInitializer:代码如下
  1. public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
  2.     /**
  3.      * 容器刷新之前调用该放啊
  4.      */
  5.     void initialize(C applicationContext);
  6. }
复制代码
4.2 Apollo扩展Spring

Apollo利用Spring扩展机制实现了先从Apollo加载配置,并解析配置,再将数据添加到ConfigurableApplicationContext中,从而实现配置有限加载:
  1. public class ApolloApplicationContextInitializer implements
  2.     ApplicationContextInitializer<ConfigurableApplicationContext> , EnvironmentPostProcessor, Ordered {
  3.   @Override
  4.   public void initialize(ConfigurableApplicationContext context) {
  5.     //从ConfigurableApplicationContext获取Environment
  6.     ConfigurableEnvironment environment = context.getEnvironment();
  7.     //初始化加载
  8.     initialize(environment);
  9.   }
  10.   /**
  11.    * Initialize Apollo Configurations Just after environment is ready.
  12.    *
  13.    * @param environment
  14.    */
  15.   protected void initialize(ConfigurableEnvironment environment) {
  16.     if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
  17.       //already initialized
  18.       return;
  19.     }
  20.     String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
  21.     logger.debug("Apollo bootstrap namespaces: {}", namespaces);
  22.     //获取所有namespace,也就是apollo.bootstrap.namespaces的值
  23.     List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
  24.     CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
  25.     //循环所有namespace获取每个namespace的值
  26.     for (String namespace : namespaceList) {
  27.       //ConfigServiceLocator.updateConfigServices执行http请求获取数据
  28.       Config config = ConfigService.getConfig(namespace);
  29.       composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
  30.     }
  31.     //将数据添加到environment中
  32.     environment.getPropertySources().addFirst(composite);
  33.   }
  34. }
复制代码
4.3 数据同步
  1. ApolloApplicationContextInitializer.initialize():
  2.         for (String namespace : namespaceList) {
  3.       //调用->DefaultConfigManager.getConfig()
  4.       Config config = ConfigService.getConfig(namespace);
  5.       System.out.println(namespace+"-->"+config);
  6.       composite.addPropertySource(x);
  7.     }
  8. DefaultConfigManager.getConfig():
  9.         //为每个命名空间创建(获取)配置文件。调用->DefaultConfigFactory.create()
  10.         config = factory.create(namespace);
  11. DefaultConfigFactory.create():
  12.         //RemoteConfigRepository核心代码
  13.         createLocalConfigRepository(namespace)  ↓
  14.     return new RemoteConfigRepository(namespace)
  15.         
  16. RemoteConfigRepository.RemoteConfigRepository():
  17.         //1:同步数据
  18.         AbstractConfigRepository.trySync()->AbstractConfigRepository.sync()
  19.         //2:为每个命名空间创建定时任务,定时同步配置,默认5min更新1次
  20.         RemoteConfigRepository.schedulePeriodicRefresh()->AbstractConfigRepository.trySync()
  21.         //3:为每个命名空间创建轮询任务,轮询更新集群配置
  22.         RemoteConfigRepository.scheduleLongPollingRefresh()
复制代码
4.4 @ApolloConfigChangeListener

@ApolloConfigChangeListener注解是监听注解,当Apollo配置文件发生变更时,用该注解标注的方法会立刻得到通知。我们来看下方法:

该注解涉及到时间对象ConfigChangeEvent,该对象信息如下:
  1. public class ConfigChangeEvent {
  2.   //命名空间
  3.   private final String m_namespace;
  4.   //变更数据
  5.   private final Map<String, ConfigChange> m_changes;
  6. }
复制代码
上面变更数据用到了一个对象记录ConfigChange,源码如下:
  1. public class ConfigChange {
  2.   //命名空间
  3.   private final String namespace;
  4.   //属性名字
  5.   private final String propertyName;
  6.   //原始值
  7.   private String oldValue;
  8.   //新值
  9.   private String newValue;
  10.   //操作类型
  11.   private PropertyChangeType changeType;
  12. }
复制代码
1)监听器添加
ApolloAnnotationProcessor前置拦截器,为每个namespace添加监听器:
  1. /***
  2. * 方法处理
  3. * @param bean
  4. * @param beanName
  5. * @param method
  6. */
  7. @Override
  8. protected void processMethod(final Object bean, String beanName, final Method method) {
  9.   //检查该方法是否有@ApolloConfigChangeListener注解
  10.   ApolloConfigChangeListener annotation = AnnotationUtils
  11.       .findAnnotation(method, ApolloConfigChangeListener.class);
  12.   //没有就直接返回
  13.   if (annotation == null) {
  14.     return;
  15.   }
  16.   //获取参数类型集合
  17.   Class<?>[] parameterTypes = method.getParameterTypes();
  18.   Preconditions.checkArgument(parameterTypes.length == 1,
  19.       "Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length,
  20.       method);
  21.   Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
  22.       "Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0],
  23.       method);
  24.   //暴力破解
  25.   ReflectionUtils.makeAccessible(method);
  26.   //获取命名空间
  27.   String[] namespaces = annotation.value();
  28.   //获取要监听的key
  29.   String[] annotatedInterestedKeys = annotation.interestedKeys();
  30.   //获取要监听的key的前缀集合
  31.   String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
  32.   //创建监听
  33.   ConfigChangeListener configChangeListener = new ConfigChangeListener() {
  34.     @Override
  35.     public void onChange(ConfigChangeEvent changeEvent) {
  36.       //执行方法调用
  37.       ReflectionUtils.invokeMethod(method, bean, changeEvent);
  38.     }
  39.   };
  40.   Set<String> interestedKeys = annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
  41.   Set<String> interestedKeyPrefixes = annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) : null;
  42.   // 给config设置listener
  43.   for (String namespace : namespaces) {
  44.     Config config = ConfigService.getConfig(namespace);
  45.     //为每个命名空间添加configChangeListener,当每个命名空间发生变化的时候,都会触发该configChangeListener执行
  46.     if (interestedKeys == null && interestedKeyPrefixes == null) {
  47.       config.addChangeListener(configChangeListener);
  48.     } else {
  49.       config.addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes);
  50.     }
  51.   }
  52. }
复制代码
2)监听器执行
监听器执行在执行同步发现数据变更的时候执行,其中RemoteConfigRepository.sync()例子如下:

本文由传智教育博学谷 - 狂野架构师教研团队发布
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力
转载请注明出处!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

种地

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表