配置就是应用程序在启动和运行的时候往往需要读取一些配置信息,配置基本上伴随着应用程序的整个生命周期,比如:数据库连接参数、启动参数等。常见的配置properties,yml,xml等。
先说为什么需要配置中心,传统的配置有什么问题?
没有配置也可以在代码写死,为什么需要配置文件,其实这个问题就是配置文件的优点了,比如,统一管理配置信息,灵活调整,方便扩展。
分布式微服务的出现,导致不同模块领域划分为独立应用,每个独立应用为了保持高可用搭建为集群部署,所以一个产品线对应几十个应用,每个应用对应上百台机器,按照传统方式配置文件,那每次修改配置都要修改上百台应用的配置,可维护性,扩展性极低。分布式配置中心是独立于每一个应用服务的单独组件,统一对外提供配置服务,便于维护扩展管理。
总结一句话:配置中心就是一种统一管理各种应用配置的基础服务组件。
1.配置是独立于程序的只读变量
配置首先是独立于程序的,同一份程序在不同的配置下会有不同的行为。其次,配置对于程序是只读的,程序通过读取配置来改变自己的行为,但是程序不应该去改变配置。
2.配置伴随应用的整个生命周期
配置贯穿于应用的整个生命周期,应用在启动时通过读取配置来初始化,在运行时根据配置调整行为。比如:启动时需要读取服务的端口号、系统在运行过程中需要读取定时策略执行定时任务等。
3.配置可以有多种加载方式
常见的有程序内部硬编码,配置文件,环境变量,启动参数,基于数据库等。
4 配置需要治理
权限控制:由于配置能改变程序的行为,不正确的配置甚至能引起灾难,所以对配置的修改必须有比较完善的权限控制。不同环境、集群配置管理:同一份程序在不同的环境(开发,测试,生产)、不同的集群(如不同的数据中心)经常需要有不同的配置,所以需要有完善的环境、集群配置管理。
总的来看,Apollo和Nacos相对于Spring Cloud Config的生态支持更广,在配置管理流程上做的更好。Apollo相对于Nacos在配置管理做的更加全面,Nacos则使用起来相对比较简洁,在对性能要求比较高的大规模场景更适合。但对于一个开源项目的选型,项目上的人力投入(迭代进度、文档的完整性)、社区的活跃度(issue的数量和解决速度、Contributor数量、社群的交流频次等),这些因素也比较关键,考虑到Nacos开源时间不长和社区活跃度,所以从目前来看Apollo应该是最合适的配置中心选型。
Apollo(阿波罗)是一款可靠的分布式配置管理中心,诞生于携程框架研发部,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。
Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。
.Net客户端不依赖任何框架,能够运行于所有.Net运行时环境。
更多产品介绍参见Apollo配置中心介绍。
本地快速部署请参见Quick Start
演示环境(Demo):
http://81.68.181.139
账号/密码:apollo/admin
apollo gitee :https://gitee.com/apolloconfig
apollo github:https://github.com/apolloconfig
apollo 官方地址:https://www.apolloconfig.com/#/zh/README
1.统一管理不同环境、不同集群的配置
2.配置修改实时生效(热发布)
用户在Apollo修改完配置并发布后,客户端能实时(1秒)接收到最新的配置,并通知到应用程序。
3.版本发布管理
所有的配置发布都有版本概念,从而可以方便的支持配置的回滚。
4.灰度发布
支持配置的灰度发布,比如点了发布后,只对部分应用实例生效,等观察一段时间没问题后再推给所有应用实例。
5.权限管理、发布审核、操作审计
6.客户端配置信息监控
可以方便的看到配置在被哪些实例使用
7.提供Java和.Net原生客户端
8.提供开放平台API
8.部署简单
如下即是Apollo的基础模型:
1.用户在配置中心对配置进行修改并发布
2.配置中心通知Apollo客户端有配置更新
3.Apollo客户端从配置中心拉取最新的配置、更新本地配置并通知到应用
下图是Apollo的作者宋顺给出的架构图概览,详细说明可以参考Apollo配置中心架构剖析。
可能从架构图并不能很清晰的明白整个架构设计,业务流转,下边给出一些说明结合上图理解相对容易些。
1.Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端。
2.Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)。
3.Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳。
4.在Eureka之上我们架了一层Meta Server用于封装Eureka的服务发现接口。
5.Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧会做load balance、错误重试。
6.Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance、错误重试。
7.为了简化部署,我们实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM进程中。
关于为什么采用eureka作者给了三点原因
1.它提供了完整的Service Registry和Service Discovery实现。
2.和SpringCloud无缝集成。
3.Open Source,对也就是开源。
各个模块说明详见官网说明。
为了让大家更快的上手了解Apollo配置中心,我们这里准备了一个Quick Start,能够在几分钟内在本地环境部署、启动Apollo配置中心。
考虑到Docker的便捷性,我们还提供了Quick Start的Docker版本,如果你对Docker比较熟悉的话,可以参考Apollo Quick Start Docker部署通过Docker快速部署Apollo。
不过这里需要注意的是,Quick Start只针对本地测试使用,如果要部署到生产环境,还请另行参考分布式部署指南。
1.Java环境
Apollo服务端:1.8+
Apollo客户端:1.8+
在配置好后,可以通过如下命令检查:
java -version
正常输出:
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
注意事项:
如需运行在 Java 1.7 运行时环境,请使用 1.x 版本的 apollo 客户端,如 1.9.1
Windows用户请确保JAVA_HOME环境变量已经设置。
2.MySQL
版本要求:5.6.5+
Apollo的表结构对timestamp使用了多个default声明,所以需要5.6.5以上版本。
连接上MySQL后,可以通过如下命令检查:
SHOW VARIABLES WHERE Variable_name = 'version';
正常输出
Variable_name | Value |
---|---|
version | 5.7.11 |
我们准备好了一个Quick Start安装包,大家只需要下载到本地,就可以直接使用,免去了编译、打包过程。
安装包共50M,如果访问github网速不给力的话,可以从百度网盘下载。
从GitHub下载
checkout或下载apollo-build-scripts项目
由于Quick Start项目比较大,所以放在了另外的repository,请注意项目地址
https://github.com/nobodyiam/apollo-build-scripts
从百度网盘下载
通过网盘链接下载,提取码: 9wwe
下载到本地后,在本地解压apollo-quick-start.zip
为啥安装包要58M这么大?
因为这是一个可以自启动的jar包,里面包含了所有依赖jar包以及一个内置的tomcat容器
注意如果是源码需要手动打包。
找到解压apollo-quick-start.zip后的文件下的sql文件夹,包含两个sql脚本apolloconfigdb.sql 与 apolloportaldb.sql。
Apollo服务端共需要两个数据库:ApolloPortalDB和ApolloConfigDB,我们把数据库、表的创建和样例数据都分别准备了sql文件,只需要导入数据库即可。
注意:如果你本地已经创建过Apollo数据库,请注意备份数据。我们准备的sql文件会清空Apollo相关的表。
通过Navicat客户端直接导入这两个sql文件即可,选择数据库右键,运行sql文件,选择sql文件执行,然后刷新,可以看到两个新的数据库apolloconfigdb与apolloportaldb。
或者通过命令执行也可,创建ApolloPortalDB与ApolloConfigDB,并且验证。
source /your_local_path/sql/apolloportaldb.sql
select `Id`, `AppId`, `Name` from ApolloPortalDB.App;
source /your_local_path/sql/apolloconfigdb.sql
select `NamespaceId`, `Key`, `Value`, `Comment` from ApolloConfigDB.Item;
Apollo服务端需要知道如何连接到你前面创建的数据库,所以需要编辑demo.sh,修改ApolloPortalDB和ApolloConfigDB相关的数据库连接串信息。
注意:填入的用户需要具备对ApolloPortalDB和ApolloConfigDB数据的读写权限。
# apollo config db info
apollo_config_db_url="jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8&serverTimezone=Asia/Shanghai"
apollo_config_db_username=${APOLLO_CONFIG_DB_USERNAME:-root}
apollo_config_db_password=${APOLLO_CONFIG_DB_PASSWORD:-123456}
# apollo portal db info
apollo_portal_db_url="jdbc:mysql://localhost:3306/ApolloPortalDB?characterEncoding=utf8&serverTimezone=Asia/Shanghai"
apollo_portal_db_username=${APOLLO_PORTAL_DB_USERNAME:-root}
apollo_portal_db_password=${APOLLO_PORTAL_DB_PASSWORD:-123456}
启动应用程序的时候需要注意,如果是Windows环境是不能使用cmd窗口执行demo.sh脚本的,一般开发安装的都有gitbash,可以通过Git Bash Here打开命令窗口执行启动命令,如果没有安装gitbash则通过Java -jar的方式单独启动。
Quick Start脚本会在本地启动3个服务,分别使用8070, 8080, 8090端口,请确保这3个端口当前没有被使用。
1.首先检查端口是否占用
在Linux/Mac下,可以通过如下命令检查:
lsof -i:8080
windows端口查看端口占用情况
输入【netstat -ano】命令,回车,就可看到Windows系统当前所有端口的占用情况
输入【netstat -aon|findstr “端口号”】命令,回车,就可以看到指定端口的占用情况
输入【tasklist|findstr “被占用端口对应的 PID”】命令,就可以查看是哪个进程或者程序占用了相应的端口。
输入【taskkill /t /f /pid pid号】 命令,结束端口占用的进程。
netstat -ano
netstat -aon|findstr "8080"
tasklist|findstr "151520"
taskkill /t /f /pid 151520
2.执行启动脚本
Windows打开gitbash,Linux打开命令窗口。执行demo.sh脚本。
./demo.sh start
当看到如下输出,则说明启动成功
Think@LAPTOP-DIDC0OGA MINGW64 /c/software/apollo-quick-start-2.0.1
$ ./demo.sh start
Windows new JAVA_HOME is: /c/software/Java/JDK18~1.0_1
==== starting service ====
Service logging file is ./service/apollo-service.log
Started [1589]
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 [1634]
Waiting for portal startup...
Portal started. You can visit http://localhost:8070 now!
1.查看样例配置
打开配置中心页面:http://localhost:8070
Quick Start集成了Spring Security简单认证,更多信息可以参考Portal 实现用户登录功能
2.登录客户端
输入用户名apollo,密码admin后登录
3.首页案例
点击SampleApp进入配置界面,可以看到当前有一个配置timeout=100 配置界面
如果提示系统出错,请重试或联系系统负责人,请稍后几秒钟重试一下,因为通过Eureka注册的服务有一个刷新的延时。
4.可以通过配置中心页面进行简单的操作
创建新的应用,简单配置,开启密匙管理。
5.运行客户端程序进行验证
我们准备了一个简单的Demo客户端来演示从Apollo配置中心获取配置。程序很简单,就是用户输入一个key的名字,程序会输出这个key对应的值。如果没找到这个key,则输出undefined。同时,客户端还会监听配置变化事件,一旦有变化就会输出变化的配置信息。
运行./demo.sh client启动Demo客户端,忽略前面的调试信息,可以看到如下提示:
Apollo Config Demo. Please input key to get the value. Input quit to exit.
>timeout
> [SimpleApolloConfigDemo] Loading key : timeout with value: 100
详情参考 Java客户端使用指南:https://www.apolloconfig.com/#/zh/usage/java-sdk-user-guide
1.项目结构
│ apollo-platform
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─zrj
│ │ │ └─apollo
│ │ │ └─platform
│ │ │ │ ApolloPlatformApplication.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ ApolloConfig.java
│ │ │ │ ApolloConfigBean.java
│ │ │ │ ApolloConfigListener.java
│ │ │ │
│ │ │ └─controller
│ │ │ ApolloController.java
│ │ │
│ │ └─resources
│ │ application.properties
│ │
└─pom.xml
2.maven依赖
<dependency>
<groupId>com.ctrip.framework.apollogroupId>
<artifactId>apollo-client-config-dataartifactId>
<version>2.0.1version>
dependency>
2.配置文件
application.properties
# 端口
server.port=8088
# apollo config
app.id=apollo-platform
apollo.meta=127.0.0.1:8080
apollo.access-key.secret=0ba77e739c4c4becbb9a4b44f8820d73
# will inject 'application' namespace in bootstrap phase
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.bootstrap.namespaces = application
3.代码实现
ApolloConfigBean
package com.zrj.apollo.platform.config;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* ApolloConfigBean
*
* Apollo自动加载热更新问题
* 1、Apollo默认会对Value和ApolloJsonValue注解的数据进行热更新。
* 2、其他数据需要通过ApplicationContext.publishEven刷新到应用中。
* 3、当多个namespace时,优先使用先加载的数据,如namespaces: zone-dev,application,使用zone-dev中数据。
* 方案一:通过ApolloConfigChangeListener监听指定namespace,无法满足根据分区配置namespace。
* 方案二:通过Config.addChangeListener监听指定namespace,动态监听不同的namespace配置。
*
* @author zrj
* @since 2023/1/6
**/
@Data
@Component
public class ApolloConfigBean {
@Value("${timeout:100}")
private int timeout;
@Value("${batch:100}")
private int batch;
@Value("${run:stop}")
private String run;
@Value("${say:hi}")
private String say;
/**
* ApolloJsonValue annotated on fields example, the default value is specified as empty list - []
*
jsonBeanProperty=[{"someString":"hello","someInt":100},{"someString":"world!","someInt":200}]
*/
@ApolloJsonValue("${jsonBeanProperty:[]}")
private List<JsonBean> jsonBeans;
private static class JsonBean {
private String someString;
private int someInt;
@Override
public String toString() {
return "JsonBean{" +
"someString='" + someString + '\'' +
", someInt=" + someInt +
'}';
}
}
}
ApolloConfigListener
package com.zrj.apollo.platform.config;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.spring.events.ApolloConfigChangeEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import java.util.Set;
/**
* apollo监听器
*
* @author zrj
* @since 2023/1/6
**/
public class ApolloConfigListener implements ApplicationListener<ApolloConfigChangeEvent> {
private static final Logger logger = LoggerFactory.getLogger(ApolloConfigListener.class);
@Override
public void onApplicationEvent(ApolloConfigChangeEvent apolloConfigChangeEvent) {
String namespace = apolloConfigChangeEvent.getConfigChangeEvent().getNamespace();
logger.info("On ApplicationEvent ApolloConfigChangeEvent received, namespace: {}", namespace);
Set<String> changedKeys = apolloConfigChangeEvent.getConfigChangeEvent().changedKeys();
for (String key : changedKeys) {
ConfigChange configChange = apolloConfigChangeEvent.getConfigChangeEvent().getChange(key);
logger.info("configChange received {}", configChange);
}
}
}
ApolloConfig
package com.zrj.apollo.platform.config;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* apollo配置
*
* @author zrj
* @since 2023/1/6
**/
@Configuration
@EnableApolloConfig
public class ApolloConfig {
@Bean
public ApplicationListener testApplicationListener() {
return new ApolloConfigListener();
}
}
ApolloController
package com.zrj.apollo.platform.controller;
import com.zrj.apollo.platform.config.ApolloConfigBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* apollo
*
* @author zrj
* @since 2023/1/6
**/
@RestController
@RequestMapping("/apollo")
public class ApolloController {
private static final Logger logger = LoggerFactory.getLogger(ApolloController.class);
private final ApolloConfigBean apolloConfigBean;
public ApolloController(ApolloConfigBean apolloConfigBean) {
this.apolloConfigBean = apolloConfigBean;
}
@GetMapping("/get")
public String getApolloConfig() {
String helloTime = "【ApolloController】ApolloConfigBean:" + apolloConfigBean.toString();
logger.info(helloTime);
return helloTime;
}
}
ApolloPlatformApplication
package com.zrj.apollo.platform;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* apollo
* 配置系统启动参数,idea,VM options:-Dapollo.meta=http://127.0.0.1:8080 -Denv=dev
*
* @author zrj
* @since 20230106
*/
@SpringBootApplication
public class ApolloPlatformApplication {
public static void main(String[] args) {
SpringApplication.run(ApolloPlatformApplication.class, args);
}
}
4.配置启动参数
配置系统启动参数,idea,VM options:-Dapollo.meta=http://127.0.0.1:8080 -Denv=dev
5.测试验证
访问地址:http://localhost:8088/apollo/get
访问结果:
【ApolloController】ApolloConfigBean:ApolloConfigBean(timeout=600, batch=900, run=start, jsonBeans=[JsonBean{someString=‘hello’, someInt=100}, JsonBean{someString=‘world!’, someInt=200}])
启动的时候遇到了两个问题,官方文档上也没具体说明,当然也可能是我忽略了,主要是启动参数必须配置,官方案例中也没用配置启动参数拉去配置是没有问题,自己集成的必须配置启动参数才能拉去配置,目前解决方案如下。
启动问题
日志循环打印拉取失败信息,RemoteConfigLongPollService: Long polling failed, will retry in 1 seconds. appId: apollo-platform, cluster: default, namespaces: application, long polling url: null, reason: Get config services failed from 127.0.0.1:8080/services/config?appId=apollo-platform&ip=192.168.3.24
解决方案
这个是未对apollo.meta 属性进行正确赋值,必须是eureka的地址和端口,就是你输入这个url+端口的时候跳转到的是eureka这个界面,而非apollo管理页面,否则必定报错。
配置系统启动参数,idea,VM options:-Dapollo.meta=http://127.0.0.1:8080 -Denv=dev
权限问题
Cause: Server returned HTTP response code: 401 for URL
解决方案
apollo开启了密匙,需要配置正确的密匙。
apollo管理页面:管理密钥。配置文件:apollo.access-key.secret=1cf998c4e2ad4704b45a98a509d15719
apollo支持灰度发布,可以通过配置灰度版本,灰度规则,根据不同的ip或者标签拉取不同的配置。只有符合灰度规则的机器才能拉取灰度配置。
1.配置灰度
灰度-添加灰度配置-添加灰度规则
2.灰度验证
访问地址:http://localhost:8088/apollo/get
访问结果:
【ApolloController】ApolloConfigBean:ApolloConfigBean(timeout=600, batch=900, run=start, say=hello, jsonBeans=[JsonBean{someString=‘hello’, someInt=100}, JsonBean{someString=‘world!’, someInt=200}])
Apollo自动加载热更新问题
1、Apollo默认会对Value和ApolloJsonValue注解的数据进行热更新。
2、其他数据需要通过ApplicationContext.publishEven刷新到应用中。
3、当多个namespace时,优先使用先加载的数据,如namespaces: zone-dev,application,使用zone-dev中数据。
方案一:通过ApolloConfigChangeListener监听指定namespace,无法满足根据分区配置namespace。
方案二:通过Config.addChangeListener监听指定namespace,动态监听不同的namespace配置。
Apollo常见问题:https://www.apolloconfig.com/#/zh/faq/faq
Apollo部署&开发问题:https://www.apolloconfig.com/#/zh/faq/common-issues-in-deployment-and-development-phase
Apollo性能测试报告:https://www.apolloconfig.com/#/zh/misc/apollo-benchmark
1.环境配置
机器配置:4C12G
JVM参数:1.8.0_60
Apollo版本:0.9。0
单台机器客户端连接数:5600
集群客户端连接数:10W+
2.性能指标
获取配置Http接口响应时间
QPS: 160
平均响应时间: 0.1ms
95线响应时间: 0.3ms
999线响应时间: 2.5ms
Config Server GC情况
YGC: 平均2Min一次,一次耗时300ms
OGC: 平均1H一次,一次耗时380ms
CPU指标
LoadAverage:0.5
System CPU利用率:6%
Process CPU利用率:8%