微服務(wù)配置中心 Apollo 使用指南贼穆,以下文檔根據(jù) apollo wiki 整理而來(lái)煌贴,部分最佳實(shí)踐說(shuō)明和代碼改造基于筆者的工作經(jīng)驗(yàn)整理而來(lái)逗余,如有問(wèn)題歡迎溝通。
配置中心
在拆分為微服務(wù)架構(gòu)前泽腮,曾經(jīng)的單體應(yīng)用只需要管理一套配置。而拆分為微服務(wù)后衣赶,每一個(gè)系統(tǒng)都有自己的配置诊赊,并且都各不相同,而且因?yàn)榉?wù)治理的需要府瞄,有些配置還需要能夠動(dòng)態(tài)改變碧磅,如業(yè)務(wù)參數(shù)調(diào)整或需要熔斷限流等功能,配置中心就是解決這個(gè)問(wèn)題的遵馆。
配置的基本概念
- 配置是獨(dú)立于程序的只讀變量
- 同個(gè)應(yīng)用在不同的配置有不同的行為
- 應(yīng)用不應(yīng)該改變配置
- 配置伴隨應(yīng)用的整個(gè)生命周期
- 初始化參數(shù)和運(yùn)行參數(shù)
- 配置可以有多種加載方式
- 配置需要治理
- 權(quán)限控制(應(yīng)用級(jí)別续崖、編輯發(fā)布隔離等)
- 多環(huán)境集群配置管理
- 框架類組件配置管理
配置中心
- 配置注冊(cè)與反注冊(cè)
- 配置治理
- 配置變更訂閱
Spring Environment
Environment 是 Spring 容器中對(duì)于應(yīng)用環(huán)境兩個(gè)關(guān)鍵因素(profile & properties)的一個(gè)抽象。
- profile
profile 是一個(gè)邏輯的分組团搞,當(dāng) bean 向容器中注冊(cè)的時(shí)候严望,僅當(dāng)配置激活時(shí)生效。
## 配置文件使用
spring.profiles.active=xxx
## 硬編碼注解形式使用
@org.springframework.context.annotation.Profile
- properties
Properties 在幾乎所有應(yīng)用程序中都扮演著重要的角色逻恐,并且可能來(lái)自各種各樣的來(lái)源:properties 文件像吻、JVM系統(tǒng)屬性、系統(tǒng)環(huán)境變量复隆、JNDI拨匆、Servlet Context 參數(shù)、ad-hoc Properties 對(duì)象挽拂、Map 等等惭每。Environment 與 Properties 的關(guān)系是為用戶提供一個(gè)方便的服務(wù)接口,用于配置屬性源并從它們中解析屬性亏栈。
- Spring 中的擴(kuò)展點(diǎn)
- spring framework 提供了便捷的方式添加自定義數(shù)據(jù)源策略添加到 Spring Enviroment 中台腥,如 @PropertySource。beans-using-propertysource
- spring boot 提供了相關(guān)的擴(kuò)展方式绒北,如 EnviromentPostProcessor 相關(guān)的黎侈。boot-features-external-config
- spring boot 同時(shí)也提供在開始之前自定義環(huán)境擴(kuò)展。howto-customize-the-environment-or-application-context
Apollo 簡(jiǎn)介
簡(jiǎn)介
Apollo(阿波羅)是攜程框架部門研發(fā)的開源配置管理中心闷游,能夠集中化管理應(yīng)用不同環(huán)境峻汉、不同集群的配置,配置修改后能夠?qū)崟r(shí)推送到應(yīng)用端脐往,并且具備規(guī)范的權(quán)限休吠、流程治理等特性。
Apollo 支持4個(gè)維度管理 Key-Value 格式的配置:
- application (應(yīng)用)
這個(gè)很好理解业簿,就是實(shí)際使用配置的應(yīng)用瘤礁,Apollo 客戶端在運(yùn)行時(shí)需要知道當(dāng)前應(yīng)用是誰(shuí),從而可以去獲取對(duì)應(yīng)的配置辖源。每個(gè)應(yīng)用都需要有唯一的身份標(biāo)識(shí)蔚携,我們認(rèn)為應(yīng)用身份是跟著代碼走的希太,所以需要在代碼中配置克饶,具體信息請(qǐng)參見 Java 客戶端使用指南酝蜒。
- environment (環(huán)境)
配置對(duì)應(yīng)的環(huán)境,Apollo 客戶端在運(yùn)行時(shí)需要知道當(dāng)前應(yīng)用處于哪個(gè)環(huán)境矾湃,從而可以去獲取應(yīng)用的配置亡脑。我們認(rèn)為環(huán)境和代碼無(wú)關(guān),同一份代碼部署在不同的環(huán)境就應(yīng)該能夠獲取到不同環(huán)境的配置邀跃,所以環(huán)境默認(rèn)是通過(guò)讀取機(jī)器上的配置(server.properties中的env屬性)指定的霉咨,不過(guò)為了開發(fā)方便,我們也支持運(yùn)行時(shí)通過(guò) System Property 等指定拍屑,具體信息請(qǐng)參見Java客戶端使用指南途戒。
- cluster (集群)
一個(gè)應(yīng)用下不同實(shí)例的分組,比如典型的可以按照數(shù)據(jù)中心分僵驰,把上海機(jī)房的應(yīng)用實(shí)例分為一個(gè)集群喷斋,把北京機(jī)房的應(yīng)用實(shí)例分為另一個(gè)集群。對(duì)不同的cluster蒜茴,同一個(gè)配置可以有不一樣的值星爪,如 zookeeper 地址。集群默認(rèn)是通過(guò)讀取機(jī)器上的配置(server.properties中的idc屬性)指定的粉私,不過(guò)也支持運(yùn)行時(shí)通過(guò) System Property 指定顽腾,具體信息請(qǐng)參見Java客戶端使用指南。
- namespace (命名空間)
一個(gè)應(yīng)用下不同配置的分組诺核,可以簡(jiǎn)單地把 namespace 類比為文件抄肖,不同類型的配置存放在不同的文件中,如數(shù)據(jù)庫(kù)配置文件窖杀,RPC配置文件憎瘸,應(yīng)用自身的配置文件等。應(yīng)用可以直接讀取到公共組件的配置 namespace陈瘦,如 DAL幌甘,RPC 等。應(yīng)用也可以通過(guò)繼承公共組件的配置 namespace 來(lái)對(duì)公共組件的配置做調(diào)整痊项,如DAL的初始數(shù)據(jù)庫(kù)連接數(shù)锅风。
同時(shí),Apollo 基于開源模式開發(fā)鞍泉,開源地址:https://github.com/ctripcorp/apollo
基礎(chǔ)模型
如下即是Apollo的基礎(chǔ)模型:
- 用戶在配置中心對(duì)配置進(jìn)行修改并發(fā)布
- 配置中心通知Apollo客戶端有配置更新
- Apollo客戶端從配置中心拉取最新的配置皱埠、更新本地配置并通知到應(yīng)用
[圖片上傳失敗...(image-cba2c1-1574608566483)]
Apollo 架構(gòu)說(shuō)明
Apollo 項(xiàng)目本身就使用了 Spring Boot & Spring Cloud 開發(fā)。
服務(wù)端
[圖片上傳失敗...(image-5c37e7-1574608566483)]
上圖簡(jiǎn)要描述了Apollo的總體設(shè)計(jì)咖驮,我們可以從下往上看:
- Config Service 提供配置的讀取边器、推送等功能训枢,服務(wù)對(duì)象是Apollo客戶端。
- Admin Service 提供配置的修改忘巧、發(fā)布等功能恒界,服務(wù)對(duì)象是Apollo Portal(管理界面)。
- Config Service 和 Admin Service 都是多實(shí)例砚嘴、無(wú)狀態(tài)部署十酣,所以需要將自己注冊(cè)到 Eureka 中并保持心跳
- 在 Eureka 之上我們架了一層 Meta Server 用于封裝 Eureka 的服務(wù)發(fā)現(xiàn)接口
Client 通過(guò)域名訪問(wèn) Meta Server 獲取 Config Service 服務(wù)列表(IP+Port),而后直接通過(guò) IP+Port 訪問(wèn)服務(wù)际长,同時(shí)在 Client 側(cè)會(huì)做 load balance耸采、錯(cuò)誤重試 - Portal 通過(guò)域名訪問(wèn) Meta Server 獲取 Admin Service 服務(wù)列表(IP+Port),而后直接通過(guò) IP+Port 訪問(wèn)服務(wù)工育,同時(shí)在 Portal 側(cè)會(huì)做 load balance虾宇、錯(cuò)誤重試
- 為了簡(jiǎn)化部署,我們實(shí)際上會(huì)把 Config Service如绸、Eureka 和 Meta Server 三個(gè)邏輯角色部署在同一個(gè) JVM 進(jìn)程中嘱朽。
客戶端
[圖片上傳失敗...(image-90f7fa-1574608566483)]
- 客戶端和服務(wù)端保持了一個(gè)長(zhǎng)連接,從而能第一時(shí)間獲得配置更新的推送竭沫。
- 客戶端還會(huì)定時(shí)從 Apollo 配置中心服務(wù)端拉取應(yīng)用的最新配置燥翅。
- 這是一個(gè)fallback機(jī)制,為了防止推送機(jī)制失效導(dǎo)致配置不更新
- 客戶端定時(shí)拉取會(huì)上報(bào)本地版本蜕提,所以一般情況下森书,對(duì)于定時(shí)拉取的操作,服務(wù)端都會(huì)返回304 - Not Modified
- 定時(shí)頻率默認(rèn)為每5分鐘拉取一次,客戶端也可以通過(guò)在運(yùn)行時(shí)指定 System Property: apollo.refreshInterval 來(lái)覆蓋,單位為分鐘腿宰。
- 客戶端從Apollo配置中心服務(wù)端獲取到應(yīng)用的最新配置后,會(huì)保存在內(nèi)存中
- 客戶端會(huì)把從服務(wù)端獲取到的配置在本地文件系統(tǒng)緩存一份
- 在遇到服務(wù)不可用猖毫,或網(wǎng)絡(luò)不通的時(shí)候,依然能從本地恢復(fù)配置
- 應(yīng)用程序從Apollo客戶端獲取最新的配置须喂、訂閱配置更新通知
長(zhǎng)連接實(shí)現(xiàn)上是使用的異步+輪詢實(shí)現(xiàn)吁断,具體實(shí)現(xiàn)的解析請(qǐng)查看下面兩篇文章
Apollo 高可用部署
在 Apollo 架構(gòu)說(shuō)明中我們提到過(guò) client 和 portal 都是在客戶端負(fù)載均衡,根據(jù) ip+port 訪問(wèn)服務(wù)坞生,所以 config service 和 admin service 是無(wú)狀態(tài)的仔役,可以水平擴(kuò)展的,portal service 根據(jù)使用 slb 綁定多臺(tái)服務(wù)器達(dá)到切換是己,meta server 同理又兵。
場(chǎng)景 | 影響 | 降級(jí) | 原因 |
---|---|---|---|
某臺(tái)config service下線 | 無(wú)影響 | Config service無(wú)狀態(tài),客戶端重連其它c(diǎn)onfig service | |
所有config service下線 | 客戶端無(wú)法讀取最新配置卒废,Portal無(wú)影響 | 客戶端重啟時(shí),可以讀取本地緩存配置文件 | |
某臺(tái)admin service下線 | 無(wú)影響 | Admin service無(wú)狀態(tài)沛厨,Portal重連其它admin service | |
所有admin service下線 | 客戶端無(wú)影響宙地,portal無(wú)法更新配置 | ||
某臺(tái)portal下線 | 無(wú)影響 | Portal域名通過(guò)slb綁定多臺(tái)服務(wù)器,重試后指向可用的服務(wù)器 | |
全部portal下線 | 客戶端無(wú)影響逆皮,portal無(wú)法更新配置 | ||
某個(gè)數(shù)據(jù)中心下線 | 無(wú)影響 | 多數(shù)據(jù)中心部署宅粥,數(shù)據(jù)完全同步,Meta Server/Portal域名通過(guò)slb自動(dòng)切換到其它存活的數(shù)據(jù)中心 |
Apollo 使用說(shuō)明
使用說(shuō)明
最佳實(shí)踐
在 Spring Boot & Spring Cloud 中使用页屠。
- 每個(gè)應(yīng)用都需要有唯一的身份標(biāo)識(shí)粹胯,我們認(rèn)為應(yīng)用身份是跟著代碼走的蓖柔,所以需要在代碼中配置辰企。關(guān)于應(yīng)用身份標(biāo)識(shí),應(yīng)用標(biāo)識(shí)對(duì)第三方中間件應(yīng)該是統(tǒng)一的况鸣,擴(kuò)展支持 apollo 身份標(biāo)識(shí)和 spring.application.name 一致(具體查看 fusion-config-apollo 中代碼)牢贸,其他中間件同理。
- 應(yīng)用開發(fā)過(guò)程中如使用代碼中的配置镐捧,應(yīng)該充分利用 Spring Environment Profile潜索,增加本地邏輯分組 local,非開發(fā)階段關(guān)閉 local 邏輯分組懂酱。同時(shí)關(guān)閉 apollo 遠(yuǎn)程獲取配置竹习,在 VM options 中增加 -Denv=local。
[圖片上傳失敗...(image-cd05e0-1574608566483)]
以下代碼是擴(kuò)展 apollo 應(yīng)用標(biāo)識(shí)使用 spring.application.name列牺,并增加監(jiān)控配置整陌,監(jiān)控一般是基礎(chǔ)架構(gòu)團(tuán)隊(duì)提供的功能,從基礎(chǔ)框架硬編碼上去瞎领,業(yè)務(wù)側(cè)做到完全無(wú)感知泌辫。
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants;
import com.ctrip.framework.foundation.internals.io.BOMInputStream;
import com.ctrip.framework.foundation.internals.provider.DefaultApplicationProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import java.util.Set;
/**
* ApolloSpringApplicationRunListener
* <p>
* SpringApplicationRunListener
* 接口說(shuō)明 https://blog.csdn.net/u011179993/article/details/51555690https://blog.csdn.net/u011179993/article/details/51555690
*
* @author Weichao Li (liweichao0102@gmail.com)
* @since 2019-08-15
*/
@Order(value = ApolloSpringApplicationRunListener.APOLLO_SPRING_APPLICATION_RUN_LISTENER_ORDER)
@Slf4j
public class ApolloSpringApplicationRunListener implements SpringApplicationRunListener {
public static final int APOLLO_SPRING_APPLICATION_RUN_LISTENER_ORDER = 1;
private static final String APOLLO_APP_ID_KEY = "app.id";
private static final String SPRINGBOOT_APPLICATION_NAME = "spring.application.name";
private static final String CONFIG_CENTER_INFRA_NAMESPACE = "infra.monitor";
public ApolloSpringApplicationRunListener(SpringApplication application, String[] args) {
}
/**
* 剛執(zhí)行run方法時(shí)
*/
@Override
public void starting() {
}
/**
* 環(huán)境建立好時(shí)候
*
* @param env 環(huán)境信息
*/
@Override
public void environmentPrepared(ConfigurableEnvironment env) {
Properties props = new Properties();
props.put(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, true);
props.put(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, true);
env.getPropertySources().addFirst(new PropertiesPropertySource("apolloConfig", props));
// 初始化appId
this.initAppId(env);
// 初始化基礎(chǔ)架構(gòu)提供的默認(rèn)配置,需在項(xiàng)目中關(guān)聯(lián)公共 namespaces
this.initInfraConfig(env);
}
/**
* 上下文建立好的時(shí)候
*
* @param context 上下文
*/
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
/**
* 上下文載入配置時(shí)候
*
* @param context 上下文
*/
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
}
@Override
public void started(ConfigurableApplicationContext context) {
}
@Override
public void running(ConfigurableApplicationContext context) {
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
}
/**
* 初始化 apollo appId
*
* @param env 環(huán)境信息
*/
private void initAppId(ConfigurableEnvironment env) {
String apolloAppId = env.getProperty(APOLLO_APP_ID_KEY);
if (StringUtils.isEmpty(apolloAppId)) {
//此處需要判斷一下 meta-inf 下的文件中的 app id
apolloAppId = getAppIdByAppPropertiesClasspath();
if (StringUtils.isEmpty(apolloAppId)) {
String applicationName = env.getProperty(SPRINGBOOT_APPLICATION_NAME);
if (!StringUtils.isEmpty(applicationName)) {
System.setProperty(APOLLO_APP_ID_KEY, applicationName);
} else {
throw new IllegalArgumentException(
"config center must config app.id in " + DefaultApplicationProvider.APP_PROPERTIES_CLASSPATH);
}
} else {
System.setProperty(APOLLO_APP_ID_KEY, apolloAppId);
}
} else {
System.setProperty(APOLLO_APP_ID_KEY, apolloAppId);
}
}
/**
* 初始化基礎(chǔ)架構(gòu)提供的配置
*
* @param env 環(huán)境信息
*/
private void initInfraConfig(ConfigurableEnvironment env) {
com.ctrip.framework.apollo.Config apolloConfig = ConfigService.getConfig(CONFIG_CENTER_INFRA_NAMESPACE);
Set<String> propertyNames = apolloConfig.getPropertyNames();
if (propertyNames != null && propertyNames.size() > 0) {
Properties properties = new Properties();
for (String propertyName : propertyNames) {
properties.setProperty(propertyName, apolloConfig.getProperty(propertyName, null));
}
EnumerablePropertySource enumerablePropertySource =
new PropertiesPropertySource(CONFIG_CENTER_INFRA_NAMESPACE, properties);
env.getPropertySources().addLast(enumerablePropertySource);
}
}
/**
* 從 apollo 默認(rèn)配置文件中取 app.id 的值九默,調(diào)整優(yōu)先級(jí)在 spring.application.name 之前
*
* @return apollo app id
*/
private String getAppIdByAppPropertiesClasspath() {
try {
InputStream in = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(DefaultApplicationProvider.APP_PROPERTIES_CLASSPATH);
if (in == null) {
in = DefaultApplicationProvider.class
.getResourceAsStream(DefaultApplicationProvider.APP_PROPERTIES_CLASSPATH);
}
Properties properties = new Properties();
if (in != null) {
try {
properties.load(new InputStreamReader(new BOMInputStream(in), StandardCharsets.UTF_8));
} finally {
in.close();
}
}
if (properties.containsKey(APOLLO_APP_ID_KEY)) {
String appId = properties.getProperty(APOLLO_APP_ID_KEY);
log.info("App ID is set to {} by app.id property from {}", appId, DefaultApplicationProvider.APP_PROPERTIES_CLASSPATH);
return appId;
}
} catch (Throwable ignore) {
}
return null;
}
}
動(dòng)態(tài)刷新
支持 Apollo 配置自動(dòng)刷新類型震放,支持 @Value @RefreshScope @ConfigurationProperties 以及日志級(jí)別的動(dòng)態(tài)刷新。具體代碼查看下文鏈接驼修。
- @Value
@Value Apollo 本身就支持了動(dòng)態(tài)刷新殿遂,需要注意的是如果@Value 使用了 SpEL 表達(dá)式,動(dòng)態(tài)刷新會(huì)失效乙各。
// 支持動(dòng)態(tài)刷新
@Value("${simple.xxx}")
private String simpleXxx;
// 不支持動(dòng)態(tài)刷新
@Value("#{'${simple.xxx}'.split(',')}")
private List<String> simpleXxxs;
- @RefreshScope
RefreshScope(org.springframework.cloud.context.scope.refresh)是 Spring Cloud 提供的一種特殊的 scope 實(shí)現(xiàn)墨礁,用來(lái)實(shí)現(xiàn)配置、實(shí)例熱加載觅丰。
動(dòng)態(tài)實(shí)現(xiàn)過(guò)程:
配置變更時(shí)饵溅,調(diào)用 refreshScope.refreshAll() 或指定 bean。提取標(biāo)準(zhǔn)參數(shù)(System,jndi,Servlet)之外所有參數(shù)變量妇萄,把原來(lái)的Environment里的參數(shù)放到一個(gè)新建的 Spring Context 容器下重新加載蜕企,完事之后關(guān)閉新容器咬荷。提取更新過(guò)的參數(shù)(排除標(biāo)準(zhǔn)參數(shù))
,比較出變更項(xiàng)轻掩,發(fā)布環(huán)境變更事件幸乒,RefreshScope 用新的環(huán)境參數(shù)重新生成Bean。重新生成的過(guò)程很簡(jiǎn)單唇牧,清除 refreshscope 緩存幷銷毀 Bean罕扎,下次就會(huì)重新從 BeanFactory 獲取一個(gè)新的實(shí)例(該實(shí)例使用新的配置)。
- @ConfigurationProperties
apollo 默認(rèn)是不支持 ConfigurationProperties 刷新的丐重,這塊需要配合 EnvironmentChangeEvent 刷新的腔召。
- 日志級(jí)別
apollo 默認(rèn)是不支持日志級(jí)別刷新的,這塊需要配合 EnvironmentChangeEvent 刷新的扮惦。
- EnvironmentChangeEvent(Spring Cloud 提供)
當(dāng)觀察到 EnvironmentChangeEvent 時(shí)臀蛛,它將有一個(gè)已更改的鍵值列表,應(yīng)用程序?qū)⑹褂靡韵聝?nèi)容:
1崖蜜,重新綁定上下文中的任何 @ConfigurationProperties bean浊仆,代碼見org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder。
2豫领,為logging.level.*中的任何屬性設(shè)置記錄器級(jí)別抡柿,代碼見 org.springframework.cloud.logging.LoggingRebinder。
支持動(dòng)態(tài)刷新
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
/**
* LoggerConfiguration
*
* @author Weichao Li (liweichao0102@gmail.com)
* @since 2019/11/14
*/
@Configuration
@Slf4j
public class ApolloRefreshConfiguration implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Autowired
private RefreshScope refreshScope;
@ApolloConfigChangeListener
private void onChange(ConfigChangeEvent changeEvent) {
applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
refreshScope.refreshAll();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
注意原有配置如果有日志級(jí)別需要初始化等恐。
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.Set;
/**
* logging 初始化
*
* @author Weichao Li (liweichao0102@gmail.com)
* @since 2019/11/14
*/
@Configuration
@Slf4j
public class LoggingConfiguration {
private static final String LOGGER_TAG = "logging.level.";
private static final String DEFAULT_LOGGING_LEVEL = "info";
@Autowired
private LoggingSystem loggingSystem;
@ApolloConfig
private Config config;
@PostConstruct
public void changeLoggingLevel() {
Set<String> keyNames = config.getPropertyNames();
for (String key : keyNames) {
if (containsIgnoreCase(key, LOGGER_TAG)) {
String strLevel = config.getProperty(key, DEFAULT_LOGGING_LEVEL);
LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());
loggingSystem.setLogLevel(key.replace(LOGGER_TAG, ""), level);
}
}
}
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 最佳實(shí)踐 - 配置治理
權(quán)限控制
由于配置能改變程序的行為洲劣,不正確的配置甚至能引起災(zāi)難,所以對(duì)配置的修改必須有比較完善的權(quán)限控制鼠锈。應(yīng)用和配置的管理都有完善的權(quán)限管理機(jī)制闪檬,對(duì)配置的管理還分為了編輯和發(fā)布兩個(gè)環(huán)節(jié),從而減少人為的錯(cuò)誤购笆。所有的操作都有審計(jì)日志粗悯,可以方便地追蹤問(wèn)題
- everyone 要有自己的賬戶(最主要的前置條件)
- 每一個(gè)項(xiàng)目都至少有一個(gè) owner(項(xiàng)目管理員,項(xiàng)目管理員擁有以下權(quán)限)
- 可以管理項(xiàng)目的權(quán)限分配
- 可以創(chuàng)建集群
- 可以創(chuàng)建 Namespace
- 項(xiàng)目管理員(owner)根據(jù)組織結(jié)構(gòu)分配配置權(quán)限
- 編輯權(quán)限允許用戶在 Apollo 界面上創(chuàng)建同欠、修改样傍、刪除配置
- 配置修改后只在 Apollo 界面上變化,不會(huì)影響到應(yīng)用實(shí)際使用的配置
- 發(fā)布權(quán)限允許用戶在 Apollo 界面上發(fā)布铺遂、回滾配置
- 配置只有在發(fā)布衫哥、回滾動(dòng)作后才會(huì)被應(yīng)用實(shí)際使用到
- Apollo在用戶操作發(fā)布、回滾動(dòng)作后實(shí)時(shí)通知到應(yīng)用襟锐,并使最新配置生效
- 編輯權(quán)限允許用戶在 Apollo 界面上創(chuàng)建同欠、修改样傍、刪除配置
- 項(xiàng)目管理員管理權(quán)限界面
[圖片上傳失敗...(image-a712b5-1574608566483)]
項(xiàng)目創(chuàng)建完撤逢,默認(rèn)沒(méi)有分配配置的編輯和發(fā)布權(quán)限,需要項(xiàng)目管理員進(jìn)行授權(quán)。
- 點(diǎn)擊application這個(gè)namespace的授權(quán)按鈕
[圖片上傳失敗...(image-8c5619-1574608566483)]
- 分配修改權(quán)限
[圖片上傳失敗...(image-7fe8ac-1574608566483)]
- 分配發(fā)布權(quán)限
[圖片上傳失敗...(image-c7ab5c-1574608566483)]
Namespace
Namespace 權(quán)限分類
apollo 獲取權(quán)限分類分為私有的和公共的蚊荣。
- private (私有的)
private權(quán)限的Namespace初狰,只能被所屬的應(yīng)用獲取到。一個(gè)應(yīng)用嘗試獲取其它應(yīng)用private的Namespace互例,Apollo會(huì)報(bào)“404”異常奢入。
- public (公共的)
public權(quán)限的Namespace,能被任何應(yīng)用獲取媳叨。
Namespace 的分類
Namespace 有三種類型腥光,私有類型,公共類型糊秆,關(guān)聯(lián)類型(繼承類型)武福。
Apollo 私有類型 Namespace 使用說(shuō)明
私有類型的 Namespace 具有 private 權(quán)限。例如服務(wù)默認(rèn)的“application” Namespace 就是私有類型扩然。
- 使用場(chǎng)景
- 服務(wù)自身的配置(如數(shù)據(jù)庫(kù)艘儒、業(yè)務(wù)行為等配置)
- 如何使用私有類型 Namespace
一個(gè)應(yīng)用下不同配置的分組聋伦,可以簡(jiǎn)單地把namespace類比為文件夫偶,不同類型的配置存放在不同的文件中,如數(shù)據(jù)庫(kù)配置文件觉增,業(yè)務(wù)屬性配置兵拢,配置文件等
Apollo 公共類型 Namespace 使用說(shuō)明
公共類型的 Namespace 具有 public 權(quán)限。公共類型的 Namespace 相當(dāng)于游離于應(yīng)用之外的配置逾礁,且通過(guò) Namespace 的名稱去標(biāo)識(shí)公共 Namespace说铃,所以公共的 Namespace 的名稱必須全局唯一。
- 使用場(chǎng)景
- 部門級(jí)別共享的配置
- 小組級(jí)別共享的配置
- 幾個(gè)項(xiàng)目之間共享的配置
- 中間件客戶端的配置
- 如何使用公共類型 Namespace
- 代碼侵入型
@EnableApolloConfig({"application", "poizon-infra.jaeger"})
- 配置方式形式
# will inject 'application' namespace in bootstrap phase
apollo.bootstrap.enabled = true
# will inject 'application', 'poizon-infra.jaeger' namespaces in bootstrap phase
apollo.bootstrap.namespaces = application,poizon-infra.jaeger
Apollo 關(guān)聯(lián)類型 Namespace 使用說(shuō)明
關(guān)聯(lián)類型又可稱為繼承類型嘹履,關(guān)聯(lián)類型具有 private 權(quán)限腻扇。關(guān)聯(lián)類型的 Namespace 繼承于公共類型的 Namespace,用于覆蓋公共 Namespace 的某些配置砾嫉。
使用建議
- 基礎(chǔ)框架部分的統(tǒng)一配置幼苛,如 DAL 的常用配置
- 基礎(chǔ)架構(gòu)的公共組件的配置,如監(jiān)控焕刮,熔斷等公共組件配置
Apollo 源碼解析
[圖片上傳失敗...(image-d18ff6-1574608566483)]
其他配置中心說(shuō)明
- 待補(bǔ)充