Apollo-微服务中集成分布式配置中心 - Go语言中文社区

Apollo-微服务中集成分布式配置中心


最近使用到微服务开发,就搜了点资料,以备学习之用。

分布式配置中心

在一个分布式环境中,同类型的服务往往会部署很多实例。这些实例使用了一些配置,为了更好地维护这些配置就产生了配置管理服务。通过这个服务可以轻松地管理成千上百个服务实例的配置问题。配置中心的特点:

配置的增删改查;

不同环境配置隔离(开发、测试、预发布、灰度/线上);

高性能、高可用性;

请求量多、高并发;

读多写少;

现有的配置中心组件有:Spring Cloud Config、Apollo、Disconf、Diamond 等等,这些组件在功能上有或多或少的差异,但是都具有基本的配置中心的功能。

Apollo 简介

Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。目前的有超过 14k 的 star,使用广泛。Apollo基于开源模式开发,开源地址:https://github.com/ctripcorp/apollo。

 

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

 

Apollo 支持4个维度管理 Key-Value 格式的配置:

 

application (应用):实际使用配置的应用,Apollo客户端在运行时需要知道当前应用是谁,从而可以去获取对应的配置;每个应用都需要有唯一的身份标识 – appId,应用身份是跟着代码走的,所以需要在代码中配置。

environment (环境):配置对应的环境,Apollo客户端在运行时需要知道当前应用处于哪个环境,从而可以去获取应用的配置。

cluster (集群):一个应用下不同实例的分组,比如典型的可以按照数据中心分,把上海机房的应用实例分为一个集群,把北京机房的应用实例分为另一个集群。对不同的cluster,同一个配置可以有不一样的值,如zookeeper地址。

namespace (命名空间):一个应用下不同配置的分组,可以简单地把namespace类比为文件,不同类型的配置存放在不同的文件中,如数据库配置文件,RPC配置文件,应用自身的配置文件等;应用可以直接读取到公共组件的配置namespace,如DAL,RPC等;应用也可以通过继承公共组件的配置namespace来对公共组件的配置做调整,如DAL的初始数据库连接数。

我们在集成 Apollo 时,可以根据需要创建相应的维度

快速入门

下面我们搭建一个基于 Spring Boot 的微服务,集成 Apollo。

启动服务端

Apollo配置中心包括:Config Service、Admin Service 和 Portal。

 

快速入门

下面我们搭建一个基于 Spring Boot 的微服务,集成 Apollo。

启动服务端

Apollo配置中心包括:Config Service、Admin Service 和 Portal。

Config Service:提供配置获取接口、配置推送接口,服务于Apollo客户端;

Admin Service:提供配置管理接口、配置修改发布接口,服务于管理界面Portal;

Portal:配置管理界面,通过MetaServer获取AdminService的服务列表,并使用客户端软负载SLB方式调用AdminService。

 

 

Config Service:提供配置获取接口、配置推送接口,服务于Apollo客户端;

Admin Service:提供配置管理接口、配置修改发布接口,服务于管理界面Portal;

Portal:配置管理界面,通过MetaServer获取AdminService的服务列表,并使用客户端软负载SLB方式调用AdminService。

官网准备好了一个Quick Start安装包,大家只需要下载到本地,就可以直接使用,免去了编译、打包过程。也可以自行编译,较为繁琐。

 

Apollo服务端共需要两个数据库:ApolloPortalDB和ApolloConfigDB。创建的语句见安装包,创建好之后需要配置启动的脚本,即 demo.sh 脚本:

 

#apollo config db info

apollo_config_db_url=jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8

apollo_config_db_username=用户名

apollo_config_db_password=密码(如果没有密码,留空即可)

 

# apollo portal db info

apollo_portal_db_url=jdbc:mysql://localhost:3306/ApolloPortalDB?characterEncoding=utf8

apollo_portal_db_username=用户名

apollo_portal_db_password=密码(如果没有密码,留空即可)

脚本会在本地启动3个服务,分别使用8070, 8080, 8090端口,请确保这3个端口当前没有被使用。执行

 

./demo.sh start

 

看到输出如下的日志信息:

 

==== starting service ====

Service logging file is ./service/apollo-service.log

Started [10768]

Waiting for config service startup.......

Config service started. You may visit http://localhost:8080 for service status now!

Waiting for admin service startup....

Admin service started

==== starting portal ====

Portal logging file is ./portal/apollo-portal.log

Started [10846]

Waiting for portal startup......

Portal started. You can visit http://localhost:8070 now!

Apollo 服务端启动成功。

 

客户端应用

搭建好 Apollo 服务器之后,接下来将我们的应用接入 Apollo。

 

引入依赖

        <dependency>

            <groupId>com.ctrip.framework.apollo</groupId>

            <artifactId>apollo-client</artifactId>

            <version>1.1.0</version>

        </dependency>

在依赖中只需要增加 apollo-client 的引用。

 

入口程序

@SpringBootApplication

@EnableApolloConfig("TEST1.product")

public class ApolloApplication {

 

    public static void main(String[] args) {

        SpringApplication.run(ApolloApplication.class, args);

    }

}

我们通过 @EnableApolloConfig("TEST1.product") 注解开启注册到 Apollo 服务端,并指定了 namespace 为 TEST1.product。

 

配置文件

app.id: spring-boot-logger

  # set apollo meta server address, adjust to actual address if necessary

apollo.meta: http://localhost:8080

server:

  port: 0

配置文件中指定了appid 和 Apollo 服务器的地址。

 

测试应用

我们通过动态设置输出的日志等级来测试接入的配置中心。

 

@Service

public class LoggerConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(LoggerConfiguration.class);

    private static final String LOGGER_TAG = "logging.level.";

 

    @Autowired

    private LoggingSystem loggingSystem;

 

    @ApolloConfig

    private Config config;

 

    @ApolloConfigChangeListener

    // 监听 Apollo 配置中心的刷新事件

    private void onChange(ConfigChangeEvent changeEvent) {

        refreshLoggingLevels();

    }

 

    @PostConstruct

    // 设置刷新之后的日志级别

    private void refreshLoggingLevels() {

        Set<String> keyNames = config.getPropertyNames();

        for (String key : keyNames) {

            if (containsIgnoreCase(key, LOGGER_TAG)) {

                String strLevel = config.getProperty(key, "info");

                LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());

                loggingSystem.setLogLevel(key.replace(LOGGER_TAG, ""), level);

                logger.info("{}:{}", key, strLevel);

            }

        }

    }

 

    private static boolean containsIgnoreCase(String str, String searchStr) {

        if (str == null || searchStr == null) {

            return false;

        }

        int len = searchStr.length();

        int max = str.length() - len;

        for (int i = 0; i <= max; i++) {

            if (str.regionMatches(true, i, searchStr, 0, len)) {

                return true;

            }

        }

        return false;

    }

}

如上的配置类用于根据 Apollo 配置中心的日志等级配置,设置本地服务的日志等级,并监听刷新事件,将刷新后的配置及时应用到本地服务,其中 @PostConstruct 注解用于在完成依赖项注入以执行任何初始化之后需要执行的方法。

 

@Service

public class PrintLogger {

    private static Logger logger = LoggerFactory.getLogger(PrintLogger.class);

 

    @ApolloJsonValue("${kk.v}")

    private String v;

 

    @PostConstruct

    public void printLogger() throws Exception {

        Executors.newSingleThreadExecutor().submit(() -> {

            while (true) {

                logger.error("=========" + v);

                logger.info("我是info级别日志");

                logger.error("我是error级别日志");

                logger.warn("我是warn级别日志");

                logger.debug("我是debug级别日志");

                TimeUnit.SECONDS.sleep(1);

            }

        });

    }

}

起一个线程,输出不同级别的日志。根据配置的日志等级,过滤后再打印。我们在如上的程序中,还自定义了一个字段,同样用以测试随机打印最新的值。

 

测试

我们在 Apollo 的配置界面中,增加如下的配置:

 

 

 

并将配置发布,启动我们本地的 SpringBoot 服务:

 

2019-05-28 20:31:36.688 ERROR 44132 --- [pool-1-thread-1] com.blueskykong.apollo.PrintLogger       : =========log-is-error-level.

2019-05-28 20:31:36.688 ERROR 44132 --- [pool-1-thread-1] com.blueskykong.apollo.PrintLogger       : 我是error级别日志

1

2

我们将调整日志的级别为warn,只需要在界面上编辑。

 

 

将编辑好的配置发布,应用服务将会收到刷新事件。

 

可以看到,服务刷新了日志的级别,打印 warn 的日志信息。

 

2019-05-28 20:35:56.819  WARN 44132 --- [pool-1-thread-1] com.blueskykong.apollo.PrintLogger       : 我是warn级别日志

2019-05-28 20:36:06.823 ERROR 44132 --- [pool-1-thread-1] com.blueskykong.apollo.PrintLogger       : =========log-is-warn-level.

1

2

原理细究

在体验了 Apollo 作为配置中心之后,我们将了解下 Apollo 的总体设计和实现的原理。

 

Apollo 整体架构

 

 

上图简要描述了 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进程中

ConfigService、AdminService、Portal 属于 Apollo 服务端的模块,其中提到的 Eureka 是为了保证高可用,Config 和 Admin 都是无状态以集群方式部署的,Client 怎么找到 Config?Portal 怎么找到 Admin?为了解决这个问题,Apollo在其架构中引入了Eureka服务注册中心组件,实现微服务间的服务注册和发现用于服务发现和注册,Config 和 Admin Service注册实例并定期报心跳, Eureka 与 ConfigService 一起部署。

 

MetaServer 其实是一个Eureka的Proxy,将Eureka的服务发现接口以更简单明确的HTTP接口的形式暴露出来,方便 Client/Protal 通过简单的 HTTPClient 就可以查询到 Config/Admin 的地址列表。获取到服务实例地址列表之后,再以简单的客户端软负载(Client SLB)策略路由定位到目标实例,并发起调用。

 

客户端实现

在配置中心中,一个重要的功能就是配置发布后实时推送到客户端。下面我们简要看一下这块是怎么设计实现的。

 

 

 

上图简要描述了配置发布的大致过程:用户在Portal操作配置发布;Portal调用Admin Service的接口操作发布;Admin Service发布配置后,发送ReleaseMessage给各个Config Service;Config Service收到ReleaseMessage后,通知对应的客户端。

 

如何通知客户端呢?我们看到 Apollo 的实现步骤如下:

 

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

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

这是一个fallback机制,为了防止推送机制失效导致配置不更新

客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified

定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟。

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

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

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

小结

本文首先介绍分布式配置中心的概念和 Apollo 接入的实践,然后深入介绍了 Apollo 的总体架构和实现的一些细节。总得来说, Apollo 是现有配置中心组件中,功能最全的一个。能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/xuyx001/article/details/104474451
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-12-12 00:42:50
  • 阅读 ( 1611 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢