微服務(wù)配置中心 - Apollo

微服務(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ù)接口,用于配置屬性源并從它們中解析屬性亏栈。

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ǔ)模型:

  1. 用戶在配置中心對(duì)配置進(jìn)行修改并發(fā)布
  2. 配置中心通知Apollo客戶端有配置更新
  3. 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)查看下面兩篇文章

service notifications

client polling

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ō)明

Apollo使用指南

Java客戶端使用指南

最佳實(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)用襟锐,并使最新配置生效
  • 項(xiàng)目管理員管理權(quán)限界面

[圖片上傳失敗...(image-a712b5-1574608566483)]

項(xiàng)目創(chuàng)建完撤逢,默認(rèn)沒(méi)有分配配置的編輯和發(fā)布權(quán)限,需要項(xiàng)目管理員進(jìn)行授權(quán)。

  1. 點(diǎn)擊application這個(gè)namespace的授權(quán)按鈕

[圖片上傳失敗...(image-8c5619-1574608566483)]

  1. 分配修改權(quán)限

[圖片上傳失敗...(image-7fe8ac-1574608566483)]

  1. 分配發(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 就是私有類型扩然。

  1. 使用場(chǎng)景
  • 服務(wù)自身的配置(如數(shù)據(jù)庫(kù)艘儒、業(yè)務(wù)行為等配置)
  1. 如何使用私有類型 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 的名稱必須全局唯一。

  1. 使用場(chǎng)景
  • 部門級(jí)別共享的配置
  • 小組級(jí)別共享的配置
  • 幾個(gè)項(xiàng)目之間共享的配置
  • 中間件客戶端的配置
  1. 如何使用公共類型 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 源碼解析

Apollo源碼解析(全)

[圖片上傳失敗...(image-d18ff6-1574608566483)]

其他配置中心說(shuō)明

  • 待補(bǔ)充
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舶沿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子配并,更是在濱河造成了極大的恐慌括荡,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溉旋,死亡現(xiàn)場(chǎng)離奇詭異畸冲,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門邑闲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)岩喷,“玉大人,你說(shuō)我怎么就攤上這事监憎∩匆猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵鲸阔,是天一觀的道長(zhǎng)偷霉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)褐筛,這世上最難降的妖魔是什么类少? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮渔扎,結(jié)果婚禮上硫狞,老公的妹妹穿的比我還像新娘。我一直安慰自己晃痴,他們只是感情好残吩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著倘核,像睡著了一般泣侮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上紧唱,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天活尊,我揣著相機(jī)與錄音,去河邊找鬼漏益。 笑死蛹锰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绰疤。 我是一名探鬼主播铜犬,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼峦睡!你這毒婦竟也來(lái)了翎苫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤榨了,失蹤者是張志新(化名)和其女友劉穎煎谍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體龙屉,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呐粘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年满俗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片作岖。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唆垃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出痘儡,到底是詐尸還是另有隱情辕万,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布沉删,位于F島的核電站渐尿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏矾瑰。R本人自食惡果不足惜砖茸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望殴穴。 院中可真熱鬧凉夯,春花似錦、人聲如沸采幌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)植榕。三九已至再沧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尊残,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工淤堵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寝衫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓拐邪,卻偏偏與公主長(zhǎng)得像慰毅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扎阶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345