Apollo介绍以及应用

传统应用配置问题

  • 静态配置
    • 传统应用的配置,都是静态配置,写在配置文件中,运行时无法动态修改,如果修改之后,就需要重启应用
  • 配置格式不统一
    • 开发人员习惯不同,使用XML、properties、DB存储配置
  • 易引起事故
    • 在上线时,有时会忘记修改配置文件,将测试环境的变量变成线上
  • 配置修改麻烦,周期长
    • 在部署多个服务器时,修改配置费时费力
  • 缺少安全审计和版本控制
    • 没有版本控制,在出现问题时,无法及时进行回滚。

配置中心解决方法

  • 静态配置
    • 集中式配置,所有的配置信息都保存到配置中心
  • 配置格式不统一
    • 配置中心统一管理格式,开发人员不用关心格式,通过界面管理
  • 易引发事故
    • 环境隔离,不同的环境使用不同的配置,互不干扰
    • 配置修改之后,可以生效
  • 配置麻烦,周期长
  • 配置集中一次修改,实时通知到所有客户端
  • 缺少安全审计和版本控制
  • 所有修改都有历史记录,方便查找修改人和时间
  • 可以按需退回到历史版本

配置基本概念

配置定义

  • 可独立于程序的可配变量
  • 同一份程序在不同配置下会有不同行为
  • 应用场景:连接字符串、应用配置、业务配置

配置形态

  • 程序内部硬编码(坚决反对)
  • 配置文件:将配置写入xml、properties文件中
  • 环境变量:根据环境不同,传递到程序中
  • 启动参数:将参数传递到程序中
  • 基于数据库:将配置写入到数据库

配置治理

  • 权限控制和审计:要保留用户的操作记录和权限
  • 不同环境、集群配置管理:根据不同环境,获取不同的配置
  • 框架类组件配置管理:获取配置,是否开启功能
  • 灰度发布:进行公测,环境隔离

配置分类和场景

动态配置

  • 应用配置
    • 请求超时、线程池、队列、缓存、数据库连接池的容量、日志级别、限流熔断阈值、黑白名单
  • 功能开关
    • 蓝绿发布、灰度开关、降级开关、HA高可用开关、DB迁移
  • 业务配置
    • 促销规律、贷款额度、利率等业务参数、A/B测试

静态配置

  • 安全配置
    用户名、密码、令牌、许可证户等
  • 环境相关
    • 数据库/中间件/其他服务的连接字符串

开关驱动开发原理

优点

  • 新功能和代码发布分离,减轻发布风险
  • 迭代速度快,快速创新实现
  • 可定时高级A/B测试
  • 相对复杂发布系统,投入成本相对低
  • 没有分支开发的合并冲突问题

缺点

  • 代码侵入、需要定期清理
  • 需要开关配置中心配合
  • 需要DevOps文化和流程配合

Apollo 功能亮点

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

Apollo 架构

核心概念

概念 应用 环境 集群 配置项
解释 应用程序的唯一标识,存储位置为
/META-INF/app.properties
功能不同,不同环境,存储位置为
/opt/settings/server.properties
一个应用不同实例的分组,不同集群,
拥有不同的配置
支持xml、properties、json格式

注意:配置项存放位置:(1)私有配置env+app+cluster+namespace+item_key;(2)共有配置:env+cluster+namespace+item_key。

命名空间

一个应用不同配置的分组,默认的命名空间为application。

命名空间类型

  • 私有类型:只能被所属应用获取
  • 共有类型:环境变量必须全局唯一
  • 关联类型:私有继承公开并覆盖、定制公共组建配置场景

基础模型

基础模型的流程:

1.用户登陆Apollo系统之后,在配置中心修改配置并发布

2.在用户发布配置之后,会同知道Apollo客户端更新配置

3.Apollo客户端会定时从配置中心拉取最新的配置、更新本地配置并通知到应用

架构模块介绍

Config Server

  • 提供配置获取接口
  • 提供配置更新推送接口(基于Http long polling)
    • 服务端使用 Spring DeferredResult实现异步化,从而大大增加长连接数量
    • 目前使用的tomcat embed默认配置是最多10000个连接
  • 接口服务对象为Apollo客户端

Admin Service

  • 提供配置管理接口
  • 提供配置修改、发布等接口
  • 接口服务对象为Portal

Meta Server

  • Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port)
  • Client通过域名访问Meta Server获取Config Service服务列表(IP+Port)
  • Meta Server从Eureka获取Config Service和Admin Service的服务信息,相当于是一个Eureka Client
  • 增设一个Meta Server的角色主要是为了封装服务发现的细节,对Portal和Client而言,永远通过一个Http接口获取Admin Service和Config Service的服务信息,而不需要关心背后实际的服务注册和发现组件
  • Meta Server只是一个逻辑角色,在部署时和Config Service是在一个JVM进程中的,所以IP、端口和Config Service一致

Eureka

  • 基于Eureka和Spring Cloud Netflix提供服务注册和发现
  • Config Service和Admin Service会向Eureka注册服务,并保持心跳
  • 为了简单起见,目前Eureka在部署时和Config Service是在一个JVM进程中的(通过Spring Cloud Netflix)

Portal

  • 提供Web界面供用户管理配置
  • 通过Meta Server获取Admin Service服务列表(IP+Port),通过IP+Port访问服务
  • 在Portal侧做load balance、错误重试

Client

  • Apollo提供的客户端程序,为应用提供配置获取、实时更新等功能
  • 通过Meta Server获取Config Service服务列表(IP+Port),通过IP+Port访问服务
  • 在Client侧做load balance、错误重试

框架如何保证实时更新

用户在Portal发布配置之后,AdminService会将配置写入到ReleaseMessage表中,ConfigServer会定期扫描ReleaeMessage表,会实时通知客户端。(长连接,通过Http Long Polling实现)

客户端还会定时从Apollo配置中心拉取配置(推拉结合,Spring DeferredResult、定期拉配置)

高可用

1.Config/Admin/Portal 都是用负载均衡技术,来保证高可用。
2.服务端的实时推送技术、客户端定时拉取配置,保证及时性,如果拉取不到,客户端使用本地缓存的配置,保证高可用。

为什么使用Eureka

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

Apollo 于Spring Boot 结合

pom依赖


    spring-boot-starter-parent
    org.springframework.boot
    2.1.8.RELEASE


    UTF-8
    UTF-8
    1.8



    
        org.springframework.boot
        spring-boot-starter-web
    
    
        com.ctrip.framework.apollo
        apollo-client
        1.4.0
    
    
        org.projectlombok
        lombok
    

yml文件

app:
  id: SampleApp
apollo:
  bootstrap:
    enabled: true
  meta: https://localhost:8080

function:
  switch:
    open: false

current:
  version: v1
server:
  port: 9000

添加Apollo配置

package com.edu.apollo.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;

@Data
public class DemoGlobalConfig {
    @Value("${current.version}")
    public String currentVersion;
    @Value("${function.switch.open}")
    public boolean switchFlag;
}
package com.edu.apollo.config;

import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableApolloConfig
public class DemoApolloConfig {
    @Bean
    public DemoGlobalConfig globalConfig() {
        return new DemoGlobalConfig();
    }
}

相对应的Controller

package com.edu.apollo.controller;

import com.edu.apollo.config.DemoGlobalConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;


@RestController
public class ApolloConfigController {
    @Autowired
    DemoGlobalConfig demoGlobalConfig;

    @Autowired
    Environment environment;

    @CrossOrigin
    @RequestMapping(value = "/config", method = RequestMethod.GET)
    public ResponseEntity getApolloConfig() {
        String currentVersion = demoGlobalConfig.getCurrentVersion();
        return new ResponseEntity(currentVersion,HttpStatus.OK);
    }
    //check whether the parameter is valid or not
    @CrossOrigin
    @RequestMapping(value = "/parameter", method = RequestMethod.GET)
    public ResponseEntity getApolloConfigByParameter(@RequestParam String parameter) {
        String result = environment.getProperty(parameter);
        if (result == null){
            result = "defaultValue";
        }
        return new ResponseEntity(result,HttpStatus.OK);
    }
}

添加开关

package com.edu.apollo.service;

public interface SwitchService {
    public String getSwitchData();
}
package com.edu.apollo.service.impl;

import com.edu.apollo.config.DemoGlobalConfig;
import com.edu.apollo.service.SwitchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SwitchServiceImpl implements SwitchService {
    @Autowired
    private DemoGlobalConfig demoGlobalConfig;

    @Override
    public String getSwitchData() {
        if (demoGlobalConfig.isSwitchFlag()) {
            return "new";
        } else {
            return "old";
        }
    }
}
package com.edu.apollo.controller;

import com.edu.apollo.service.SwitchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SwitchController {

    @Autowired
    private SwitchService switchService;

    @GetMapping(value = "/switch")
    public ResponseEntity getSwitchOpen(){
        return new ResponseEntity(switchService.getSwitchData(),HttpStatus.OK);
    }
}

在启动程序的时候,需要添加环境变量:-Dapollo.configService=http://localhost:8080

Apollo启动过程

start config service  for client 
start admin service for portal 8090
start portal service 8070
eureka 8080
apollo cache:/opt/data/{appid}/config-cache  要有读写权限

运行步骤

校验是否启动成功

在程序启动之后,需要进行校验: http://localhost:9000/config

返回值为v1

校验Apollo是否生效

在Apollo界面上更改配置为current.version=v2
在程序启动之后,需要进行校验: http://localhost:9000/config

返回值为v2

开关功能校验

在程序启动之后,需要进行校验: http://localhost:9000/switch

返回值为old

在Apollo界面上更改配置为function.switch.open=true

在程序启动之后,需要进行校验: http://localhost:9000/config

返回值为v2

参考文献

Apollo配置中心设计
微服务架构~携程Apollo配置中心架构剖析
source code

你可能感兴趣的:(Apollo介绍以及应用)