Apollo()分布式配置中⼼
第⼀部分: Apollo简介
随着程序功能的⽇益复杂,程序的配置⽇益增多:各种功能的开关、参数的配置、服务器的地址……
对程序配置的期望值也越来越⾼:配置修改后实时⽣效,灰度发布,分环境、分集管理配置,完善的权限、审核机制……
在这样的⼤环境下,传统的通过配置⽂件、数据库等⽅式已经越来越⽆法满⾜开发⼈员对配置管理的需求。
Apollo配置中⼼应运⽽⽣!
1、Apollo简介
Apollo⽀持4个维度管理Key-Value格式的配置:
application (应⽤)
environment (环境)
cluster (集)
namespace (命名空间)
配置基本概念
配置是独⽴于程序的只读变量
配置独⽴于程序的,同⼀个程序在不同的配置下有不同的⾏为
配置对于程序是只读的,程序通过读取配置来改变⾃⼰的⾏为,程序不应该去改变配置
配置伴随应⽤的整个⽣命周期
配置贯穿于应⽤的整个⽣命周期,应⽤在启动时通过读取配置来初始化,在运⾏时根据配置调整⾏为
配置可以有多种加载⽅式
配置⽂件、环境变量、启动参数、基于数据库
配置需要治理
权限控制(由于配置能改变程序的⾏为,不正确的配置甚⾄能引起灾难,所以对配置的修改必须有⽐较完善的权限控制)
不同环境、集配置管理(同⼀份程序在不同的环境(开发,测试,⽣产)、不同的集(如不同的数据中⼼)经常需要有不同的配置,所以需要有完善的环境、集配置管理)为什么需要Apollo
统⼀管理不同环境、不同集的配置
Apollo提供了统⼀界⾯集中式管理不同环境(environment)、不同集(cluster)、不同命名空间(namespace)的配置。
同⼀份代码部署在不同的集,可以有不同的配置,⽐如zookeeper的地址等
通过命名空间(namespace)可以很⽅便地⽀持多个不同应⽤共享同⼀份配置,同时还允许应⽤对共享的配置进⾏覆盖
配置修改实时⽣效(热发布)
⽤户在Apollo修改完配置并发布后,客户端能实时(1秒)接收到最新的配置,并通知到应⽤程序
版本发布管理
所有的配置发布都有版本管理,从来很⽅便的⽀持配置回滚
灰度发布
⽀持配置的灰度发布,⽐如点了发布后,只对部分应⽤实例⽣效,等观察⼀段时间没问题后再推给所有应⽤实例
权限管理、发布审核、操作审计
应⽤和配置的管理都有完善的权限管理机制
所有操作都有审计⽇志
客户端配置信息监控
可以在界⾯上⽅便地看到配置在被哪些实例使⽤
提供Java和.Net原⽣客户端
提供开放平台API
部署简单
 2、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进程中
为什么需要eureka
由于config-server和admin-server会部署多个,哪个前端client(⽐如JAVA)和portal怎么到对应的config和admin呢?Apollo配置中⼼是基于Spring Cloud开发的,我们引⼊eureka作为服务注册及服务发现,Client和Portal通过eureka获取到对应config-server和admin-server的地址,然后直接于config-server或admin-server联系
各模块概要介绍
Config Service
提供配置获取接⼝
提供配置更新推送接⼝(基于Http long polling)
服务端使⽤Spring DeferredResult实现异步化,从⽽⼤⼤增加长连接数量
⽬前使⽤的tomcat embed默认配置是最多10000个连接(可以调整),使⽤了4C8G的虚拟机实测可以⽀撑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操作配置发布
Portal调⽤Admin Service的接⼝操作发布
Admin Service发布配置后,发送ReleaseMessage给各个Config Service
Config Service收到ReleaseMessage后,通知对应的客户端
实现⽅式如下:
Admin Service在配置发布后会往ReleaseMessage表插⼊⼀条消息记录,消息内容就是配置发布的AppId+Cluster+Namespace,参见DatabaseMessageSender
Config Service有⼀个线程会每秒扫描⼀次ReleaseMessage表,看看是否有新的消息记录,参见ReleaseMessageScanner
Config Service如果发现有新的消息记录,那么就会通知到所有的消息(ReleaseMessageListener),如NotificationControllerV2,消息的注册过程参见ConfigServiceAutoConfiguration
NotificationControllerV2得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端
Config Service通知客户端的实现⽅式
那NotificationControllerV2在得知有配置发布后是如何通知到客户端的呢?
实现⽅式如下:
客户端会发起⼀个Http请求到Config Service的notifications/v2接⼝,也就是NotificationControllerV2,参见RemoteConfigLongPollService
NotificationControllerV2不会⽴即返回结果,⽽是通过Spring DeferredResult把请求挂起
如果在60秒内没有该客户端关⼼的配置发布,那么会返回Http状态码304给客户端
如果有该客户端关⼼的配置发布,NotificationControllerV2会调⽤DeferredResult的setResult⽅法,传⼊有配置变化的namespace信息,同时该请求会⽴即返回。客户端从返回的结果中获取到配置变化的namespace后,会⽴即请求Config Service获取 
客户端设计
上图简要描述了Apollo客户端的实现原理:
客户端和服务端保持了⼀个长连接,从⽽能第⼀时间获得配置更新的推送。(通过Http Long Polling实现)
客户端还会定时从Apollo配置中⼼服务端拉取应⽤的最新配置。
这是⼀个fallback机制,为了防⽌推送机制失效导致配置不更新
客户端定时拉取会上报本地版本,所以⼀般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
定时频率默认为每5分钟拉取⼀次,客户端也可以通过在运⾏时指定System Property: freshInterval来覆盖,单位为分钟
客户端从Apollo配置中⼼服务端获取到应⽤的最新配置后,会保存在内存中
客户端会把从服务端获取到的配置在本地⽂件系统缓存⼀份
在遇到服务不可⽤,或⽹络不通的时候,依然能从本地恢复配置
应⽤程序可以从Apollo客户端获取最新的配置、订阅配置更新通知
总结:
推拉结合
保持长连接,配置实时推送
定期拉配置(fallback)
配置缓存在内存
本地缓存⼀份
应⽤程序
通过apollo客户端获取最新配置
订阅配置更新通知
3  客户端获取配置
#启动加载apollo
apollo:
bootstrap:
enabled: true
namespaces:  application,datasource
#拦截feign调⽤后去request请求参数开启
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
logging:
config: l
src/main/resources/META-INF/app.properties 
# test
app.id=100003
配置中⼼地址
有⼏种传递metaserver地址的⽅式
1)启动参数: -Ddev_meta=192.168.31.20
2)apollo-core.jar中添加apollo-env.properties
3)classpath中单独放⼀份:apollo-env.properties (src/main/resources/apollo-env.properties)
运⾏环境Env的⽅式
1)启动参数: -Denv=ENV
2)配置⽂件(推荐)
Linux:  /opt/settings/server.properties
Windows: C:\opt\settings\server.properties
⽀持:DEV/FAT/UAT/PRO/LOCAL
3)环境变量  ENV
⼀个环境中的⼀个app,在不同的集中可以有不同的配置
1)启动参数: -Dapollo.cluster=app_cluster_v1
2)通过配置⽂件
Linux        /opt/settings/server.properties
windows  C:\opt\settings\server.properties
本地缓存
Linux:  /opt/data/{appid}/config-cache
windows:  C:\opt\data\{appid}\config-cache
注意应⽤需要有读写权限
⽂件名: {appId}-{cluster}-{namespace}:properties
案例:
[root@bss-core-df948cbb8-vv98w config-cache]# pwd
/opt/data/100004/config-cache
[root@bss-core-df948cbb8-vv98w config-cache]# ll
total 8
-rw-r--r-- 1 root root 1021 8⽉ 12 10:49 100004+default+application.properties
-rw-r--r-- 1 root root 469 8⽉ 12 10:49 100004+default+datasource.properties
Maven Dependency
<dependency>
<groupId&ip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.1.0</version>
</dependency>
API使⽤⽅式
获取默认namespace的配置(application)
Config config = AppConfig(); //config instance is singleton for each namespace and is never null String someKey = "someKeyFromDefaultNamespace";
String someDefaultValue = "someDefaultValueForTheKey";
String value = Property(someKey, someDefaultValue);
通过上述的Property可以获取到someKey对应的实时最新的配置值。
另外,配置值从内存中获取,所以不需要应⽤⾃⼰做缓存
监听配置变化事件
Config config = AppConfig(); //config instance is singleton for each namespace and is never null
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
System.out.println("Changes for namespace " + Namespace());
for (String key : changeEvent.changedKeys()) {
ConfigChange change = Change(key);
System.out.println(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", PropertyName(), OldValue(), NewValue(), ChangeType()));
}
}
});
获取公共Namespace的配置
String somePublicNamespace = "CAT";
Config config = Config(somePublicNamespace); //config instance is singleton for each namespace and is never null
String someKey = "someKeyFromPublicNamespace";
String someDefaultValue = "someDefaultValueForTheKey";
String value = Property(someKey, someDefaultValue);
第⼆部分:部署案例
Portal部署⼀套,⽤于管理Dev,FAT,UAT,PRO环境
MetaServer/ConfigService/AdminService  需要每个环境独⽴部署,每个环境独⽴数据库DB
EurekaServer和MetaServer和ConfigService住在同⼀个JVM进程,AdminService使⽤另⼀个JVM进程
AdminService和ConfigService使⽤同⼀个数据库,PortalService使⽤不同的数据库
ApolloConfigDB: Eureka服务URL列表,各环境不同
update apolloconfigbetadb.ServerConfig set ServerConfig.Value="config-p.aa/eureka" where ServerConfig.Key="eureka.service.url";
ApolloPortalDB:调整⽀持多个环境
select * from ServerConfig;
update Serverconfig set Value="dev,fat,uat,pro" where Id=1;
在Kubernetes平台运⾏ConfigService和AdminService、PortalService (版本以:1.5.1)
1)部署ConfigService
第⼀步:制作镜像
startup.sh脚本更改 
[root@master01 scripts]# cat startup.sh
#!/bin/bash
SERVICE_NAME=apollo-configservice
## Adjust log dir if necessary
LOG_DIR=/opt/logs/apollo-config-server
## Adjust server port if necessary
SERVER_PORT=8080
APOLLO_CONFIG_SERVICE_NAME=$(hostname -i)
SERVER_URL="${APOLLO_CONFIG_SERVICE_NAME}:${SERVER_PORT}"
## Adjust memory settings if necessary
#export JAVA_OPTS="-Xms6144m -Xmx6144m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=384m -XX:NewSize=4096m -XX:MaxNewSize=4096m -XX:SurvivorRatio=8"
## Only uncomment the following when you are using server jvm
#export JAVA_OPTS="$JAVA_OPTS -server -XX:-ReduceInitialCardMarks"
>># The following is the same for configservice, adminservice, portal >>#
export JAVA_OPTS="$JAVA_OPTS -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=9 -XX:+DisableExplicitGC -XX:+ScavengeBeforeFullGC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+ExplicitGCInvokesConcurrent -XX:+PrintGCDetails -X export JAVA_OPTS="$JAVA_OPTS -Dserver.port=$SERVER_PORT -Dlogging.file=$LOG_DIR/$SERVICE_NAME.log -XX:HeapDumpPath=$LOG_DIR/HeapDumpOnOutOfMemoryError/"
# Find Java
if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
javaexe="$JAVA_HOME/bin/java"
elif type -p java > /dev/null 2>&1; then
javaexe=$(type -p java)
elif [[ -x "/usr/bin/java" ]];  then
javaexe="/usr/bin/java"
else
echo "Unable to find Java"
exit 1
fi
if [[ "$javaexe" ]]; then
version=$("$javaexe" -version 2>&1 | awk -F '"' '/version/ {print $2}')
version=$(echo "$version" | awk -F. '{printf("%03d%03d",$1,$2);}')spring怎么读取properties
# now version is of format 009003 (9.3.x)
if [ $version -ge 011000 ]; then
JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace"
elif [ $version -ge 010000 ]; then
JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace"
elif [ $version -ge 009000 ]; then
JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace"
else
JAVA_OPTS="$JAVA_OPTS -XX:+UseParNewGC"
JAVA_OPTS="$JAVA_OPTS -Xloggc:$LOG_DIR/gc.log -XX:+PrintGCDetails"
JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=60 -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelR    fi
fi
printf "$(date) ==== Starting ==== \n"
cd `dirname $0`/..
chmod 755 $SERVICE_NAME".jar"
./$SERVICE_NAME".jar" start

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。

发表评论