环境SpringBoot 2

一、Apollo简介

项目组最近的项目都是使用springcloud微服务开发,整个微服务框架中分布式的系统服务、集群等等都非常的多。

每一个服务都有着自己的配置(包括参数配置、服务器地址配置、功能开关等都能),当配置需要修改的时候就显得异常的麻烦,传统的通过配置文件、数据库配置完全不能满足要求。

在这种情况下,诞生了很多的统一配置的服务,虽然springcloud有自己的config配置中心,但是和携程的apollo相比还是太弱了,对比之后我们使用了Apollo。

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

github上携程开发人员对Apollo做了特别细致的介绍,但是对于我们只使用springboot进行开发的项目来说,其实只用到了里面一小部分内容。

因此我主要将Apollo和Springboot相关的内容整理出来,本文首先对Apollo进行QuickStart简单入门介绍以及基于Springboot框架的使用。

1.1 Apollo配置中心基础模型

  1. 开发人员在配置中心对某些应用的配置(就是一些键值对的信息)进行修改
  2. 配置中心通知客户端进行配置更新(需要注册监听事件)
  3. Apollo客户端(应用程序)从配置中心拉取更新最新配置

1.2 Apollo的总体设计

上图是Apollo的总体设计,从下往上看:

  • ConfigService(Apollo集成在一个jar包中)提供配置的读取,推送功能,Apollo客户端(你的应用程序)从这儿读取配置。
  • AdminService(Apollo集成在一个jar包中)提供配置的修改、发布功能,Apollo Portal(开发人员登录的修改配置的系统)调用该服务。
  • ConfigService和AdminService都是多实例服务,需要将它们注册到Eureka中。
  • 在Eureka之上有一层MetaServer用于封装Eureka的服务发现接口。
  • Client和Portal通过域名访问MetaServer获取ConfigService和AdminService的服务列表(IP+Port),然后直接通过套接字访问服务。
  • 为了简化部署,实际上会把Configservice、Eureka和MetaServer部署在同一个JVM进程中。

1.3 Apollo配置分类

因为对于不同的应用,在不同的环境(开发、测试)、不同的集群(华东、华北)、不同的命名空间(例如springboot的application.property文件、你自己自定义的myappconfig.property),配置都有可能不同。

所以Apollo支持了4个维度管理Key-Value格式的配置(这些都是可以配置的):

  • application(应用)
    • 在springboot的application.property定义appid这个key的value,标识该类型的应用。
  • environment(4种环境)
    • DEV(开发环境)
    • FAT(功能测试)
    • UAT(验收测试)
    • PRO(生产环境)
  • cluster(集群)
  • namespace(命名空间,其实就是某个应用的不同配置文件)

同时,Apollo基于开源模式开发,开源地址:https://github.com/ctripcorp/apollo

1.4 Apollo特性

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

  • 统一管理不同环境、不同集群的配置
    • Apollo 提供了一个统一界面集中式管理不同环境(environment)、不同集群(cluster)、不同命名空间(namespace)的配置。
    • 同一份代码部署在不同的集群,可以有不同的配置,比如zk的地址等
    • 通过命名空间(namespace)可以很方便的支持多个不同应用共享同一份配置,同时还允许应用对共享的配置进行覆盖 
  • 配置修改实时生效(热发布)
    • 用户在Apollo修改完配置并发布后,客户端能实时(1秒)接收到最新的配置,并通知到应用程序
  • 版本发布管理
    • 所有的配置发布都有版本概念,从而可以方便地支持配置的回滚
  • 灰度发布
    • 支持配置的灰度发布,比如点了发布后,只对部分应用实例生效,等观察一段时间没问题后再推给所有应用实例
  • 权限管理、发布审核、操作审计
    • 应用和配置的管理都有完善的权限管理机制,对配置的管理还分为了编辑和发布两个环节,从而减少人为的错误。
    • 所有的操作都有审计日志,可以方便的追踪问题
  • 客户端配置信息监控
    • 可以在界面上方便地看到配置在被哪些实例使用
  • 提供Java和.Net原生客户端
    • 提供了Java和.Net的原生客户端,方便应用集成
    • 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便应用使用(需要Spring 3.1.1+)
    • 同时提供了Http接口,非Java和.Net应用也可以方便的使用
  • 提供开放平台API
    • Apollo自身提供了比较完善的统一配置管理界面,支持多环境、多数据中心配置管理、权限、流程治理等特性。
    • 不过Apollo出于通用性考虑,对配置的修改不会做过多限制,只要符合基本的格式就能够保存。
    • 在我们的调研中发现,对于有些使用方,它们的配置可能会有比较复杂的格式,而且对输入的值也需要进行校验后方可保存,如检查数据库、用户名和密码是否匹配。
    • 对于这类应用,Apollo支持应用方通过开放接口在Apollo进行配置的修改和发布,并且具备完善的授权和权限控制
  • 部署简单
    • 配置中心作为基础服务,可用性要求非常高,这就要求Apollo对外部依赖尽可能地少
    • 目前唯一的外部依赖是MySQL,所以部署非常简单,只要安装好Java和MySQL就可以让Apollo跑起来
    • Apollo还提供了打包脚本,一键就可以生成所有需要的安装包,并且支持自定义运行时参数

 1.5 配置获取规则

【本节仅当应用自定义了集群或namespace才需要,如无相关需求,可以跳过本节】

在有了cluster概念后,配置的规则就显得重要了。

比如应用部署在A机房,但是并没有在Apollo新建cluster,这个时候Apollo的行为是怎样的?

或者在运行时指定了cluster=SomeCluster,但是并没有在Apollo新建cluster,这个时候Apollo的行为是怎样的?

接下来就来介绍一下配置获取的规则。

1.5.1 应用自身配置的获取规则

当应用使用下面的语句获取配置时,我们称之为获取应用自身的配置,也就是应用自身的application namespace的配置。

Config config = ConfigService.getAppConfig();

对这种情况的配置获取规则,简而言之如下:

  1. 首先查找运行时cluster的配置(通过apollo.cluster指定)
  2. 如果没有找到,则查找数据中心cluster的配置
  3. 如果还是没有找到,则返回默认cluster的配置

图示如下:

application-config-precedence

所以如果应用部署在A数据中心,但是用户没有在Apollo创建cluster,那么获取的配置就是默认cluster(default)的。

如果应用部署在A数据中心,同时在运行时指定了SomeCluster,但是没有在Apollo创建cluster,那么获取的配置就是A数据中心cluster的配置,如果A数据中心cluster没有配置的话,那么获取的配置就是默认cluster(default)的。
Apollo客户端会把从服务端获取到的配置在本地文件系统缓存一份,当去服务器读取配置失败时,会使用本地缓存的。

Mac/Linux: /opt/data/{appId}/config-cache

Windows: C:\opt\data{appId}\config-cache

确保目录存在,且应用有读写权限。

1.5.2 公共组件配置的获取规则

FX.Hermes.Producer为例,hermes producer是hermes发布的公共组件。当使用下面的语句获取配置时,我们称之为获取公共组件的配置。

Config config = ConfigService.getConfig("FX.Hermes.Producer");

对这种情况的配置获取规则,简而言之如下:

  1. 首先获取当前应用下的FX.Hermes.Producer namespace的配置
  2. 然后获取hermes应用下FX.Hermes.Producer namespace的配置
  3. 上面两部分配置的并集就是最终使用的配置,如有key一样的部分,以当前应用优先

图示如下:

public-namespace-config-precedence

通过这种方式,就实现了对框架类组件的配置管理,框架组件提供方提供配置的默认值,应用如果有特殊需求,可以自行覆盖。

1.6 总体设计

overall-architecture

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

  • Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
  • Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
  • Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳
  • 在Eureka之上我们架了一层Meta Server用于封装Eureka的服务发现接口
  • Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧会做load balance、错误重试
  • Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance、错误重试
  • 为了简化部署,我们实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM进程中

为什么选择Eureka

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

  • 它提供了完整的Service Registry和Service Discovery实现
    • 首先是提供了完整的实现,并且也经受住了Netflix自己的生产环境考验,相对使用起来会比较省心。
  • 和Spring Cloud无缝集成
    • 我们的项目本身就使用了Spring Cloud和Spring Boot,同时Spring Cloud还有一套非常完善的开源代码来整合Eureka,所以使用起来非常方便。
    • 另外,Eureka还支持在我们应用自身的容器中启动,也就是说我们的应用启动完之后,既充当了Eureka的角色,同时也是服务的提供者。这样就极大的提高了服务的可用性。
    • 这一点是我们选择Eureka而不是zk、etcd等的主要原因,为了提高配置中心的可用性和降低部署复杂度,我们需要尽可能地减少外部依赖。
  • Open Source
    • 最后一点是开源,由于代码是开源的,所以非常便于我们了解它的实现原理和排查问题。

1.7 客户端设计

client-architecture

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

  1. 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。
  2. 客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。
    • 这是一个fallback机制,为了防止推送机制失效导致配置不更新
    • 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
    • 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟。
  3. 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
  4. 客户端会把从服务端获取到的配置在本地文件系统缓存一份
    • 在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
  5. 应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知

1.7 配置更新推送实现

前面提到了Apollo客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。

长连接实际上我们是通过Http Long Polling实现的,具体而言:

  • 客户端发起一个Http请求到服务端
  • 服务端会保持住这个连接30秒
  • 如果在30秒内有客户端关心的配置变化,被保持住的客户端请求会立即返回,并告知客户端有配置变化的namespace信息,客户端会据此拉取对应namespace的最新配置
  • 如果在30秒内没有客户端关心的配置变化,那么会返回Http状态码304给客户端
  • 客户端在服务端请求返回后会自动重连

考虑到会有数万客户端向服务端发起长连,在服务端我们使用了async servlet(Spring DeferredResult)来服务Http Long Polling请求。

1.8 可用性考虑

配置中心作为基础服务,可用性要求非常高,下面的表格描述了不同场景下Apollo的可用性:

场景影响降级原因
某台config service下线无影响 Config service无状态,客户端重连其它config service
所有config service下线客户端无法读取最新配置,Portal无影响客户端重启时,可以读取本地缓存配置文件 
某台admin service下线无影响 Admin service无状态,Portal重连其它admin service
所有admin service下线客户端无影响,portal无法更新配置  
某台portal下线无影响 Portal域名通过slb绑定多台服务器,重试后指向可用的服务器
全部portal下线客户端无影响,portal无法更新配置  
某个数据中心下线无影响 多数据中心部署,数据完全同步,Meta Server/Portal域名通过slb自动切换到其它存活的数据中心

 

 

二、Apollo快速开始

携程的开发人员为了让大家几分钟快速上手Apollo配置中心,已经准备了一套安装包,按照教程马上就能部署好了

注意windows环境要安装能运行bash脚本的软件如gitbash QuickStart github地址

安装包在不同的端口安装了配置中心的不同服务:

2.1 打开 http://localhost:8070 进入配置中心就可以对各种应用进行配置(修改键值对)

进入配置中心就可以新建项目(对应你的应用)
比如你有一个叫KiddService的接口,可以给它起一个唯一标识的应用Id(这里我叫kiddApp),提交之后在主页就可以看到了。

进入该项目的配置,先发布后配置或先配置后发布都行,这样你的应用就可以从配置中心拉取配置了。

编辑配置,其实就是添加或修改键值对

2.2 Springboot客户端连接Apollo配置中心

使用Springboo客户端连接Apollo特别的简单,只需要几步:

  • 必要的设置

    • AppId,在application.properties文件加入指定appId,app.id=YOUR-APP-ID。
    • Apollo Meta Server,前面说过,这是对Eureka的一个封装服务,主要是用来得到ConfigServer的地址。在application.propertiesbootstrap.properties中指定apollo.meta=http://config-service-url
    • 本地配置缓存路径,确保目录存在
      • Mac/Linux: /opt/data/{appId}/config-cache
      • Windows: C:\opt\data{appId}\config-cache
  • 可选设置

    • Environment(上面说的四种环境)
      • 在Java程序启动脚本中,可以指定-Denv=YOUR-ENVIRONMENT
      • 还可以通过操作系统的System Environment ENV来指定
      • 通过配置文件来指定env=YOUR-ENVIRONMENT;(windows C:\opt\settings\server.properties)(linux /opt/settings/server.properties)
    • Cluster(集群)
      • 在Java程序启动脚本中,可以指定-Dapollo.cluster=SomeCluster
      • 过Spring Boot的配置文件,apollo.cluster=SomeCluster
      • 可以在server.properties配置文件中指定idc=xxx

例如在我的quickstart测试环境中我在application.properties文件中只要如下配置就行了:

server.port=9017
app.id=apolloapp
apollo.meta=http://127.0.0.1:8080

Maven Dependency(maven的依赖)

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

获取本应用的配置

在springboot下获取配置的方法有很多种,以我开发的KiddService为例:

    Config config = ConfigService.getAppConfig(); //config instance is singleton for each namespace and is never null
    String someKey = "key1";
    String someDefaultValue = "none";
    String value = config.getProperty(someKey, someDefaultValue);
@Configuration
@EnableApolloConfig
public class AppConfig {
 @Bean
 public TestJavaConfigBean javaConfigBean() {
    return new TestJavaConfigBean();
    }
}
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;
    }
}

一般所有的配置放在一个bean里面就可以了

 

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐