关于apollo:AREX-Agent-如何实现-Apollo-配置中心-Mock

52次阅读

共计 5846 个字符,预计需要花费 15 分钟才能阅读完成。

背景

对于 AREX

AREX 是基于实在申请与数据的自动化回归测试平台,利用 Java Agent 和字节码加强技术,在生产环境中记录实在申请链路的入口和依赖的申请和响应数据,而后在测试环境中进行模仿申请回放,并逐个验证整个调用链路的逻辑正确性。AREX Agent 当初曾经反对了大部分开源组件的 Mock,本文将介绍 Agent 如何实现 Apollo 配置核心的 Mock。

对于 Apollo

Apollo(阿波罗)是一款牢靠的分布式配置管理核心,诞生于携程框架研发部,可能集中化治理利用不同环境、不同集群的配置,配置批改后可能实时推送到利用端。

以下是官网对 Apollo 根底模型的形容:

  1. 用户在配置核心对配置进行批改并公布;
  2. 配置核心告诉 Apollo 客户端有配置更新;
  3. Apollo 客户端从配置核心拉取最新的配置、更新本地配置并告诉到利用。

实现原理

下图简要形容了 Apollo 客户端的实现原理:

  1. 客户端和服务端放弃了一个长连贯,从而能第一工夫取得配置更新的推送。(通过 Http Long Polling 实现)
  2. 客户端还会定时从 Apollo 配置核心服务端拉取利用的最新配置。
  3. 客户端从 Apollo 配置核心服务端获取到利用的最新配置后,会保留在内存中

图片起源:https://www.apolloconfig.com/#/zh/design/apollo-design

开发过程

从上图可知 AREX 只须要反对 Apollo 客户端的录制和回放,即 Java 利用我的项目外部援用 apollo-client 的组件:

<dependency>
    <groupId>com.ctrip.framework.apollo</groupId>
    <artifactId>apollo-client</artifactId>
    <version>{apollo-client.version}</version>
</dependency>

通常,我的项目中应用 Apollo 的形式次要有以下三种:

  1. Spring Autowired 注解 configBean (外部还是应用 EnableApolloConfig 注解)
  2. 基于 Apollo 自带的注解 ApolloConfig,如代码中的 config 对象
  3. API 形式,如代码中的 config1 对象:

<!—->

@Autowired
ConfigBean configBean; // 第一种形式,外部基于 EnableApolloConfig 注解  

@ApolloConfig("TEST1.lucas")
private Config config; // 第二种形式

private Config config1; // 第三种形式,在代码中调用 getAppConfig 实例化

public void test() {config1 = ConfigService.getAppConfig();

    System.out.println("timeout="+config.getProperty("timeout", "0"));
    System.out.println("switch="+config.getBooleanProperty("switch", false));
    System.out.println("json="+config.getProperty("json", ""));
    System.out.println("white.list="+config1.getProperty("flight.change.white.list", ""));
    System.out.println("configBean="+configBean);
 
 // 监听 Apollo 配置变更
    ConfigChangeListener changeListener = changeEvent -> {System.out.println("Changes for namespace:" + changeEvent.getNamespace());
    };
    config.addChangeListener(changeListener);
}

@Component
@Configuration
@EnableApolloConfig("TEST1.sofia")
public class ConfigBean {@Value("${age:0}")
    int age;

    @Value("${name:}")
    String name;

    @ApolloJsonValue("${resume:[]}")
    private List<JsonBean> jsonBean;

}

如果 AREX 须要实现 Apollo 的录制和回放就要 兼容这 3 种应用形式,通过查看 Apollo 源码发现前两种基于注解 EnableApolloConfigApolloConfig 和最初一种调用 API 的形式底层都是通过 ConfigService.getAppConfig() 创立的实例,也就是说底层 API 是共用的,这样咱们就能够润饰这些 Apollo 底层的办法插入 AREX 的字节码,达到录制和回放的目标。

录制实现

Apollo 里所有的配置项是依据 Namespace 辨别的,咱们要获取所有的配置实例,即拿到所有 Namespace 对应的 config instance 能力录制。进一步查看 apollo-client 源码发现,config instance 都保护在 DefaultConfigManager 类的 Mapm_configs 里。

但须要思考以下几个问题:

  1. m_configs 属性是 private 的,且没有相干 API 能够获取到;
  2. 这个实例创立后,在业务运行期间很少会调用该类,所以通过惯例的 AREX Mock 形式可能无奈获取到这个 m_configs
  3. 拿到 m_configs 实例后还须要获取 Config 类中的 m_configProperties,这外面才是真正的配置数据。

UML 依赖如下图:

所以衡量下来应用反射的形式获取所有的配置并录制(只有第一次启动和配置产生变更了才通过反射录制,频率很低)。

另外一个须要思考的点就是录制的机会,比方下面代码展现的我的项目中应用 Apollo 的形式中的第 3 种,在业务接口 test() 中才创立 config1 实例:

Config config1 = ConfigService.getAppConfig()

这种能够了解为增量配置实例(比照前两种注解的形式在我的项目启动时已创立好的全量配置实例),所以咱们在录制的时候须要思考这两种形式都要录制到,目前的做法是在申请完主入口 servlet/dubbo 接口,返回后果前,即 postHandle 后置点进行录制,这样不论是哪种形式创立的 config 实例咱们都能获取到并录制下来。(具体实现参考:ApolloServletV3RequestHandler#postHandle)

如果录制期间 Apollo 配置产生了变更,咱们能够通过在 Apollo 源码:com.ctrip.framework.apollo.internals.DefaultConfig#updateAndCalcConfigChanges 办法退出润饰代码,监听变更事件,从新关上咱们的录制开关,这样就能够在下次录制时录制到新的配置。(具体实现参考:ApolloDefaultConfigInstrumentation&dollar;UpdateAdvice)

AREX 在录制时会生成一个版本号,用来辨别不同时间段内录制的这一批用例 (Case) 属于哪个版本号,即起到一个批次的概念,如上面时间轴所示:

回放实现

回放的实现能够参考录制的实现,通过反射给 m_configProperties 赋值,应用 Mock 的配置笼罩掉实在的配置。

但同样有以下几个问题须要思考:

  1. 如何触发利用设置的配置变更监听办法,如下面 Apollo 应用形式里的 changeListener 办法;
  2. 回放期间,Apollo 长轮询获取配置变更后可能笼罩掉咱们回放的配置,须要防止这种状况;
  3. 回放多个版本的配置如何保障配置数据的正确性;
  4. 回放完结后如何还原回原来的配置。

基于以上几点,如果还应用录制的实现形式来做回放是不全面的,无奈满足这些非凡场景。

咱们通过查看源码后确定最终的实现计划是通过润饰 com.ctrip.framework.apollo.internals.RemoteConfigRepository#loadApolloConfig 办法,在申请服务器配置前间接返回咱们 Mock 的配置数据,这样就能利用 Apollo 现有的机制触发残缺的配置更新流程,也就达到了咱们回放的目标。当然触发回放是调用 com.ctrip.framework.apollo.internals.RemoteConfigRepository#syncloadApolloConfig

解决方案如下:

  1. 如果在回放过程中则不会调用实在的 Apollo-Server 服务,间接返回 Mock 的配置;
  2. 如果回放完结后不再 Mock 该办法(不回放超过 1 分钟则认为回放配置完结),执行失常的逻辑,即应用实在的配置。

流程图如下所示:

因为 Apollo 的长轮询始终在运行中,如果回放完结,此时 Apollo 发现服务器的配置和咱们 AREX 回放的配置不统一则会触发更新本地配置的操作,达到还原的目标。(具体实现参考:ApolloConfigHelper#replayAllConfigs)

另外下面列的第 2 点问题:如何保障回放多个版本的配置数据正确性?

参考 录制实现 中的时间轴图,AREX 在第一次(我的项目启动时)和后续配置产生变更时才录制配置,此时生成的版本号(UUID)也会作为回放时版本号应用,即如果录制了多个版本号,那回放时也是依照不同的版本号顺次做的回放,也就是说通过 AREX 生成的版本号来辨别不同版本的配置。实现形式是在每次回放配置时,在结构返回的 Apollo 配置实体 com.ctrip.framework.apollo.core.dto.ApolloConfig 类后,设置 releaseKey 属性为咱们 AREX 的版本号,这样就能够保障回放多个版本的配置数据正确性。(releaseKey 是 Apollo Client 和 Server 端交互的关键字段,Server 端依据此字段判断配置是否和 Client 统一,不统一则会返回一个新的 releaseKey 值,统一则返回 304 状态)具体实现参考:ApolloConfigHelper#getReplayConfig

版本差别

另外还须要留神润饰的 apollo-client 不同版本之间源码的差别,有些办法或类在不同的版本会有一些差别,咱们在决定润饰底层办法前最难看下不同版本之间的差别,否则可能因为用户我的项目应用的 apollo-client 版本和 arex-agent 润饰的版本不一样而失败。

比方咱们润饰的上面这个 Apollo 办法 com.ctrip.framework.apollo.internals.DefaultConfig#updateAndCalcConfigChanges 别离在 1.0.0 和 1.2.0 两个版本的入参差别:

v1.0.0

v1.2.0

咱们在润饰这个办法时要兼容这种差别,能力让咱们插入的字节码在不同版本下都能失常工作,另外对于咱们要润饰组件的版本抉择倡议选低版本的,依照个别的开闭准则,尽量能做到兼容。

联调

这里的联调指的是跟 AREX Schedule Service 的回放联调,用户在页面点击回放按钮时,Schedule 会先依照录制时生成的所有配置版本号进行一次分组,即这个时间段内的所有用例分完组后再依据版本号,每次回放前先进行一次版本号的切换,即告知 arex-agent 须要回放 Apollo 的配置了。

这次版本切换的申请不作为实在的回放后果,仅仅是作为一次版本预热,等版本号对应的配置切换胜利后再进行惯例的回放流程,前面如果要切换其余的版本号时一样的流程,如下图所示:

同一批用例 (case) 的版本号一样,同一批次的只录制或回放一次。

配置版本号别离存在数据库的 RollingServletMockerRollingDubboProviderMockerRollingConfigFileMocker,能够先去入口表 RollingServletMocker/RollingDubboProviderMocker -> targetRequest -> attributes -> configBatchNo (录制的配置版本号),而后应用 configBatchNo 值关联到 RollingConfigFileMocker 表的 recordId,即可查出这个版本号录制的所有配置。

总结思考

以上就是如何在 Agent 中实现 Apollo 配置核心录制和回放的具体实现细节,总体来说 Apollo Config 的源码逻辑还是比拟清晰的,能够借鉴下它和服务端交互的实现形式,如长轮询 + 服务端推送的机制,另外 Apollo 对 Spring 的反对也做得很好,比方基于 BeanPostProcessor 机制,实现自定义注解初始化 Config 实例,以及通过继承 EnumerablePropertySource 类利用 Spring 现有的注解 @Value("${timeout:0}") 来实现读取自定义的配置,对业务方来说应用就会很不便,升高接入老本。

注意事项

  1. 回放期间如果有其余申请到这台机器,也会返回回放的 Apollo 配置,Apollo 的配置都是存在内存中共享的,所以回放期间最好不要申请这个机器的利用,待回放完结,配置会主动还原。
  2. Apollo 有 local 模式(com.ctrip.framework.apollo.spi.DefaultConfigFactory#createLocalConfigRepository),这个的应用场景很少,所以 AREX Agent 临时不思考反对这种模式,如果大家有应用 local 模式且须要录制回放的,能够在 Github 提交 Issue。
  3. AREX 生成的配置版本号 (UUID.randomUUID()) 是无序的,Schedule 如果依照版本号分组做预热,可能不反对排序,不过目前临时没有这种业务场景。

AREX 文档:http://arextest.com/zh-Hans/docs/intro/

AREX 官网:http://arextest.com/

AREX GitHub:https://github.com/arextest

AREX 官网 QQ 交换群:656108079

正文完
 0