一/apollo简介
apollo是携程开源的一款企业级配置中心,功能比spring cloud config强大得多,解决配置变更操作特别是多服务多实例部署修改日志级别,促销限制,黑白名单,超时,功能开关等特别麻烦,无法动态变更即时生效并反馈成功与否,改代码重新上线耗时,不便于配置查看,apollo可以解决这些问题,并有权限控制及变更版本管理,统一集中管理不同环境,配置信息监听,开发api平台等功能。代码侵入很小,跟springcloud config差不多,但springcloud config需要依赖GIT操作不友好,设计简单,配置治理能力弱。另有阿里的nacos也是不错的选择。
二/原理图
配置中心会推送apollo客户端,且客户端会定时长轮询拉取最新配置,client端有jvm缓存与本地文件缓存,具有高可用性。Apollo配置中心内部结构图如下图:
- portal是一个主要是web UI工程,它通过SLB软负载或直接根据metaServer地址从eureka找到adminserver暴露的配置CURD接口进行配置修改。
- client端如springboot项目同理在eureka找到configserver暴露的端点进行配置查询。
- adminserver与configserver通过上图双保险进行配置同步。
- 注意:metaserver eureka是集成到configserver中的。
三/安装步骤
Github有详细等学习资源及集成步骤: https://github.com/ctripcorp/apollo。
先到https://github.com/ctripcorp/apollo/tree/master/scripts/sql下载两个数据库脚本安装到mysql,然后进行三个核心模块安装。
方式1:wget zip包解压安装,地址https://github.com/ctripcorp/apollo/releases
方式2:checkout本地打包安装,适合二次开发;可以利用项目的脚本构建镜像push到docker进行容器化安装,参考https://blog.csdn.net/qq_38983728/article/details/90108387
方式3:搜索docker search搜索可靠镜像进行安装,下面已方式3进行安装。
docker run -d \
-e spring_datasource_url=jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8 \
-e spring_datasource_username=root \
-e spring_datasource_password=xxx. \
-e server.port=8180 \
-e eureka.instance.ip-address=1XX.XX.0.205 \
-v /opt/logs:/opt/logs \
--network host \
--name apollo_configserver \
chenchuxin/apollo
docker run -d \
-e spring_datasource_url=jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8 \
-e spring_datasource_username=root \
-e spring_datasource_password=xxx. \
-e server.port=8190 \
-e eureka.instance.ip-address=1XX.XX.0.205 \
-v /opt/logs:/opt/logs \
--network host \
--name apollo_adminserver \
docker.io/akafra/apollo-adminservice
docker run -d \
-e spring_datasource_url=jdbc:mysql://localhost:3306/ApolloPortalDB?characterEncoding=utf8 \
-e spring_datasource_username=root \
-e spring_datasource_password=xxx. \
-e SERVER_PORT=8170 \
-e DEV_META=http://1XX.XX.0.205:8180 \
-v /opt/logs:/opt/logs \
--network host \
--name apollo_portalserver \
docker.io/chrishoo/apollo-portal
注意:
a.默认端口8080 8090 8070改为8180 8190 8170避免冲突,注意修改修改ApolloConfigDB的serverConfig表的eureka的地址改为
configer服务的地址,这里端口应改为8180。(configserver服务包含eureka注册中心及metaserver两个逻辑角色,正常启动后访问localhost:8180可见eureka页面且注册了configserver /adminserver两个服务)
b.configserver adminserver每个环境都要部署,portal只需要一套,注意各自链接的mysql数据库
c.docker inspect可发现前configserver/adminserver容器启动是Java -jar启动,参数-e eureka.instance.ip-address=1XX.XX.0.205 不配置可能导致多网卡环境,portal页面,日志文件报错client端不能正确找到admin/metaserver服务如图instanceID与homepageUrl不一致;portal是利用里面的startup.sh启动,- e DEV_META指定的是eureka地址(即configserver地址).
四/springboot项目集成,即时刷新
1/登录portal :http://localhost:8170 默认超级账号apollo/admin ,创建一个项目,关键是appid,它是一个应用的标识。
然后跳转到配置页面,先报了个“系统错误请联系管理员”?管理员不容易!!!,多刷新几下提示“当前环境有缺失,请点击左侧【补缺环境】”,点了之后就OK了。因为一个项目配置只能属于一个环境,默认只有DEV,需要其他环境在portal数据库表增加。
新增几个测试配置并发布:该配置属于appid=10086,env=DEV,namespace=application(默认)的配置,下面用Java client来获取这些参数并测试实时动态刷新。
springboot增加apollo客户端依赖
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.4.0</version>
</dependency>
配置文件配置文件apollo读取配置中心的url,appid为10086的配置信息,env环境一般在启动命令-Denv=DEV或在/opt/settings/server.properties指定(env=DEV)
app:
id: 10086 # 使用的 Apollo 的项目(应用)编号
apollo:
meta: http://127.0.0.1:8180 # Apollo Meta Server 地址
bootstrap:
enabled: true # 是否开启 Apollo 配置预加载功能。默认为 false。
eagerLoad:
enable: true # 是否开启 Apollo 支持日志级别的加载时机。默认为 false。
namespaces: application,TEST1.apollo # 使用的 Apollo 的命名空间,默认为 application(私有)。
java代码用@Value("${test1:111}")即可注入,并且是实时刷新的,有是我们需要把某些配置统一用一个bean来接收,需要@RefreshScope注解,并用一个监听器实现刷新
@Getter
@Setter
@ToString
@Slf4j
@ConditionalOnProperty("redis.cache.enabled")
@ConfigurationProperties(prefix = "redis.cache")
@Component("sampleRedisProperties") //定个beanName,监听器中根据beanName刷新
@RefreshScope //需要刷新,还需要监听器配合
public class SampleRedisProperties {
private int expireSeconds;
private String clusterNodes;
private int commandTimeout;
@PostConstruct
private void initialize() {
log.info("**************初始化完毕>>>>:{}",this.toString());
}
}
@Slf4j
@ConditionalOnProperty("redis.cache.enabled")
@Component
public class SpringBootApolloRefreshConfig {
@Resource
private SampleRedisProperties sampleRedisProperties;
@Autowired
private RefreshScope refreshScope;
//监听apollo配置,可指定namespace,配置项前缀等
@ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION, "TEST1.apollo"}, interestedKeyPrefixes = {"redis.cache."})
public void onChange(ConfigChangeEvent changeEvent) {
//正式环境变更可考虑发邮件
log.info("监听到配置项变化,{}", changeEvent.changedKeys());
log.info("刷新前,{}", sampleRedisProperties.toString());
refreshScope.refresh("sampleRedisProperties");//使用refreshScope对象刷新bean name为sampleRedisProperties的容器对象
log.info("刷新后,{}", sampleRedisProperties.toString());
}
}
日志级别的动态刷新
@Slf4j
@Component
public class LoggingSystemConfigListener {
private static final String LOGGER_PREFIX = "logging.level.";
@Autowired
private LoggingSystem loggingSystem;
@ApolloConfig
private Config config;
@ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) throws Exception {
log.info("监听到配置项变化,{}",changeEvent.changedKeys());
Set<String> keys = config.getPropertyNames();
for (String key : keys) {
// 如果是 logging.level 配置项,则设置其对应的日志级别
if (key.startsWith(LOGGER_PREFIX)) {
String strLevel = config.getProperty(key, "info");
LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());
loggingSystem.setLogLevel(key.replace(LOGGER_PREFIX, ""), level);
}
}
}
}