随着业务的发展、微服务架构的升级,服务的数量、程序的配置日益增多(各种微服务、各种服务器地址、各种参数),传统的配置文件方式和数据库的方式已无法满足开发人员对配置管理的要求:配置修改后实时生效,灰度发布,分环境、分集群管理配置,完善的权限、审核机制。分布式环境下,这些配置更加复杂。
因此,我们需要配置中心来统一管理配置!把业务开发者从复杂以及繁琐的配置中解脱出来,只需专注于业务代码本身,从而能够显著提升开发以及运维效率。同时将配置和发布包解藕也进一步提升发布的成功率,并为运维的细力度管控、应急处理等提供强有力的支持。
在之前的文章中,我们介绍过 Spring Cloud 中的分布式配置中心组件:Spring Cloud Config。本文将会介绍功能更为强大的 Apollo。
在一个分布式环境中,同类型的服务往往会部署很多实例。这些实例使用了一些配置,为了更好地维护这些配置就产生了配置管理服务。通过这个服务可以轻松地管理成千上百个服务实例的配置问题。配置中心的特点:
现有的配置中心组件有:Spring Cloud Config、Apollo、Disconf、Diamond 等等,这些组件在功能上有或多或少的差异,但是都具有基本的配置中心的功能。
Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。目前的有超过 14k 的 star,使用广泛。Apollo基于开源模式开发,开源地址: https://github.com/ctripcorp/apollo。
首先用户在配置中心对配置进行修改并发布;配置中心通知Apollo客户端有配置更新;Apollo客户端从配置中心拉取最新的配置、更新本地配置并通知到应用。
Apollo 支持4个维度管理 Key-Value 格式的配置:
我们在集成 Apollo 时,可以根据需要创建相应的维度。
下面我们搭建一个基于 Spring Boot 的微服务,集成 Apollo。
Apollo配置中心包括:Config Service、Admin Service 和 Portal。
官网准备好了一个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 服务端启动成功。
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.1.0</version>
</dependency>
@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
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;
}
}
@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的总体设计,我们可以从下往上看: