如何理解携程架构部开源的配置中心Apollo

发布时间:2021-10-20 17:08:37 作者:iii
来源:亿速云 阅读:166

这篇文章主要讲解了“如何理解携程架构部开源的配置中心Apollo”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何理解携程架构部开源的配置中心Apollo”吧!

为什么要使用配置中心

随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关、参数的配置、服务器的地址……

对程序配置的期望值也越来越高:配置修改后实时生效,灰度发布,分环境、分集群管理配置,完善的权限、审核机制……

在这样的大环境下,传统的通过配置文件、数据库等方式已经越来越无法满足开发人员对配置管理的需求。

想必大家都深有体会,我们做的项目都伴随着各种配置文件,而且总是本地配置一套配置文件,测试服配置一套,正式服配置一套,有时候一不小心就改错了,挨骂是小事,扣绩效那可就闹大了。

而且每当项目发布的时候,配置文件也会被打包进去,也就是配置文件会跟着项目一起发布。然后每次出现问题需要我们修改配置文件的时候,我们总是得先在本地修改,然后重新发布才能让新的配置生效。

当请求压力越来越大,你的项目也会从 1 个节点变成多个节点,这个时候如果配置需要发生变化,对应的修改操作也是相同的,只需要在项目中修改一次即可,但对于发布操作工作量就比之前大了很多,因为要发布多个节点。

修改这些配置,增加的发布的工作量降低了整体的工作效率,为了能够提升工作效率,配置中心应运而生了,我们可以将配置统一存放在配置中心来进行管理。

总体而言在没有引入配置中心之前,我们都会面临以下问题:

  1. 配置散乱格式不标准,有的用 properties 格式,有的用 xml 格式,还有的存 DB,团队倾向自造轮子,做法五花八门。

  2. 主要采用本地静态配置,配置修改麻烦,配置修改一般需要经过一个较长的测试发布周期。在分布式微服务环境下,当服务实例很多时,修改配置费时费力。

  3. 易引发生产事故,这个是我亲身经历,之前在一家互联网公司,有团队在发布的时候将测试环境的配置带到生产上,引发百万级资损事故。

  4. 配置缺乏安全审计和版本控制功能,谁改的配置?改了什么?什么时候改的?无从追溯,出了问题也无法及时回滚。

  5. 增加了运维小哥哥的工作量,极大的损害了运维小哥哥和开发小哥哥的基情。

到底什么是配置中心

配置中心就是把项目中各种个样的配置、参数、开关,全部都放到一个集中的地方进行统一管理,并提供一套标准的接口。当各个服务需要获取配置的时候,就来配置中心的接口拉取。当配置中心中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。

Apollo 简介

Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。

Apollo支持4个维度管理Key-Value格式的配置(下面会详细说明):

1、application (应用)
2、environment (环境)

3、cluster (集群)
4、namespace (命名空间)

什么是配置

既然Apollo定位于配置中心,那么在这里有必要先简单介绍一下什么是配置。

按照我们的理解,配置有以下几个属性:

为什么使用ApolloApollo有哪些特征

正是基于配置的特殊性,所以Apollo从设计之初就立志于成为一个有治理能力的配置发布平台,目前提供了以下的特性:

Apollo整体架构

首先我们来看看Applo的基本工作流程如下图所示

如何理解携程架构部开源的配置中心Apollo

1.用户在配置中心对配置进行修改并发布
2.配置中心通知Apollo客户端有配置更新
3.Apollo客户端从配置中心拉取最新的配置、更新本地配置并通知到应用

接下来我们来看看Apollo的整体架构图

如何理解携程架构部开源的配置中心Apollo

上图简要描述了Apollo的总体设计,我们可以从下往上看:

Why Eureka

为什么我们采用Eureka作为服务注册中心,而不是使用传统的zk、etcd呢?我大致总结了一下,有以下几方面的原因:

各模块概要介绍

Config Service

Admin Service

Meta Server

Eureka

Portal

Client

Apollo核心概念介绍

1、application

1、Apollo 客户端在运行时需要知道当前应用是谁,从而可以根据不同的应用来获取对应应用的配置。

2、每个应用都需要有唯一的身份标识,可以在代码中配置 app.id 参数来标识当前应用,Apollo 会根据此指来辨别当前应用。

2、environment

在实际开发中,我们的应用经常要部署在不同的环境中,一般情况下分为 开发、测试、生产 等等不同环境,不同环境中的配置也是不同的,在 Apollo 中默认提供了

四种环境:

FAT:功能测试环境

UAT:集成测试环境

DEV:开发环境

PRO:生产环境

在程序中如果想指定使用哪个环境,可以配置变量 env 的值为对应环境名称即可。

3、cluster

1、一个应用下不同实例的分组,比如典型的可以按照数据中心分,把上海机房的应用实例分为一个集群,把北京机房的应用实例分为另一个集群。

2、对不同的集群,同一个配置可以有不一样的值,比如说上面所指的两个北京、上海两个机房设置两个集群,都有 mysql 配置参数,其中参数中配置的地址是不一样的。

4、namespace

一个应用中不同配置的分组,可以简单地把 namespace 类比为不同的配置文件,不同类型的配置存放在不同的文件中,如数据库配置文件,RPC 配置文件等。

熟悉 SpringBoot 的都知道,SpringBoot 项目都有一个默认配置文件 application.yml,如果还想用多个配置,可以创建多个配置文件来存放不同的配置信息,通过

指定 spring.profiles.active 参数指定应用不同的配置文件。这里的 namespace 概念与其类似,将不同的配置放到不同的配置 namespace 中。

Namespace 分为两种权限,分别为:

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

Apollo实时发布配置

1. 配置发布后的实时推送设计

配置中心最重要的一个特性就是实时推送,正因为有这个特性,我们才可以依赖配置中心做很多事情。如图所示。

如何理解携程架构部开源的配置中心Apollo

如何理解携程架构部开源的配置中心Apollo

图 1 简要描述了配置发布的大致过程。

2. 发送 ReleaseMessage 的实现方式

ReleaseMessage 消息是通过 Mysql 实现了一个简单的消息队列。之所以没有采用消息中间件,是为了让 Apollo 在部署的时候尽量简单,尽可能减少外部依赖,如图所示。

如何理解携程架构部开源的配置中心Apollo

上图简要描述了发送 ReleaseMessage 的大致过程:

3. Config Service 通知客户端的实现方式

通知采用基于 Http 长连接实现,主要分为下面几个步骤:

4. 源码解析实时推送设计

Apollo 推送涉及的代码比较多,本教程就不做详细分析了,笔者把推送这里的代码稍微简化了下,给大家进行讲解,这样理解起来会更容易。

当然,这些代码比较简单,很多细节就不做考虑了,只是为了能够让大家明白 Apollo 推送的核心原理。

发送 ReleaseMessage 的逻辑我们就写一个简单的接口,用队列存储,测试的时候就调用这个接口模拟配置有更新,发送 ReleaseMessage 消息。具体代码如下所示。

@RestControllerpublic class NotificationControllerV2 implements ReleaseMessageListener {// 模拟配置更新, 向其中插入数据表示有更新public static Queue<String> queue = new LinkedBlockingDeque<>();  @GetMapping("/addMsg")  public String addMsg() {    queue.add("xxx");    return "success";  }}

消息发送之后,根据前面讲过的 Config Service 会启动一个线程定时扫描 ReleaseMessage 表,查看是否有新的消息记录,然后取通知客户端,在这里我们也会启动一个线程去扫描,具体代码如下所示。

@Componentpublic class ReleaseMessageScanner implements InitializingBean {  @Autowired  private NotificationControllerV2 configController;  @Override  public void afterPropertiesSet() throws Exception {    // 定时任务从数据库扫描有没有新的配置发布    new Thread(() -> {      for (;;) {        String result = NotificationControllerV2.queue.poll();        if (result != null) {        ReleaseMessage message = new ReleaseMessage();        message.setMessage(result);        configController.handleMessage(message);      }    }  }).start();  ;  }}

循环读取 NotificationControllerV2 中的队列,如果有消息的话就构造一个 Release-Message 的对象,然后调用 NotificationControllerV2 中的 handleMessage() 方法进行消息的处理。

ReleaseMessage 就一个字段,模拟消息内容,具体代码如下所示。

public class ReleaseMessage {  private String message;  public void setMessage(String message) {    this.message = message;  }  public String getMessage() {    return message;  }}

接下来,我们来看 handleMessage 做了哪些工作。

NotificationControllerV2 实现了 ReleaseMessageListener 接口,ReleaseMessageListener 中定义了 handleMessage() 方法,具体代码如下所示。

public interface ReleaseMessageListener {    void handleMessage(ReleaseMessage message);}

handleMessage 就是当配置发生变化的时候,发送通知的消息监听器。消息监听器在得到配置发布的信息后,会通知对应的客户端,具体代码如下所示。

@RestControllerpublic class NotificationControllerV2 implements ReleaseMessageListener {  private final Multimap<String, DeferredResultWrapper> deferredResults = Multimaps.synchronizedSetMultimap(HashMultimap.create());  @Override  public void handleMessage(ReleaseMessage message) {    System.err.println("handleMessage:" + message);    List<DeferredResultWrapper> results = Lists.newArrayList(deferredResults.get("xxxx"));    for (DeferredResultWrapper deferredResultWrapper : results) {      List<ApolloConfigNotification> list = new ArrayList<>();      list.add(new ApolloConfigNotification("application", 1));      deferredResultWrapper.setResult(list);    }  }}

Apollo 的实时推送是基于 Spring DeferredResult 实现的,在 handleMessage() 方法中可以看到是通过 deferredResults 获取 DeferredResult,deferredResults 就是第一行的 Multimap,Key 其实就是消息内容,Value 就是 DeferredResult 的业务包装类 DeferredResultWrapper,我们来看下 DeferredResultWrapper 的代码,代码如下所示。

public class DeferredResultWrapper {  private static final long TIMEOUT = 60 * 1000;// 60 seconds  private static final ResponseEntity<List<ApolloConfigNotification>> NOT_MODIFIED_RESPONSE_LIST = new ResponseEntity<>(HttpStatus.NOT_MODIFIED);  private DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> result;  public DeferredResultWrapper() {    result = new DeferredResult<>(TIMEOUT, NOT_MODIFIED_RESPONSE_LIST);  }  public void onTimeout(Runnable timeoutCallback) {    result.onTimeout(timeoutCallback);  }  public void onCompletion(Runnable completionCallback) {    result.onCompletion(completionCallback);  }  public void setResult(ApolloConfigNotification notification) {    setResult(Lists.newArrayList(notification));  }  public void setResult(List<ApolloConfigNotification> notifications) {    result.setResult(new ResponseEntity<>(notifications, HttpStatus.OK));  }  public DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> getResult() {    return result;  }}

通过 setResult() 方法设置返回结果给客户端,以上就是当配置发生变化,然后通过消息监听器通知客户端的原理,那么客户端是在什么时候接入的呢?具体代码如下。

@RestControllerpublic class NotificationControllerV2 implements ReleaseMessageListener {// 模拟配置更新, 向其中插入数据表示有更新  public static Queue<String> queue = new LinkedBlockingDeque<>();  private final Multimap<String, DeferredResultWrapper> deferredResults = Multimaps.synchronizedSetMultimap(HashMultimap.create());  @GetMapping("/getConfig")  public DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> getConfig() {    DeferredResultWrapper deferredResultWrapper = new DeferredResultWrapper();    List<ApolloConfigNotification> newNotifications = getApolloConfigNotifications();    if (!CollectionUtils.isEmpty(newNotifications)) {      deferredResultWrapper.setResult(newNotifications);    } else {      deferredResultWrapper.onTimeout(() -> {    System.err.println("onTimeout");    });  deferredResultWrapper.onCompletion(() -> {    System.err.println("onCompletion");  });  deferredResults.put("xxxx", deferredResultWrapper);  }  return deferredResultWrapper.getResult();  }  private List<ApolloConfigNotification> getApolloConfigNotifications() {    List<ApolloConfigNotification> list = new ArrayList<>();    String result = queue.poll();    if (result != null) {    list.add(new ApolloConfigNotification("application", 1));  }    return list;  }}

NotificationControllerV2 中提供了一个 /getConfig 的接口,客户端在启动的时候会调用这个接口,这个时候会执行 getApolloConfigNotifications() 方法去获取有没有配置的变更信息,如果有的话证明配置修改过,直接就通过 deferredResultWrapper.setResult(newNotifications) 返回结果给客户端,客户端收到结果后重新拉取配置的信息覆盖本地的配置。

如果 getApolloConfigNotifications() 方法没有返回配置修改的信息,则证明配置没有发生修改,那就将 DeferredResultWrapper 对象添加到 deferredResults 中,等待后续配置发生变化时消息监听器进行通知。

同时这个请求就会挂起,不会立即返回,挂起是通过 DeferredResultWrapper 中的下面这部分代码实现的,具体代码如下所示。

private static final long TIMEOUT = 60 * 1000; // 60 secondsprivate static final ResponseEntity<List<ApolloConfigNotification>> NOT_MODIFIED_RESPONSE_LIST           = new ResponseEntity<>(HttpStatus.NOT_MODIFIED);private DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> result;public DeferredResultWrapper() {  result = new DeferredResult<>(TIMEOUT, NOT_MODIFIED_RESPONSE_LIST);}

在创建 DeferredResult 对象的时候指定了超时的时间和超时后返回的响应码,如果 60s 内没有消息监听器进行通知,那么这个请求就会超时,超时后客户端收到的响应码就是 304。

整个 Config Service 的流程就走完了,接下来我们来看一下客户端是怎么实现的,我们简单地写一个测试类模拟客户端注册,具体代码如下所示。

public class ClientTest {    public static void main(String[] args) {        reg();    }      private static void reg() {        System.err.println("注册");        String result = request("http://localhost:8081/getConfig");        if (result != null) {        // 配置有更新, 重新拉取配置        // ......        }        // 重新注册        reg();    }      private static String request(String url) {        HttpURLConnection connection = null;        BufferedReader reader = null;        try {            URL getUrl = new URL(url);            connection = (HttpURLConnection) getUrl.openConnection();            connection.setReadTimeout(90000);            connection.setConnectTimeout(3000);            connection.setRequestMethod("GET");            connection.setRequestProperty("Accept-Charset", "utf-8");            connection.setRequestProperty("Content-Type", "application/json");            connection.setRequestProperty("Charset", "UTF-8");            System.out.println(connection.getResponseCode());            if (200 == connection.getResponseCode()) {                reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));                StringBuilder result = new StringBuilder();                String line = null;                while ((line = reader.readLine()) != null) {                    result.append(line);                }                System.out.println("结果 " + result);                return result.toString();            }        } catch (IOException e) {            e.printStackTrace();        } finally {            if (connection != null) {                connection.disconnect();            }        }        return null;    }}

首先启动 /getConfig 接口所在的服务,然后启动客户端,然后客户端就会发起注册请求,如果有修改直接获取到结果,则进行配置的更新操作。如果无修改,请求会挂起,这里客户端设置的读取超时时间是 90s,大于服务端的 60s 超时时间。
每次收到结果后,无论是有修改还是无修改,都必须重新进行注册,通过这样的方式就可以达到配置实时推送的效果。
我们可以调用之前写的 /addMsg 接口来模拟配置发生变化,调用之后客户端就能马上得到返回结果。

Apollo客户端设计

如何理解携程架构部开源的配置中心Apollo

上图简要描述了Apollo客户端的实现原理:

  1. 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。(通过Http Long Polling实现)

  2. 客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。

  3. 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中

  4. 客户端会把从服务端获取到的配置在本地文件系统缓存一份(在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置)

  5. 应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知

Apollo客户端用法

Apollo支持API方式和Spring整合方式,该怎么选择用哪一种方式?

1、API使用方式

API方式是最简单、高效使用Apollo配置的方式,不依赖Spring框架即可使用。

获取默认namespace的配置(application)

Config config = ConfigService.getAppConfig(); //config instance is singleton for each namespace and is never nullString someKey = "someKeyFromDefaultNamespace";String someDefaultValue = "someDefaultValueForTheKey";String value = config.getProperty(someKey, someDefaultValue);

通过上述的config.getProperty可以获取到someKey对应的实时最新的配置值。

另外,配置值从内存中获取,所以不需要应用自己做缓存。

监听配置变化事件

监听配置变化事件只在应用真的关心配置变化,需要在配置变化时得到通知时使用,比如:数据库连接串变化后需要重建连接等。

如果只是希望每次都取到最新的配置的话,只需要按照上面的例子,调用config.getProperty即可。

Config config = ConfigService.getAppConfig(); //config instance is singleton for each namespace and is never nullconfig.addChangeListener(new ConfigChangeListener() {    @Override    public void onChange(ConfigChangeEvent changeEvent) {        System.out.println("Changes for namespace " + changeEvent.getNamespace());        for (String key : changeEvent.changedKeys()) {            ConfigChange change = changeEvent.getChange(key);            System.out.println(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()));        }    }});

获取公共Namespace的配置

String somePublicNamespace = "CAT";Config config = ConfigService.getConfig(somePublicNamespace); //config instance is singleton for each namespace and is never nullString someKey = "someKeyFromPublicNamespace";String someDefaultValue = "someDefaultValueForTheKey";String value = config.getProperty(someKey, someDefaultValue);

获取非properties格式namespace的配置

1.yaml/yml格式的namespace

apollo-client 1.3.0版本开始对yaml/yml做了更好的支持,使用起来和properties格式一致。

Config config = ConfigService.getConfig("application.yml");String someKey = "someKeyFromYmlNamespace";String someDefaultValue = "someDefaultValueForTheKey";String value = config.getProperty(someKey, someDefaultValue);
2.非yaml/yml格式的namespace

获取时需要使用ConfigService.getConfigFile接口并指定Format,如ConfigFileFormat.XML

String someNamespace = "test";ConfigFile configFile = ConfigService.getConfigFile("test", ConfigFileFormat.XML);String content = configFile.getContent();

Spring整合方式

配置

Apollo也支持和Spring整合(Spring 3.1.1+),只需要做一些简单的配置就可以了。

Apollo目前既支持比较传统的基于XML的配置,也支持目前比较流行的基于Java(推荐)的配置。

如果是Spring Boot环境,建议参照3.2.1.3 Spring Boot集成方式(推荐)配置。

需要注意的是,如果之前有使用org.springframework.beans.factory.config.PropertyPlaceholderConfigurer的,请替换成org.springframework.context.support.PropertySourcesPlaceholderConfigurer。Spring 3.1以后就不建议使用PropertyPlaceholderConfigurer了,要改用PropertySourcesPlaceholderConfigurer。

如果之前有使用<context:property-placeholder>,请注意xml中引入的spring-context.xsd版本需要是3.1以上(一般只要没有指定版本会自动升级的),建议使用不带版本号的形式引入,如:http://www.springframework.org/schema/context/spring-context.xsd

注1:yaml/yml格式的namespace从1.3.0版本开始支持和Spring整合,注入时需要填写带后缀的完整名字,比如application.yml

注2:非properties、非yaml/yml格式(如xml,json等)的namespace暂不支持和Spring整合。

基于XML的配置

注:需要把apollo相关的xml namespace加到配置文件头上,不然会报xml语法错误。

1.注入默认namespace的配置到Spring中

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:apollo="http://www.ctrip.com/schema/apollo"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">    <!-- 这个是最简单的配置形式,一般应用用这种形式就可以了,用来指示Apollo注入application namespace的配置到Spring环境中 -->    <apollo:config/>    <bean class="com.ctrip.framework.apollo.spring.TestXmlBean">        <property name="timeout" value="${timeout:100}"/>        <property name="batch" value="${batch:200}"/>    </bean></beans>

2.注入多个namespace的配置到Spring中

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:apollo="http://www.ctrip.com/schema/apollo"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">    <!-- 这个是最简单的配置形式,一般应用用这种形式就可以了,用来指示Apollo注入application namespace的配置到Spring环境中 -->    <apollo:config/>    <!-- 这个是稍微复杂一些的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring环境中 -->    <apollo:config namespaces="FX.apollo,application.yml"/>    <bean class="com.ctrip.framework.apollo.spring.TestXmlBean">        <property name="timeout" value="${timeout:100}"/>        <property name="batch" value="${batch:200}"/>    </bean></beans>

3.注入多个namespace,并且指定顺序

Spring的配置是有顺序的,如果多个property source都有同一个key,那么最终是顺序在前的配置生效。

apollo:config如果不指定order,那么默认是最低优先级。

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:apollo="http://www.ctrip.com/schema/apollo"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">    <apollo:config order="2"/>    <!-- 这个是最复杂的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring环境中,并且顺序在application前面 -->    <apollo:config namespaces="FX.apollo,application.yml" order="1"/>    <bean class="com.ctrip.framework.apollo.spring.TestXmlBean">        <property name="timeout" value="${timeout:100}"/>        <property name="batch" value="${batch:200}"/>    </bean></beans>

基于Java的配置(推荐)

相对于基于XML的配置,基于Java的配置是目前比较流行的方式。

注意@EnableApolloConfig要和@Configuration一起使用,不然不会生效。

1.注入默认namespace的配置到Spring中

//这个是最简单的配置形式,一般应用用这种形式就可以了,用来指示Apollo注入application namespace的配置到Spring环境中@Configuration@EnableApolloConfigpublic class AppConfig {  @Bean  public TestJavaConfigBean javaConfigBean() {    return new TestJavaConfigBean();  }}

2.注入多个namespace的配置到Spring中

@Configuration@EnableApolloConfigpublic class SomeAppConfig {  @Bean  public TestJavaConfigBean javaConfigBean() {    return new TestJavaConfigBean();  }}//这个是稍微复杂一些的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring环境中@Configuration@EnableApolloConfig({"FX.apollo", "application.yml"})public class AnotherAppConfig {}

3.注入多个namespace,并且指定顺序

//这个是最复杂的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring环境中,并且顺序在application前面@Configuration@EnableApolloConfig(order = 2)public class SomeAppConfig {  @Bean  public TestJavaConfigBean javaConfigBean() {    return new TestJavaConfigBean();  }}@Configuration@EnableApolloConfig(value = {"FX.apollo", "application.yml"}, order = 1)public class AnotherAppConfig {}

Spring Boot集成方式(推荐)

Spring Boot除了支持上述两种集成方式以外,还支持通过application.properties/bootstrap.properties来配置,该方式能使配置在更早的阶段注入,比如使用@ConditionalOnProperty的场景或者是有一些spring-boot-starter在启动阶段就需要读取配置做一些事情(如dubbo-spring-boot-project),所以对于Spring Boot环境建议通过以下方式来接入Apollo(需要0.10.0及以上版本)。

使用方式很简单,只需要在application.properties/bootstrap.properties中按照如下样例配置即可。

注入默认application namespace的配置示例

  #will inject 'application' namespace in bootstrap phase  apollo.bootstrap.enabled = true

注入非默认application namespace或多个namespace的配置示例

  apollo.bootstrap.enabled = true  # will inject 'application', 'FX.apollo' and 'application.yml' namespaces in bootstrap phase  apollo.bootstrap.namespaces = application,FX.apollo,application.yml

将Apollo配置加载提到初始化日志系统之前(1.2.0+)

从1.2.0版本开始,如果希望把日志相关的配置(如logging.level.root=infologback-spring.xml中的参数)也放在Apollo管理,那么可以额外配置apollo.bootstrap.eagerLoad.enabled=true来使Apollo的加载顺序放到日志系统加载之前,不过这会导致Apollo的启动过程无法通过日志的方式输出(因为执行Apollo加载的时候,日志系统压根没有准备好呢!所以在Apollo代码中使用Slf4j的日志输出便没有任何内容),更多信息可以参考PR 1614。参考配置示例如下:

  # will inject 'application' namespace in bootstrap phase  apollo.bootstrap.enabled = true  # put apollo initialization before logging system initialization  apollo.bootstrap.eagerLoad.enabled=true

Spring Placeholder的使用

Spring应用通常会使用Placeholder来注入配置,使用的格式形如${someKey:someDefaultValue},如${timeout:100}。冒号前面的是key,冒号后面的是默认值。

建议在实际使用时尽量给出默认值,以免由于key没有定义导致运行时错误。

从v0.10.0开始的版本支持placeholder在运行时自动更新,具体参见PR #972。

如果需要关闭placeholder在运行时自动更新功能,可以通过以下两种方式关闭:

1. 通过设置System Property apollo.autoUpdateInjectedSpringProperties,如启动时传入-Dapollo.autoUpdateInjectedSpringProperties=false

2.通过设置META-INF/app.properties中的apollo.autoUpdateInjectedSpringProperties属性,如

app.id=SampleAppapollo.autoUpdateInjectedSpringProperties=false

 XML使用方式

假设我有一个TestXmlBean,它有两个配置项需要注入:

public class TestXmlBean {  private int timeout;  private int batch;  public void setTimeout(int timeout) {    this.timeout = timeout;  }  public void setBatch(int batch) {    this.batch = batch;  }  public int getTimeout() {    return timeout;  }  public int getBatch() {    return batch;  }}

那么,我在XML中会使用如下方式来定义(假设应用默认的application namespace中有timeout和batch的配置项):

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:apollo="http://www.ctrip.com/schema/apollo"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">    <apollo:config/>    <bean class="com.ctrip.framework.apollo.spring.TestXmlBean">        <property name="timeout" value="${timeout:100}"/>        <property name="batch" value="${batch:200}"/>    </bean></beans>

 Java Config使用方式

假设我有一个TestJavaConfigBean,通过Java Config的方式还可以使用@Value的方式注入:

public class TestJavaConfigBean {  @Value("${timeout:100}")  private int timeout;  private int batch;  @Value("${batch:200}")  public void setBatch(int batch) {    this.batch = batch;  }  public int getTimeout() {    return timeout;  }  public int getBatch() {    return batch;  }}

在Configuration类中按照下面的方式使用(假设应用默认的application namespace中有timeoutbatch的配置项):

@Configuration@EnableApolloConfigpublic class AppConfig {  @Bean  public TestJavaConfigBean javaConfigBean() {    return new TestJavaConfigBean();  }}

ConfigurationProperties使用方式

Spring Boot提供了@ConfigurationProperties把配置注入到bean对象中。

Apollo也支持这种方式,下面的例子会把redis.cache.expireSecondsredis.cache.commandTimeout分别注入到SampleRedisConfig的expireSecondscommandTimeout字段中。

@ConfigurationProperties(prefix = "redis.cache")public class SampleRedisConfig {  private int expireSeconds;  private int commandTimeout;  public void setExpireSeconds(int expireSeconds) {    this.expireSeconds = expireSeconds;  }  public void setCommandTimeout(int commandTimeout) {    this.commandTimeout = commandTimeout;  }}

在Configuration类中按照下面的方式使用(假设应用默认的application namespace中有redis.cache.expireSecondsredis.cache.commandTimeout的配置项):

@Configuration@EnableApolloConfigpublic class AppConfig {  @Bean  public SampleRedisConfig sampleRedisConfig() {    return new SampleRedisConfig();  }}

需要注意的是,@ConfigurationProperties如果需要在Apollo配置变化时自动更新注入的值,需要配合使用EnvironmentChangeEvent或RefreshScope。相关代码实现,可以参考apollo-use-cases项目中的ZuulPropertiesRefresher.java和apollo-demo项目中的SampleRedisConfig.java以及SpringBootApolloRefreshConfig.java

Spring Annotation支持

Apollo同时还增加了几个新的Annotation来简化在Spring环境中的使用。

  1. @ApolloConfig

  2. @ApolloConfigChangeListener

  3. @ApolloJsonValue

使用样例如下:

public class TestApolloAnnotationBean {  @ApolloConfig  private Config config; //inject config for namespace application  @ApolloConfig("application")  private Config anotherConfig; //inject config for namespace application  @ApolloConfig("FX.apollo")  private Config yetAnotherConfig; //inject config for namespace FX.apollo  @ApolloConfig("application.yml")  private Config ymlConfig; //inject config for namespace application.yml  /**   * ApolloJsonValue annotated on fields example, the default value is specified as empty list - []   * <br />   * jsonBeanProperty=[{"someString":"hello","someInt":100},{"someString":"world!","someInt":200}]   */  @ApolloJsonValue("${jsonBeanProperty:[]}")  private List<JsonBean> anotherJsonBeans;  @Value("${batch:100}")  private int batch;  //config change listener for namespace application  @ApolloConfigChangeListener  private void someOnChange(ConfigChangeEvent changeEvent) {    //update injected value of batch if it is changed in Apollo    if (changeEvent.isChanged("batch")) {      batch = config.getIntProperty("batch", 100);    }  }  //config change listener for namespace application  @ApolloConfigChangeListener("application")  private void anotherOnChange(ConfigChangeEvent changeEvent) {    //do something  }  //config change listener for namespaces application, FX.apollo and application.yml  @ApolloConfigChangeListener({"application", "FX.apollo", "application.yml"})  private void yetAnotherOnChange(ConfigChangeEvent changeEvent) {    //do something  }  //example of getting config from Apollo directly  //this will always return the latest value of timeout  public int getTimeout() {    return config.getIntProperty("timeout", 200);  }  //example of getting config from injected value  //the program needs to update the injected value when batch is changed in Apollo using @ApolloConfigChangeListener shown above  public int getBatch() {    return this.batch;  }  private static class JsonBean{    private String someString;    private int someInt;  }}

在Configuration类中按照下面的方式使用:

@Configuration@EnableApolloConfigpublic class AppConfig {  @Bean  public TestApolloAnnotationBean testApolloAnnotationBean() {    return new TestApolloAnnotationBean();  }}

 已有配置迁移

很多情况下,应用可能已经有不少配置了,比如Spring Boot的应用,就会有bootstrap.properties/yml, application.properties/yml等配置。

在应用接入Apollo之后,这些配置是可以非常方便的迁移到Apollo的,具体步骤如下:

  1. 在Apollo为应用新建项目

  2. 在应用中配置好META-INF/app.properties

  3. 建议把原先配置先转为properties格式,然后通过Apollo提供的文本编辑模式全部粘帖到应用的application namespace,发布配置

  4. 如果原来是yml,想继续使用yml来编辑配置,那么可以创建私有的application.yml namespace,把原来的配置全部粘贴进去,发布配置

  5. 把原先的配置文件如bootstrap.properties/yml, application.properties/yml从项目中删除

如:

spring.application.name = reservation-serviceserver.port = 8080logging.level = ERROReureka.client.serviceUrl.defaultZone = http://127.0.0.1:8761/eureka/eureka.client.healthcheck.enabled = trueeureka.client.registerWithEureka = trueeureka.client.fetchRegistry = trueeureka.client.eurekaServiceUrlPollIntervalSeconds = 60eureka.instance.preferIpAddress = true

Apollo的高可用性设计


高可用是分布式系统架构设计中必须考虑的因素之一,它通常是指通过设计减少系统不能提供服务的时间。

Apollo 在高可用设计上下了很大的功夫,下面我们来简单的分析下:

1)某台Config Service 下线

无影响,Config Service 可用部署多个节点。

2)所有 Config Service 下线

所有 Config Service 下线会影响客户端的使用,无法读取最新的配置。可采用读取本地缓存的配置文件来过渡。

3)某台 Admin Service 下线

无影响,Admin Service 可用部署多个节点。

4)所有 Admin Service 下线

Admin Service 是服务于 Portal,所有 Admin Service 下线之后只会影响 Portal 的操作,不会影响客户端,客户端是依赖 Config Service。

5)某台 Portal 下线

Portal 可用部署多台,通过 Nginx 做负载,某台下线之后不影响使用。

6)全部 Portal 下线

对客户端读取配置是没有影响的,只是不能通过 Portal 去查看,修改配置。

7)数据库宕机

当配置的数据库宕机之后,对客户端是没有影响的,但是会导致 Portal 中无法更新配置。当客户端重启,这个时候如果需要重新拉取配置,就会有影响,可采取开启配置缓存的选项来避免数据库宕机带来的影响。

感谢各位的阅读,以上就是“如何理解携程架构部开源的配置中心Apollo”的内容了,经过本文的学习后,相信大家对如何理解携程架构部开源的配置中心Apollo这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. 携程基于Flink的实时特征平台
  2. 学习Apollo服务配置中心,与SpringBoot整合

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

apollo java

上一篇:如何自定义ForkJoinPool提升并行流 ParallelStream执行速度

下一篇:封装系统全局操作日志aop拦截且可打包给其他项目依赖

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》