精华内容
下载资源
问答
  • Apollo配置中心与本地配置优先级
    千次阅读
    2021-08-28 15:31:41

    背景

    在项目重构时,删除若干个application-{env}.yml文件,仅保留一个application.yml文件,该文件中保留的配置项都是几乎不会变更的配置,至于需要跟随不同环境而变更的配置项都放置在Apollo配置中心。

    然后本地application.yml文件里面有一个静态配置,调用外部接口的URL:https://graph.facebook.com/v9.0/aaaaa,有一天没有得到事前通知(至少研发同事都没人注意到),这个v9.0版本的API被废弃(deprecated),进而引发严重的生产问题。
    方案:

    1. 修改本地配置,然后重新部署应用;
    2. 添加Apollo配置。

    故而引出问题:本地配置和Apollo配置的优先级关系是怎样的?方案2的预设前提是:Apollo的优先级高于本地配置应用,并且可以自动下发推送到客户端,即无需重新部署应用。

    调研

    Apollo使用的Spring @Value注解为字段注入值,那么Apollo与yml同时存在相同配置时,以谁为准?Apollo官网有此解释,在 3.1 和Spring集成的原理,结论是优先读取Apollo的配置,Apollo中没有的读取其他的。官网解释如下:

    Apollo除了支持API方式获取配置,也支持和Spring/Spring Boot集成,集成原理简述如下。

    Spring从3.1版本开始增加ConfigurableEnvironment和PropertySource:
    ConfigurableEnvironment
    Spring的ApplicationContext会包含一个Environment(实现ConfigurableEnvironment接口)
    ConfigurableEnvironment自身包含了很多个PropertySource
    PropertySource
    属性源
    可以理解为很多个Key - Value的属性配置
    在运行时的结构形如:

    Application Context
    	Environment
    		PropertySources
    			PropertySource1
    			PropertySource2
    

    PropertySource之间是有优先级顺序的,如果有一个Key在多个property source中都存在,在前面的property source优先。

    基于此原理后,Apollo和Spring/SB集成的方案就是:在应用启动阶段,Apollo从远端获取配置,然后组装成PropertySource并插入到第一个即可,即 Remote Property Source

    源码

    package com.ctrip.framework.apollo.spring.config;
    
    import com.ctrip.framework.apollo.build.ApolloInjector;
    import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
    import com.ctrip.framework.apollo.spring.util.SpringInjector;
    import com.ctrip.framework.apollo.util.ConfigUtil;
    import com.google.common.collect.ImmutableSortedSet;
    import com.google.common.collect.LinkedHashMultimap;
    import com.google.common.collect.Multimap;
    import com.ctrip.framework.apollo.Config;
    import com.ctrip.framework.apollo.ConfigService;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.core.Ordered;
    import org.springframework.core.PriorityOrdered;
    import org.springframework.core.env.CompositePropertySource;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.core.env.Environment;
    import org.springframework.core.env.MutablePropertySources;
    import org.springframework.core.env.PropertySource;
    
    /**
     * Apollo Property Sources processor for Spring Annotation Based Application. <br /> <br />
     *
     * The reason why PropertySourcesProcessor implements {@link BeanFactoryPostProcessor} instead of
     * {@link org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor} is that lower versions of
     * Spring (e.g. 3.1.1) doesn't support registering BeanDefinitionRegistryPostProcessor in ImportBeanDefinitionRegistrar
     * - {@link com.ctrip.framework.apollo.spring.annotation.ApolloConfigRegistrar}
     *
     * @author Jason Song(song_s@ctrip.com)
     */
    public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
    	private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create();
    	private static final Set<BeanFactory> AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES = Sets.newConcurrentHashSet();
    
    	private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);
    	
    	private ConfigUtil configUtil;
    	
    	private ConfigurableEnvironment environment;
    
    	public static boolean addNamespaces(Collection<String> namespaces, int order) {
    		return NAMESPACE_NAMES.putAll(order, namespaces);
    	}
    
    	@Override
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    		this.configUtil = ApolloInjector.getInstance(ConfigUtil.class);
    		initializePropertySources();
    		initializeAutoUpdatePropertiesFeature(beanFactory);
    	}
    
    	private void initializePropertySources() {
    		if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
    			//already initialized
    			return;
    		}
    		CompositePropertySource composite;
    		if (configUtil.isPropertyNamesCacheEnabled()) {
    			composite = new CachedCompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);
    		} else {
    			composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);
    		}
    
    		//sort by order asc
    		ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());
    		Iterator<Integer> iterator = orders.iterator();
    
    		while (iterator.hasNext()) {
    			int order = iterator.next();
    			for (String namespace : NAMESPACE_NAMES.get(order)) {
    				Config config = ConfigService.getConfig(namespace);
    				composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
    			}
    		}
    
    		// clean up
    		NAMESPACE_NAMES.clear();
    
    		// add after the bootstrap property source or to the first
    		if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
    			// ensure ApolloBootstrapPropertySources is still the first
    			ensureBootstrapPropertyPrecedence(environment);
    			environment.getPropertySources().addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);
    		} else {
    			environment.getPropertySources().addFirst(composite);
    		}
    	}
    
    	private void ensureBootstrapPropertyPrecedence(ConfigurableEnvironment environment) {
    		MutablePropertySources propertySources = environment.getPropertySources();
    
    		PropertySource<?> bootstrapPropertySource = propertySources.get(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
    
    		// not exists or already in the first place
    		if (bootstrapPropertySource == null || propertySources.precedenceOf(bootstrapPropertySource) == 0) {
    			return;
    		}
    
    		propertySources.remove(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
    		propertySources.addFirst(bootstrapPropertySource);
    	}
    
    	private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {
    		if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled() || !AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) {
    			return;
    		}
    
    		AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(environment, beanFactory);
    
    		List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
    		for (ConfigPropertySource configPropertySource : configPropertySources) {
    			configPropertySource.addChangeListener(autoUpdateConfigChangeListener);
    		}
    	}
    
    	@Override
    	public void setEnvironment(Environment environment) {
    		//it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
    		this.environment = (ConfigurableEnvironment) environment;
    	}
    
    	@Override
    	public int getOrder() {
    		//make it as early as possible
    		return Ordered.HIGHEST_PRECEDENCE;
    	}
    
    	// for test only
    	static void reset() {
    		NAMESPACE_NAMES.clear();
    		AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.clear();
    	}
    }
    

    验证

    本地application.yml添加一个配置test: aaaaa,Controller打印配置值(不够严谨:没有加时间戳):

    @RestController
    public class HealthController {
        @Value("${test}")
        private String test;
    
        @RequestMapping(value = "/hs")
        public String hs() {
            System.out.println("test" + test);
            return "ok";
        }
    }
    

    启动应用程序,postman请求接口http://localhost:8080/hs,控制台输出:testaaaaa

    增加Apollo配置项:
    在这里插入图片描述
    发布配置项,配置生效时间有延迟,等一分钟,再次请求接口,控制台打印输出依然是:testaaaaa

    至于未打印时间的不够严谨的问题,给出postman截图:
    在这里插入图片描述
    所以,应用需要重启

    结论:应用发布后,Apollo新增的配置,无论这个配置是否在本地配置文件存在与否,都不能覆盖。想要Apollo的配置生效,必须要重启(重新部署)应用,因为配置是启动时加载的

    注:本文使用的Apollo为公司内部二次开发版,基于Apollo 1.4版本,开源最新版本为1.9。

    热更新

    public String currentUser() {
        log.info("levelOne" + levelOne);
    }
    
    2022-03-09 15:50:29.413 [INFO][http-nio-8080-exec-4]:c.x.c.common.services.impl.UserServiceImpl [currentUser:274] levelOne10
    

    在这里插入图片描述
    另外,再给出配置发布历史,发布前是10,发布后是15。
    在这里插入图片描述

    2022-03-09 15:52:58.564 [INFO][http-nio-8080-exec-1]:c.x.c.common.services.impl.UserServiceImpl [currentUser:274] levelOne10
    

    结论:公司内部维护的Apollo版本,真是一个笑话!!!!!!!!

    如何实现热更新

    答案:使用@ApolloConfig

    实例:

    @ApolloConfig
    private Config config;
    
    int one = config.getIntProperty("category.level.one", 11);
    log.info("one: " + one);
    

    变更前的日志:
    2022-03-10 14:01:38.373 [INFO][http-nio-8080-exec-4]:c.x.c.common.services.impl.UserServiceImpl [currentUser:280] one: 10
    配置变更:
    在这里插入图片描述
    变更前的日志:
    2022-03-10 14:03:11.463 [INFO][http-nio-8080-exec-8]:c.x.c.common.services.impl.UserServiceImpl [currentUser:280] one: 12

    其他

    另外还有一个感觉很恶心的使用问题,配置项必须不能为空:
    在这里插入图片描述

    参考

    Apollo配置中心设计
    apollo配置中心与yml中存在相同配置
    https://github.com/apolloconfig/apollo/issues/1800

    更多相关内容
  • Nacos配置中心用法详细介绍

    万次阅读 2022-02-13 23:31:47
    配置中心区别于传统的配置信息分散到系统各个角落的方式,对系统中的配置文件进行集中统一管理,而不需要逐一对单个的服务器进行管理。通过配置中心,可以使得配置标准化、格式统一化;当配置信息发生变动时,修改...

            上篇文章介绍了 Nacos 作为注册中心的用法,除此之外,Nacos 还能作为配置中心使用,那这篇文章就介绍下 Nacos 作为配置中心的基本用法,首先我们先了解下为什么需要使用配置中心。

    一、为什么需要配置中心:

    在没有配置中心之前,传统应用配置的存在以下痛点:

    (1)采用本地静态配置,无法保证实时性:修改配置不灵活且需要经过较长的测试发布周期,无法尽快通知到客户端,还有些配置对实时性要求很高,比方说主备切换配置或者碰上故障需要修改配置,这时通过传统的静态配置或者重新发布的方式去配置,那么响应速度是非常慢的,业务风险非常大

    (2)易引发生产事故:比如在发布的时候,容易将测试环境的配置带到生产上,引发生产事故。

    (3)配置散乱且格式不标准:有的用properties格式,有的用xml格式,还有的存DB,团队倾向自造轮子,做法五花八门。

    (4)配置缺乏安全审计、版本控制、配置权限控制功能:谁?在什么时间?修改了什么配置?无从追溯,出了问题也无法及时回滚到上一个版本;无法对配置的变更发布进行认证授权,所有人都能修改和发布配置。

            而配置中心区别于传统的配置信息分散到系统各个角落的方式,对系统中的配置文件进行集中统一管理,而不需要逐一对单个的服务器进行管理。那这样做有什么好处呢?

    (1)通过配置中心,可以使得配置标准化、格式统一化

    (2)当配置信息发生变动时,修改实时生效,无需要重新重启服务器,就能够自动感知相应的变化,并将新的变化统一发送到相应程序上,快速响应变化。比方说某个功能只是针对某个地区用户,还有某个功能只在大促的时段开放,使用配置中心后只需要相关人员在配置中心动态去调整参数,就基本上可以实时或准实时去调整相关对应的业务。

    (3)通过审计功能还可以追溯问题

    二、Nacos配置中心的使用:

            微服务中配置中心的主流解决方案主要有三种:Nacos、Apollo、Config+Bus,不过这篇文章我们主要介绍 Nacos 作为配置中心的用法,对其他两种方式感兴趣的读者请自行上网查阅

    1、Springboot 整合 Nacos 配置中心:

    (1)首先我们声明项目的版本信息:

    <properties>
        <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
    </properties>
    
    <!--  只声明依赖,不引入依赖 -->
    <dependencyManagement>
        <dependencies>
            <!-- 声明springBoot版本 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 声明springCloud版本 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 声明 springCloud Alibaba 版本 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    (2)添加 nacos 配置中心的 maven 依赖:

    <!-- SpringCloud Ailibaba Nacos Config -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    (3)在 application.properties 文件中添加 nacos 配置中心相关配置:

    spring.profiles.active=dev
    spring.application.name=cloud-producer-server
    server.port=8080
    
    # nacos 配置中心地址
    spring.cloud.nacos.config.server-addr=localhost:8848
    # 配置文件的类型
    spring.cloud.nacos.config.file-extension=yaml

    (4)在 nacos 控制台新建一个 DataID 为 cloud-producer-server-dev.yaml 的配置集:

    为什么 DataID 的命名为 cloud-producer-server-dev.yaml 会在下文介绍

     (5)编写测试类:

    //配置发布之后,动态刷新配置
    @RefreshScope
    @RestController
    @RequestMapping("provider")
    public class ProviderController
    {
        // 使用原生注解@Value()导入配置
        @Value("${user.id}")
        private String id;
        @Value("${user.name}")
        private String name;
        @Value("${user.age}")
        private String age;
    
        @GetMapping("getNacosConfig")
        public String providerTest()
        {
            return "我是provider,已成功获取nacos配置中心的数据:(id:" + id + ",name:" + name + ",age:" + age +")";
        }
    }

    (6)启动服务验证:

            启动项目,访问 http://localhost:8080/provider/getNacosConfig 接口,可以看到 nacos 配置中心的配置信息已经生效并被成功获取到了

    (7)验证动态刷新配置:

            配置的动态刷新是配置中心最核心的功能之一,假设我现在需要修改 user.name 的值,那么我直接改变 Nacos 中的配置会生效吗?我们试下直接将 Nacos 中的配置修改成 “zhangsan”,如下图:

     此时不重启项目并重新访问该接口,结果如下:

            我们发现配置已经动态刷新了,这是为什么呢?其实是由于我们在类上添加了 @RefreshScope 注解而产生的效果。

    //配置发布之后,动态刷新配置
    @RefreshScope
    @RestController
    @RequestMapping("provider")
    public class ProviderController

    2、Nacos 的核心概念:

    2.1、Data ID:

    (1)Data ID 的命名格式:

            前面我们演示了在 nacos 控制台新建一个 DataID 为 cloud-producer-server-dev.yaml 的数据集,那么这个 Data ID 是什么呢?Data ID 是配置集的唯一标识,一个应用可以包含多个配置集,每个配置集都需要被一个有意义的名称标识。那么 Data ID 怎么取值呢?格式通俗一点就是 “前缀-环境-扩展名”,如下所示:

    ${spring.cloud.nacos.config.prefix}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

    ① prefix:前缀,默认是 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置。

    # 若不指定,默认采用应用名的方案
    spring.application.name=cloud-producer-server
    
    # 手动指定配置的dataID前缀标识
    # spring.cloud.nacos.config.prefix=cloud-producer-server-config

    ② active:配置运行环境,即为当前环境对应的 profile。

    注意:当 spring.profiles.active 为空时,对应的连接符 ”-“ 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}

    # dev表示开发环境
    spring.profiles.active=dev

    ③ file-exetension:配置文件的类型,默认是 properties,也可以通过配置项 spring.cloud.nacos.config.file-extension 来配置,目前支持的类型有 TEXT、JSON、XML、YAML、HTML、Properties

    # 指定配置文件类型为yaml文件
    spring.cloud.nacos.config.file-extension=yaml

    ④ 最终配置:

            经过前面三个步骤,我们最终在nacos配置中心的控制台新增配置文件就是:cloud-producer-server.yaml

    2.2、环境隔离-命名空间Namespace:

            Nacos 引入命名空间 Namespace 的概念来进行多环境配置和服务的管理及隔离。例如,你可能存在本地开发环境dev、测试环境test、生产环境prod 三个不同的环境,那么可以创建三个不同的 Namespace 区分不同的环境。创建方式如下:

     创建完成后,就可以在Nacos 控制台的配置列表上面看到不同的命名空间了,如下图:

            成功创建新命名空间后,就可以在 springboot 的配置文件配置命名空间的 id 切换到对应的命名空间,并获取对应空间下的配置文件,但在没有指定命名空间配置的情况下,默认的配置都是在 public 空间中,指定命名空间的方式如下:

    # 对应创建的命名空间的ID,此处对应的是dev命名空间
    cloud.nacos.config.namespace=483bb765-a42d-4112-90bc-50b8dff768b3

    2.3、业务隔离-Group分组:

            Group 也可以实现环境隔离的功能,但 Group 设计的目的主要是做同一个环境中的不同服务分组,把不同的微服务的配置文件划分到同一个分组里面去,Nacos 如果不指定 Group,则默认的分组是 DEFAULT_GROUP。

            如果没有 Group,试想一下这个场景:有两个微服务,一个是订单系统,一个是用户系统,但是他们有着相同的配置,比如 datasource-url,那么如何区分呢?这时候 Group 就派上用场了。上述场景中订单系统、用户系统可以单独分为一个组,比如 ORDER_GROUP、USER_GROUP,当然这是比较细粒度的分组,根据企业的业务也可以多个微服务分为一组。

            接下来我们演示一下创建配置集时以及集成时如何指定分组,还是前面的例子,新建配置集是在如下位置指定Group分组:

     接下来在 application.properties 文件分组:

    spring.cloud.nacos.config.namespace=483bb765-a42d-4112-90bc-50b8dff768b3

    3、小结:

    Nacos 实现配置管理和动态配置刷新很简单,总结如下步骤:

    • ① 添加对应 spring-cloud-starter-alibaba-nacos-config 依赖
    • ② 使用原生注解 @Value() 导入配置
    • ③ 使用原生注解 @RefreshScope 刷新配置
    • ④ 根据自己业务场景做好多环境配置隔离(Namespace)、不同业务配置隔离(Group)

    4、共享配置:

            当我们微服务的数量越来越多,势必会有相同的配置,这时我们可以将相同的配置抽取出来作为项目中共有的配置,比如集群中的数据源信息、日志的配置信息,nacos 也是支持这种一个配置中心多个配置集这种写法的。

    (1)我们在nacos中新建两个 Data ID 分别是 db.yaml 和 log.yaml 的文件。

    (2)在配置文件中分别加入部分配置内容

    (3)在 Springboot 项目中添加如下的 nacos 配置:

    spring:
      cloud:
        nacos:
          config:
            extension-configs[0]:
              data-id: db.yaml
              # 默认为DEFAULT_GROUP
              group: DEFAULT_GROUP
              # 是否动态刷新,默认为false
              refresh: true
            extension-configs[1]:
              data-id: log.yaml
              group: DEFAULT_GROUP
              refresh: true

    为了更加清晰的在多个应用间配置共享的 Data Id,官方推荐使用 shared-configs,配置如下:

    spring:
      cloud:
        nacos:
          config:
            shared-configs[0]:
              data-id: db.yaml
              # 默认为DEFAULT_GROUP
              group: DEFAULT_GROUP   
              # 是否动态刷新,默认为false
              refresh: true   
            shared-configs[1]:
              data-id: log.yaml
              group: DEFAULT_GROUP
              refresh: true

    (4)思考:在这2个文件中出现相同配置,nacos如何选取?

            当多个 Data Id 同时出现相同的配置时,它的优先级关系是 spring.cloud.nacos.config.extension-configs[n].data-id 其中 n 的值越大,优先级越高。

    注意:spring.cloud.nacos.config.extension-configs[n].data-id 的值必须带文件扩展名,文件扩展名既可支持 properties,又可以支持 yaml/yml。此时 spring.cloud.nacos.config.file-extension 的配置对自定义扩展配置的 Data Id 文件扩展名没有影响。

    (5)不同方式配置加载优先级:

            Nacos 配置中心目前提供以下三种配置能力从 Nacos 拉取相关的配置,当三种方式共同使用时,他们的一个优先级关系是:A < B < C:

    • A:通过 spring.cloud.nacos.config.shared-configs[n].data-id 支持多个共享 Data Id 的配置
    • B:通过 spring.cloud.nacos.config.extension-configs[n].data-id 的方式支持多个扩展 Data Id 的配置
    • C:通过内部相关规则(spring.cloud.nacos.config.prefix、spring.cloud.nacos.config.file-extension、spring.cloud.nacos.config.group)自动生成相关的 Data Id 配置

    相关阅读:

    常见的服务器架构入门:从单体架构、EAI 到 SOA 再到微服务和 ServiceMesh

    常见分布式理论(CAP、BASE)和一致性协议(Gosssip协议、Raft一致性算法)

    一致性哈希算法原理详解

    Nacos注册中心的部署与用法详细介绍

    Nacos配置中心用法详细介绍

    SpringCloud OpenFeign 远程HTTP服务调用用法与原理

    什么是RPC?RPC框架dubbo的核心流程

    服务容错设计:流量控制、服务熔断、服务降级

    sentinel 限流熔断神器详细介绍

    Sentinel 规则持久化到 apollo 配置中心

    Sentinel-Dashboard 与 apollo 规则的相互同步

    Spring Cloud Gateway 服务网关的部署与使用详细介绍

    Spring Cloud Gateway 整合 sentinel 实现流控熔断

    Spring Cloud Gateway 整合 knife4j 聚合接口文档

    常见分布式事务详解(2PC、3PC、TCC、Saga、本地事务表、MQ事务消息、最大努力通知)

    分布式事务Seata原理

    RocketMQ事务消息原理


    参考文章:

    SpringBoot2.X整合Nacos做配置中心

    五十五张图告诉你微服务的灵魂摆渡者Nacos究竟有多强?

    展开全文
  • 服务配置中心:Config

    千次阅读 多人点赞 2021-02-04 18:40:35
    目录第一章 Config介绍1.1、什么是配置中心1.2、为啥用配置中心1.3、常见的配置中心第二章 Config工作流程第三章 Config入门案例3.1、项目准备与启动3.2、配置中心远程库3.3、配置中心服务端3.4、配置中心客户端第四...


    配套资料,免费下载
    链接:https://pan.baidu.com/s/1la_3-HW-UvliDRJzfBcP_w
    提取码:lxfx
    复制这段内容后打开百度网盘手机App,操作更方便哦

    第一章 Config介绍

    1.1、什么是配置中心

    使用微服务就意味着要将单体应用中的业务拆分成一个个的子服务,每个服务的粒度相对较小,因此,系统中将会出现大量的服务。由于每一个服务都需要必要的配置信息才能运行,所以也将会产生大量的配置文件,所以有一套对配置文件进行集中式、动态式的管理的设施就必不可少了。

    常见对配置的管理有三种:

    1. 传统的配置方式:配置信息分散到系统各个角落方式,配置文件或者在代码中。
    2. 集中式配置中心:将应用系统中对配置信息的管理作为一个新的应用功能模块,进行集中统一管理,并且提供额外功能。
    3. 分布式配置中心:在分布式、微服务架构中,独立的配置中心服务。

    1.2、为啥用配置中心

    在微服务体系中,服务的数量以及配置信息日益增多,比如各种服务器参数配置、各种数据库访问参数配置、各种环境下配置信息的不同、配置信息修改之后实时生效等等,传统的配置文件方式或者将配置信息存放于数据库中的方式已无法满足开发人员对配置管理的要求,如:

    • 安全性:配置跟随源代码保存在代码库中,容易造成配置泄漏。
    • 时效性:修改配置,需要重启服务才能生效。
    • 局限性:无法支持动态调整,例如日志开关、功能开关。

    1.3、常见的配置中心

    • Diamond:淘宝开源的持久配置中心,支持各种持久信息(比如各种规则,数据库配置等)的发布和订阅。
    • Qconf:奇虎360内部分布式配置管理工具,用来替代传统的配置文件,使得配置信息和程序代码分离,同时配置变化能够实时同步到客户端,而且保证用户高效读取配置,这使的工程师从琐碎的配置修改、代码提交、配置上线流程中解放出来,极大地简化了配置管理工作。
    • Disconf:百度的分布式配置管理平台,专注于各种分布式系统配置管理的通用组件和通用平台,提供统一的配置管理服务。
    • Apollo:携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
    • Spring Cloud Config:Spring Cloud微服务开发的配置中心,他是一个解决分布式系统的配置管理方案。它包含 Client 和 Server 两个部分,Server 提供配置文件的存储、以接口的形式将配置文件的内容提供出去,Client 通过接口获取数据、并依据此数据初始化自己的应用。(我们学习这一款产品)

    Spring Cloud Config官方文档:https://docs.spring.io/spring-cloud-config/docs/2.2.6.RELEASE/reference/html/

    第二章 Config工作流程

    Spring Cloud Config Server 的工作流程,如下图所示:

    1. 首先需要一个远程 Git 仓库,平时测试可以使用 GitHub,在实际生产环境 中,需要自己搭建一个 Git 服务器,远程 Git 仓库的主要作用是用来保存我们的配置文件,我们在这里采用国内的码云 gitee,也可以使用国外的 GitHub,由于使用 Github 网络比较慢,可能连接失败,用哪个都是可以的。
    2. 除了远程 Git 仓库之外,我们还需要一个本地 Git 仓库,每当 Config Server 访问远程 Git 仓库时,都会克隆一份到本地,这样当远程仓库无法连接时,就直接使用本地存储的配置信息;
    3. 微服务 A、微服务 B 则是我们的具体应用,这些应用在启动的时候会从 Config Server 中获取相应的配置信息;
    4. 当微服务 A、微服务 B 尝试从 Config Server 中加载配置信息的时候,Config Server 会先通过 git clone 命令克隆一份配置文件保存到本地;
    5. 由于配置文件是存储在 Git 仓库中,所以配置文件天然具有版本管理功能;

    第三章 Config入门案例

    3.1、项目准备与启动

    我们接下来的所有操作均是在Gateway最后完成的工程上进行操作,相关代码请到配套资料中寻找。

    3.2、配置中心远程库

    创建远程仓库:https://gitee.com/projects/new

    上传配置文件:

    application-dev.yaml(内容自己随便定义的,没有什么特殊含义,用来模拟一些配置信息)

    computer:
      username: caochenlei
      password: dev123456
      env: dev
      version: 1
    

    application-prod.yaml(内容自己随便定义的,没有什么特殊含义,用来模拟一些配置信息)

    computer:
      username: caochenlei
      password: prod123456
      env: prod
      version: 1
    

    application-test.yaml(内容自己随便定义的,没有什么特殊含义,用来模拟一些配置信息)

    computer:
      username: caochenlei
      password: test123456
      env: test
      version: 1
    

    3.3、配置中心服务端

    (1)在父项目spring-cloud-study下创建一个子项目config-server4001

    (2)打开依赖文件pom.xml,把以下依赖信息拷贝进去:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
    </dependencies>
    

    (3)创建配置文件application.yaml,把以下配置信息拷贝进去:

    server:
      port: 4001
    
    spring:
      application:
        name: config-server
      cloud:
        config:
          server:
            git:
              uri: https://gitee.com/caochenlei/config-center.git  #代表你配置文件仓库的远程地址
              search-paths: config-center                          #代表你配置文件仓库的仓库路径
              username: XXXXXXXXXXXXXXX                            #代表你Github/Gitee的账户
              password: XXXXXXXXXXXXXXX                            #代表你Github/Gitee的密码
    

    (4)编写主启动类com.caochenlei.ConfigServer4001Application,把以下代码拷贝进去,然后启动当前应用:

    @SpringBootApplication
    @EnableConfigServer
    public class ConfigServer4001Application {
        public static void main(String[] args) {
            SpringApplication.run(ConfigServer4001Application.class, args);
        }
    }
    

    (5)打开浏览器,在浏览器地址输入以下地址依次进行访问仓库的配置文件:

    访问地址1:http://localhost:4001/master/application-dev.yaml

    访问地址2:http://localhost:4001/master/application-prod.yaml

    访问地址3:http://localhost:4001/master/application-test.yaml

    当访问成功后配置中心会通过 git clone 命令将远程配置文件在本地也保存一份,以确保在 git 仓库故障时我们的应用还可以继续正常使用。

    (6)访问远程仓库的配置文件,官方提供了很多种方式,但是我们最推荐的就是上边这种,还有一些其他的访问规则,我在下边都给出了,但不具体演示了。

    /{application}/{profile}[/{label}]
    
    /{application}-{profile}.yaml
    /{label}/{application}-{profile}.yaml
    
    /{application}-{profile}.yml
    /{label}/{application}-{profile}.yml
    
    /{application}-{profile}.properties
    /{label}/{application}-{profile}.properties
    

    {application}:表示配置文件的名字,对应的配置文件即:application。

    {profile}:表示环境,有 dev、prod、test、online及默认(不写就是默认)。

    {label} 表示分支,默认我们放在 master 分支上。

    (7)一个拓展的小知识,YAML文件的拓展名到底是用yml还是yaml

    回答:两种都可以,都有人在用,Spring Boot都能识别,无伤大雅,这里推荐使用yaml,为什么,不是我说的,是官网说的,我们只要打开YAML的官网。

    地址:https://yaml.org/faq.html

    3.4、配置中心客户端

    (1)前面已经搭建好了配置中心的服务端,并且通过访问接口从config服务端读取配置信息, 不过实际开发中,更多的不是我们人为去获取配置信息,而是由微服务自己从config服务端上加载配置信息, 那么怎么来加载呢?按道理来说,我们需要给微服务的所有服务都配置上连接配置中心的配置,但是,目前我们正处于学习阶段,为了保持思路清晰,也为了减少修改工程的代码量,我在这里就不在修改以前原有的项目了,我现在要重新创建新的服务,来模拟配置中心的客户端,实际上,这一块的配置应该和微服务的服务们一块用的。

    (2)在父项目spring-cloud-study下创建一个子项目config-client3001

    (3)打开依赖文件pom.xml,把以下依赖信息拷贝进去:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
    

    (4)创建配置文件application.yaml,把以下配置信息拷贝进去:

    server:
      port: 3001
    
    spring:
      application:
        name: config-client3001
    

    (5)创建配置文件bootstrap.yaml,把以下配置信息拷贝进去:

    #有啥区别:application.yaml和bootstrap.yaml都是配置文件,都可以被Spring Boot所识别
    #加载顺序:bootstrap.yaml > application.yaml > application-(dev/prod/test).yaml
    #具体配置如下:
    spring:
      cloud:
        config:
          profile: dev               #要使用哪种环境的配置文件
          label: master              #要使用哪个分支的配置文件
          uri: http://localhost:4001 #分布式配置中心的服务地址
    

    (6)编写主启动类com.caochenlei.ConfigClient3001Application,把以下代码拷贝进去,然后启动当前应用:

    @SpringBootApplication
    public class ConfigClient3001Application {
        public static void main(String[] args) {
            SpringApplication.run(ConfigClient3001Application.class, args);
        }
    }
    

    (7)编写一个控制器类,在里边编写一个方法,来获取配置文件当中的属性信息,这样,当我们启动这个项目的时候,我们可以通过浏览器地址来访问,查看当前的配置是不是从配置中心加载出来的,具体类名:com.caochenlei.controller.ConfigController,具体代码如下:

    @RestController
    public class ConfigController {
        @Value("${computer.username}")
        private String username;
        @Value("${computer.password}")
        private String password;
        @Value("${computer.env}")
        private String env;
        @Value("${computer.version}")
        private String version;
    
        @RequestMapping("/info")
        public String info() {
            return username + " : " + password + " : " + env + " : " + version;
        }
    }
    

    (8)启动当前这个项目,在浏览器地址输入:http://localhost:3001/info

    第四章 Config信息安全

    4.1、介绍 JCE

    前面是在 Git 仓库中明文存储配置信息值,很多场景下,对于某些敏感的配置内容(例如数据库账号、密码等),应该加密存储,Config Server为我们考虑到了这一点,对配置内容提供了加密与解密支持,Config Server的加解密功能依赖Java Cryptography Extension(JCE)。

    4.2、安装 JCE

    Java 8 JCE下载地址(默认需要登录Oracle账号,不想登录,可以使用配套资料中我已经下载好的资源包):

    http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

    下载得到JCE的zip压缩包并解压,将其中的jar包覆盖到JDK安装目录/jre/lib/security目录下。

    4.3、对称加密说明

    Config Server提供了加密与解密的接口,并且使用的是对称加密算法,因此,你需要提供一个密钥来进行加解密使用,通常我们只需要手动加密,而不需要手动解密,因为,程序在执行的过程中,发现你使用了密文以后,会自动的根据你提供的密钥来进行解密,我们学习解密只是为了告诉你,原来解密后确实是咱们以前定义的明文数据,让你对加解密有更深的印象。

    • 加密接口:http://localhost:4001/encrypt
    • 解密接口:http://localhost:4001/decrypt

    我们需要在config-server4001项目下创建一个bootstrap.yaml配置文件,配置文件的内容如下:

    #设置对称密钥,用这个密钥进行加密和解密,密钥自己随便定义,越复杂越好
    encrypt:
      key: 123456789ABC
    

    接下来,我们需要重新启动项目来进行加密和解密测试,加密和解密接口都需要post请求,因此,请选择你熟练使用的工具来发送,推荐postmancurl

    4.4、加密敏感信息

    加密数据请发送:curl -XPOST http://localhost:4001/encrypt -d "caochenlei"

    4.5、解密敏感信息

    解密数据请发送:curl -XPOST http://localhost:4001/decrypt -d “3ec5f0bcdeef2348a2f03059ed551bb57353ea3b8f21011bd94b19a3fd73e876”

    4.6、修改明文数据

    application-dev.yaml({cipher}就是用来告诉程序这是一个密文)

    computer:
      username: '{cipher}3ec5f0bcdeef2348a2f03059ed551bb57353ea3b8f21011bd94b19a3fd73e876'
      password: '{cipher}cc08112f0bfc3979553c80afbc2aa65183ecd54351236b899bf431df79f82805'
      env: dev
      version: 1
    

    application-prod.yaml({cipher}就是用来告诉程序这是一个密文)

    computer:
      username: '{cipher}3ec5f0bcdeef2348a2f03059ed551bb57353ea3b8f21011bd94b19a3fd73e876'
      password: '{cipher}f10682be4b46b06b17c78fba441347d8dd603a83874949e71d0182c8fe02a037'
      env: prod
      version: 1
    

    application-test.yaml({cipher}就是用来告诉程序这是一个密文)

    computer:
      username: '{cipher}3ec5f0bcdeef2348a2f03059ed551bb57353ea3b8f21011bd94b19a3fd73e876'
      password: '{cipher}82136fe37c3f9b1989992e4b398a8fa1a82a494879c23b68f00a28734c1911e1'
      env: test
      version: 1
    

    注意:你可以直接在线修改,也可以在本地修改好,使用 git 上传到远程仓库。

    4.7、测试配置获取

    重新启动config-client3001,然后访问:http://localhost:3001/info

    我们发现可以成功读取,这说明Config Server能自动解密配置内容。

    第五章 Config局部刷新

    5.1、问题描述

    我们发现我们刚才修改了远程仓库中的配置文件,但是,我们需要重新启动一次config-client3001客户端才能够读取最新的配置信息,实际上,当一个应用上线以后,我们尽量不能让他来回重启,很多场景下,需要在运行期间动态调整配置,如果配置发生了修改,微服务要如何实现配置的动态刷新呢?

    5.2、问题解决

    (1)在Controller上添加注解@RefreshScope,添加这个注解的类会在配置更新时得到特殊的处理。

    com.caochenlei.controller.ConfigController

    @RestController
    @RefreshScope
    public class ConfigController {
        @Value("${computer.username}")
        private String username;
        @Value("${computer.password}")
        private String password;
        @Value("${computer.env}")
        private String env;
        @Value("${computer.version}")
        private String version;
    
        @RequestMapping("/info")
        public String info() {
            return username + " : " + password + " : " + env + " : " + version;
        }
    }
    

    (2)开启web访问端点:management.endpoints.web.exposure.include=*

    application.yaml

    server:
      port: 3001
    
    spring:
      application:
        name: config-client3001
    
    management:
      endpoints:
        web:
          exposure:
            include: '*'
    

    (3)重新启动当前这个项目,以让配置生效,重启以后访问地址:http://localhost:3001/info

    (4)修改远程仓库application-dev.yaml的版本号为2

    application-dev.yaml

    computer:
      username: '{cipher}3ec5f0bcdeef2348a2f03059ed551bb57353ea3b8f21011bd94b19a3fd73e876'
      password: '{cipher}cc08112f0bfc3979553c80afbc2aa65183ecd54351236b899bf431df79f82805'
      env: dev
      version: 2
    

    (5)通知当前的应用刷新配置,需要使用post方式来访问:http://localhost:3001/actuator/refresh

    curl -XPOST http://localhost:3001/actuator/refresh
    

    (6)访问地址:http://localhost:3001/info

    这样我们就实现了,再不重新启动当前应用的情况下,如何优雅的让配置信息动态更新。

    第六章 Config全局刷新

    6.1、问题描述

    上边介绍的这种方式的刷新,就是你对每个微服务分别进行刷新,也就是一个一个操作,如果你有80个微服务,那么就需要手动刷新这80个微服务,而我们很清楚,一个微服务项目,很有很多服务模块,一个一个刷新,也可以,可以让运维人员写一个批处理脚本,这样也能减轻工作总量,但是,我们除了写脚本以外,还有没有一种更加优雅的方式,当远程仓库的配置信息发生修改,可以让所有连接配置中心的服务(客户端)都能够即使的刷新,当然是有办法的。

    前面使用/actuator/refresh端点手动刷新配置虽然可以实现刷新,但所有微服务节点的配置都需要手动去刷新,如果微服务非常多,其工作量非常庞大。因此,实现配置的自动刷新是志在必行,Spring Cloud Bus就可以用来实现配置的自动刷新。

    Spring Cloud Bus使用轻量级的消息代理/总线(例如RabbitMQ、Kafka等)广播传播状态的更改(例如配置的更新)或者其他的管理指令,可以将Spring Cloud Bus想象成一个分布式的Spring Boot Actuator。

    使用了Spring Cloud Bu以后,我们只需要向配置中心发送一次/actuator/bus-refresh,就能让所有连接到该配置中心的服务(客户端)都能动态刷新。

    6.2、问题解决

    注意:这一章节我们需要使用RabbitMQ,而RabbitMQ的学习与搭建与启动,请参考:https://caochenlei.blog.csdn.net/article/details/112549952

    6.2.1、修改服务端

    (1)打开config-server4001pom.xml,新增加如下依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    

    (2)打开config-server4001application.yaml,修改为如下配置:

    server:
      port: 4001
    
    spring:
      application:
        name: config-server
      cloud:
        #配置中心的配置信息
        config:
          server:
            git:
              uri: https://gitee.com/caochenlei/config-center.git  #代表你配置文件仓库的远程地址
              search-paths: config-center                          #代表你配置文件仓库的仓库路径
              username: XXXXXXXXXXXXXXX                            #代表你Github/Gitee的账户
              password: XXXXXXXXXXXXXXX                            #代表你Github/Gitee的密码
        #开启spring cloud bus,默认是开启的,也可以省略该配置
        bus:
          enabled: true
      #rabbitmq的一些常见配置
      rabbitmq:
        host: localhost
        port: 5672
        username: guest
        password: guest
    
    #打开所有的web访问端点
    management:
      endpoints:
        web:
          exposure:
            include: '*'
      endpoint:
        bus-refresh:
          enabled: true
    

    (3)重新启动当前配置中心config-server4001

    6.2.2、修改客户端

    (1)打开config-client3001pom.xml,新增加如下依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    

    (2)打开config-client3001application.yaml,修改为如下配置:

    server:
      port: 3001
    
    spring:
      application:
        name: config-client3001
      cloud:
        #开启spring cloud bus,默认是开启的,也可以省略该配置
        bus:
          enabled: true
      #rabbitmq的一些常见配置
      rabbitmq:
        host: localhost
        port: 5672
        username: guest
        password: guest
    
    #打开所有的web访问端点
    management:
      endpoints:
        web:
          exposure:
            include: '*'
      endpoint:
        bus-refresh:
          enabled: true
    

    (3)因为我们要模拟只请求一次,而让所有连接配置中心的客户端都能自动刷新,现在只有一个,显然效果不是很明显,所以,我们需要拷贝config-client3001config-client3002,这里建议,先新建一个子项目,然后把依赖、配置、启动类、代码在拷贝进去,不要直接拷贝项目,可能会出现一些意想不到的问题,当config-client3001config-client3002都准备好了以后,请一个一个启动起来。

    注意:ConfigClient3002Application和application.yaml的端口号

    (4)我们分别访问如下地址:

    地址1:http://localhost:3001/info

    地址2:http://localhost:3002/info

    (5)修改远程仓库application-dev.yaml的版本号为3

    application-dev.yaml

    computer:
      username: '{cipher}3ec5f0bcdeef2348a2f03059ed551bb57353ea3b8f21011bd94b19a3fd73e876'
      password: '{cipher}cc08112f0bfc3979553c80afbc2aa65183ecd54351236b899bf431df79f82805'
      env: dev
      version: 3
    

    (6)通知当前的配置中心刷新配置,需要使用post方式来访问:http://localhost:4001/actuator/bus-refresh

    curl -XPOST http://localhost:4001/actuator/bus-refresh
    

    (7)我们分别访问如下地址:

    地址1:http://localhost:3001/info

    地址2:http://localhost:3002/info

    这样我们就实现了,再不重新启动当前应用的情况下,如何优雅的让配置信息动态更新。

    第七章 Config安全认证

    我们现在是可以随便访问config-server4001的,因此,我们的配置中心也需要安全认证保护。

    7.1、修改服务端

    (1)打开config-server4001pom.xml,把用于安全认证的依赖加入:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    (2)打开config-server4001application.yaml,给安全认证加上账户和密码:

    spring:
      security:
        user:
          name: config
          password: 123456
    

    (3)重新启动当前的config-server4001

    7.2、修改客户端

    (1)修改config-client3001config-client3002bootstrap.yaml,修改后如下:

    #有啥区别:application.yaml和bootstrap.yaml都是配置文件,都可以被Spring Boot所识别
    #加载顺序:bootstrap.yaml > application.yaml > application-(dev/prod/test).yaml
    #具体配置如下:
    spring:
      cloud:
        config:
          profile: dev               #要使用哪种环境的配置文件
          label: master              #要使用哪个分支的配置文件
          uri: http://localhost:4001 #分布式配置中心的服务地址
          username: config           #分布式配置中心的登录账号
          password: 123456           #分布式配置中心的登录密码
    

    (2)重新启动config-client3001config-client3002

    (3)我们分别访问如下地址:

    地址1:http://localhost:3001/info

    地址2:http://localhost:3002/info

    第八章 Config的高可用

    8.1、高可用介绍

    有了配置中心之后,其他的微服务都是从配置中心上获取配置信息,此时配置中心就至关重要了,在真实的项目环境中,Spring Cloud Config配置中心难免会出现各种问题,此时就需要考虑Spring Cloud Config的高可用机制了。

    Spring Cloud Config的高可用机制解决方式非常简单,把Spring Cloud Config注册到Eureka就搞定了,此时用户访问的时候不是直接从配置中心获取配置信息,而是先通过Eureka中获取配置中心的地址,然后再从配置中心获取具体服务的配置信息。

    要想实现Config配置中心的高可用,我们还要实现配置中心的集群环境,目前我们只有一台配置中心服务端,因此,我们拷贝config-server4001一份为config-server4002,这里建议不要直接拷贝,要先在父工程下创建一个子项目,然后再把依赖、配置、主启动类、代码拷贝进来,注意修改配置文件和主启动类的端口号。

    接下来,我们需要依次启动:

    • eureka-server7001
    • eureka-server7002

    8.2、高可用实现

    服务端添加对注册中心支持:

    (1)在config-server4001config-server4002pom.xml中,添加如下依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    

    (2)在config-server4001config-server4002application.yaml中,添加如下配置:

    eureka:
      instance:
        #是否使用 ip 地址注册
        prefer-ip-address: true
        #该实例注册到服务中心的唯一ID
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
        #告诉注册中心,每间隔10s,向服务端发送一次心跳,证明自己依然"存活"
        lease-renewal-interval-in-seconds: 10
        #告诉注册中心,如果我20s之内没有给你发心跳,就代表我故障了,将我踢出掉
        lease-expiration-duration-in-seconds: 20
      client:
        #设置服务注册中心地址
        service-url:
          defaultZone: http://root:123456@eureka-server7001.com:7001/eureka/,http://root:123456@eureka-server7002.com:7002/eureka/
    

    (3)重新启动config-server4001config-server4002,然后在浏览器输入:http://localhost:7001,账号:root,密码:123456

    客户端添加对注册中心支持:

    (1)在config-client3001config-client3002pom.xml中,添加如下依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    

    (2)在config-client3001config-client3002bootstrap.yaml中,修改成如下配置:

    #有啥区别:application.yaml和bootstrap.yaml都是配置文件,都可以被Spring Boot所识别
    #加载顺序:bootstrap.yaml > application.yaml > application-(dev/prod/test).yaml
    #具体配置如下:
    spring:
      cloud:
        config:
          discovery:
            enabled: true               #开启注册中心发现服务
            service-id: CONFIG-SERVER   #使用注册中心注册名称为CONFIG-SERVER的分布式配置中心集群
          profile: dev               #要使用哪种环境的配置文件
          label: master              #要使用哪个分支的配置文件
          username: config           #分布式配置中心的登录账号
          password: 123456           #分布式配置中心的登录密码
    
    eureka:
      instance:
        #是否使用 ip 地址注册
        prefer-ip-address: true
        #该实例注册到服务中心的唯一ID
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
      client:
        #是否将自己注册到注册中心,默认为 true
        register-with-eureka: true
        #表示 Eureka Client 间隔多久去服务器拉取注册信息,默认为 30 秒
        registry-fetch-interval-seconds: 10
        #设置服务注册中心地址
        service-url:
          defaultZone: http://root:123456@eureka-server7001.com:7001/eureka/,http://root:123456@eureka-server7002.com:7002/eureka/
    

    (3)重新启动config-client3001config-client3002,然后在浏览器分别输入如下地址测试:

    地址1:http://localhost:3001/info

    地址2:http://localhost:3002/info

    (4)将config-server4001停止,模拟宕机情况。

    (5)我们分别访问如下地址:

    地址1:http://localhost:3001/info

    地址2:http://localhost:3002/info

    展开全文
  • 图文解析 Nacos 配置中心的实现

    千次阅读 2020-01-17 16:32:13
    图文解析 Nacos 配置中心的实现 本文不会贴太多源码,基本靠图片和文字叙述 全文共 2582 字,预计阅读时间 12 分钟 什么是 Nacos Nacos 是阿里发起的开源项目,地址:https://github.com/alibaba/nacos。Nacos ...

    图文解析 Nacos 配置中心的实现

    本文不会贴太多源码,基本靠图片和文字叙述
    全文共 2582 字,预计阅读时间 12 分钟

    什么是 Nacos

    Nacos 是阿里发起的开源项目,地址:https://github.com/alibaba/nacos。Nacos 主要提供两种服务,一是配置中心,支持配置注册、变更下发、层级管理等,意义是不停机就可以动态刷新服务内部的配置项;二是作为命名服务,提供服务的注册和发现功能,通常用于在 RPC 框架的 Client 和 Server 中间充当媒介,还附带有健康监测、负载均衡等功能。

    本文聚焦于 Nacos 的第一块功能,即配置中心的实现。先叙述一个配置中心通常需要哪些组成部分,再结合 Nacos 1.1.4 的源码,探究一下这些设计是如何反映在源码上的。

    配置中心的架构

    配置中心本身并不复杂,前提是你先将 CAP 的取舍问题晾在一边的话。配置中心最基础的功能就是存储一个键值对,用户发布一个配置(configKey),然后客户端获取这个配置项(configValue);进阶的功能就是当某个配置项发生变更时,将变更告知客户端刷新旧值。

    下方的架构图,简要描述了一个配置中心的大致架构,用户可以通过管理平台发布配置,通过 HTTP 调用将配置注册到服务端,服务端将之保存在 MySQL 等持久化存储引擎中;用户通过客户端 SDK 访问服务端的配置,同时建立 HTTP 的长轮询监听配置项变更,同时为了减轻服务端压力和保证容灾特性,配置项拉取到客户端之后会保存一份快照在本地文件中,SDK 优先读取文件里的内容。

    这里省略了许多细节问题,例如配置分层设计,权限校验,客户端长轮询的间隔设置,服务端每次查询都需要访问 MySQL 么,配置变更是主动推送还是等定时轮询触发等,还有就是运维高可用方面的工作(私以为这个是配置中心的精华),例如节点跨地域部署,网络分区时配置如何保证可写可推送变更等。真正实现一个高质量的配置中心,还是需要长时间打磨的。

    image.png

    Nacos 使用示例

    下文涉及的源码均基于 Nacos 1.1.4 版本

    官方代码示例

    先看一下官方文档中对于 Nacos 的 API 使用的示例代码,第一步是传递配置,新建 ConfigService 实例,第二步可以通过相应的接口获取配置和注册配置监听器。使用方式非常简单易懂,不再赘述。

    try {
        // 传递配置
    	String serverAddr = "{serverAddr}";
    	String dataId = "{dataId}";
    	String group = "{group}";
    	Properties properties = new Properties();
    	properties.put("serverAddr", serverAddr);
        
        // 新建 configService
    	ConfigService configService = NacosFactory.createConfigService(properties);
    	String content = configService.getConfig(dataId, group, 5000);
    	System.out.println(content);
        
        // 注册监听器
        configService.addListener(dataId, group, new Listener() {
    	@Override
    	public void receiveConfigInfo(String configInfo) {
    		System.out.println("recieve1:" + configInfo);
    	}
    	@Override
    	public Executor getExecutor() {
    		return null;
    	}
    });
    } catch (NacosException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    

    Properties 解读

    serverAddr 传递的是配置中心服务端的地址列表,被内部名为 ServerListManager 的类解析成地址列表进行管理,进行 HTTP 调用时会从中选择存活的机器拼接成 URL 完成调用,一旦在调用时该地址抛异常,则客户端会有一些处理措施,例如转换下次选择的节点等。值得注意的是,通常在实践中不会采取这种硬编码的方式,可以将其配置在 Zookeeper 或者注册发现中心上,在启动时动态拉取。

    配置项的层级设计

    Nacos 官方给出了这样的设计图:

    image.png


    dataId 可以理解为用户自定义的配置健,group 可以理解为配置分组名称,这个属于配置层级设计的概念。简单来说,配置中心会通过层次设计,来支持不同的分区,以此区分不同的环境、不同的分组、甚至不同的开发者,满足在开发过程中灰度发布、测试等需求。因此怎样设计都可以,只要有含义就好,例如下图也不是不可以。

    image.png

    Nacos 客户端解析

    获取配置

    获取配置的主要方法是 NacosConfigService 类的 getConfigInner 方法,通常情况下该方法直接从本地文件中取得配置的值,如果本地文件不存在或者内容为空,则再通过 HTTP GET 方法从远端拉取配置,并保存到本地快照中。

    image.png

    当通过 HTTP 获取远端配置时,Nacos 提供了两种熔断策略,一是超时时间,二是最大重试次数,默认重试三次。

    注册监听器

    配置中心客户端对某个配置项注册监听器是很常见的需求,达到在配置项变更的时候执行回调的功能。

    iconfig.addListener(dataId, group, ml);
    iconfig.getConfigAndSignListener(dataId, group, 1000, ml);
    

    Nacos 可以通过以上方式注册监听器,它们内部的实现均是调用 ClientWorker 类的 addCacheDataIfAbsent。其中 CacheData 是一个维护配置项和其下注册的所有监听器的实例,私以为这个名字取得并不好,不容易理解。

    所有的 CacheData 都保存在 ClientWorker 类中的原子 cacheMap 中,其内部的核心成员有:

    image.png

    其中,content 是配置内容,MD5 值是用来检测配置是否发生变更的关键,内部还维护着一个若干监听器组成的数组,一旦发生变更则依次回调这些监听器。

    配置长轮询

    ClientWorker 通过其下的两个线程池完成配置长轮询的工作,一个是单线程的 executor,每隔 10ms 按照每 3000 个配置项为一批次捞取待轮询的 cacheData 实例,将其包装成为一个 LongPollingTask 提交进入第二个线程池 executorService 处理。

    image.png

    该长轮询任务内部主要分为四步:

    1. 检查本地配置,忽略本地快照不存在的配置项,检查是否存在需要回调监听器的配置项
    2. 如果本地没有配置项的,从服务端拿,返回配置内容发生变更的键值列表
    3. 每个键值再到服务端获取最新配置,更新本地快照,补全之前缺失的配置
    4. 检查 MD5 标签是否一致,不一致需要回调监听器

    如果该轮询任务抛出异常,等待一段时间再开始下一次调用,减轻服务端压力。另外,Nacos 在 HTTP 工具类中也有限流器的代码,通过多种手段降低轮询或者大流量情况下的风险。下文还会讲到,如果在服务端没有发现变更的键值,那么服务端会夯住这个 HTTP 请求一段时间(客户端侧默认传递的超时是 30s),以此进一步减轻客户端的轮询频率和服务端的压力。

    Nacos 服务端解析

    配置 Dump

    服务端启动时就会依赖 DumpService 的 init 方法,从数据库中 load 配置存储在本地磁盘上,并将一些重要的元信息例如 MD5 值缓存在内存中。服务端会根据心跳文件中保存的最后一次心跳时间,来判断到底是从数据库 dump 全量配置数据还是部分增量配置数据(如果机器上次心跳间隔是 6h 以内的话)。

    全量 dump 当然先清空磁盘缓存,然后根据主键 ID 每次捞取一千条配置刷进磁盘和内存。增量 dump 就是捞取最近六小时的新增配置(包括更新的和删除的),先按照这批数据刷新一遍内存和文件,再根据内存里所有的数据全量去比对一遍数据库,如果有改变的再同步一次,相比于全量 dump 的话会减少一定的数据库 IO 和磁盘 IO 次数。

    配置注册

    Nacos 服务端是一个 SpringBoot 实现的服务,注册配置主要代码位于 ConfigController 和 ConfigServletInner 中。服务端一般是多节点部署的集群,因此请求一开始只会打到一台机器,这台机器将配置插入 MySQL 中进行持久化,这部分代码很简单不再赘述。

    因为服务端并不是针对每次配置查询都去访问 MySQL 的,而是会依赖 dump 功能在本地文件中将配置缓存起来。因此当单台机器保存完毕配置之后,需要通知其他机器刷新内存和本地磁盘中的文件内容,因此它会发布一个名为 ConfigDataChangeEvent 的事件,这个事件会通过 HTTP 调用通知所有集群节点(包括自身),触发本地文件和内存的刷新。

    image.png

    处理长轮询

    上文提到,客户端会有一个长轮询任务,拉取服务端的配置变更,那么服务端是如何处理这个长轮询任务的呢?源码逻辑位于 LongPollingService 类,其中有一个 Runnable 任务名为 ClientLongPolling,服务端会将受到的轮询请求包装成一个 ClientLongPolling 任务,该任务持有一个 AsyncContext 响应对象(Servlet 3.0 的新机制),通过定时线程池延后 29.5s 执行。

    为什么比客户端 30s 的超时时间提前 500ms 返回是为了最大程度上保证客户端不会因为网络延时造成超时

    image.png

    这里需要注意的是,在 ClientLongPolling 任务被提交进入线程池待执行的同时,服务端也通过一个队列 allSubs 保存了所有正在被夯住的轮询请求,这是因为在配置项被夯住的期间内,如果用户通过管理平台操作了配置项变更、或者服务端该节点收到了来自其他节点的 dump 刷新通知,那么都应立即取消夯住的任务,及时通知客户端数据发生了变更。

    为了达到这个目的,LongPollingService 类继承自 Event 接口,实际上本身是个事件触发器,需要实现 onEvent 方法,其事件类型是 LocalDataChangeEvent。

    当服务端在请求被夯住的期间接收到某项配置变更时,就会发布一个 LocalDataChangeEvent 类型的事件通知(注意同上文中的 ConfigDataChangeEvent 区别),之后会将这个变更包装成一个 DataChangeTask 异步执行,内容就是从 allSubs 中找出夯住的 ClientLongPolling 请求,写入变更强制其立即返回。

    因此完整的流程如下,如果非接收请求的节点,那么忽略第一步持久化配置后开始:

    image.png

    全文总结

    本文聚焦于 Nacos 作为配置中心的源码实现,包含了客户端和服务端两部分,内容基本覆盖了配置中心功能的关键点,既作为学习总结,也希望对阅读的朋友有所帮助。

    展开全文
  • 配置中心的作用

    千次阅读 2021-12-13 00:35:15
    配置中心将配置从各个应用中剥离出来,自成一体,对所有的配置进行单独的统一管理,优雅的解决了上述诸多问题。在系统架构中,和安全、日志、监控等非功能需求一样,配置管理也是一种非功能需求。配置中心是整个...
  • 分布式配置中心

    千次阅读 2019-12-19 16:53:45
    服务配置的现状 在微服务系统中,每个系统不仅仅只有代码,他还需要连接其他资源,例如数据库的配置或功能性的开关等等。但是随着微服务系统的不断迭代,整个微服务系统可能成为一个网状结构,这个时候就要考虑整个...
  • 文章目录 简介 配置中心搭建 服务端搭建 客户端配置 重试 配置信息加密 配置动态加载刷新 自定义分布式配置中心 简介 实现 测试 简介 在微服务架构中,每个服务都会一系列的配置信息,而这些配置如果只是进行常规的...
  • Apollo配置中心

    千次阅读 2021-06-18 14:29:09
    Apollo配置中心
  • 近期,我们顺手把go语言重新捡起来,边写边学,手撸一个数据配置中心玩玩。笔者的习惯是,不喜欢在自己文章中赘述别人已经讲的很好的东西了,所以关于什么是数据配置中心配置中心的作用和意义、以及当下热门的产品...
  • Nacos注册中心和配置中心

    千次阅读 2021-04-20 18:14:25
    一:Nacos注册中心原理 服务提供者、服务消费者、服务发现组件这三个角色之间的关系大致如下 1、微服务在启动时,将自己的网络地址等信息注册到服务发现组件(nacos server)中,服务发现组件会存储这些信息。 2...
  • 2.nacos服务配置中心

    千次阅读 2021-01-28 08:55:38
    修改nacos配置数据库: 我们在控制台配置的信息, 默认是写到nacos的默认数据库中, 不方便管理, 因此我们设置一个自己的数据库, 进行管理操作在控制台配置nacos配置nacos配置管理的模型: 基本概念,namespace, group, ...
  • 配置中心

    千次阅读 2020-07-02 20:41:32
    在阿里有很多配置中心,这其中有集团公共的服务,也有BU自己内部的配置中心。我当时就有了疑问,为啥搞这么多配置中心啊,一两个不就好了嘛。问了帅帅的师兄,他提示了我。 配置中心的作用 配置中心其实就是为了统一...
  • SpringCloudAlibaba之配置中心Nacos

    千次阅读 2020-07-06 12:04:24
    在SpringCloud中也有一个同样优秀的配置中心组件:Spring Cloud Config,以及它对应的高阶组件:Spring Cloud Bus,但是它不提供可视化操作界面,这也是我为什么要学习 Nacos 的原因之一。 如果你也想了解 Spring ...
  • 一、前言 之前一直学习SpringCloud, 对于配置中心,一直也是采用的Spring Cloud Config,但是用久了,发现很多地方满足不了要求,同时也感觉很low(个人看法勿喷)。在学习Spring cloud config 的时候也有听到过携程的...
  • Nacos配置中心

    万次阅读 2020-12-18 16:55:17
    一、配置中心介绍 ** 1、Spring Cloud Config Spring Cloud Config 为分布式系统的外部配置提供了服务端和客户端的支持方案。在配置的服务端您可以在所有环境中为应用程序管理外部属性的中心位置。客户端和服务端...
  • 为什么需要分布式配置中心

    万次阅读 多人点赞 2018-09-25 16:20:17
    对于配置文件,我们并不陌生,它提供我们可以动态修改程序运行能力。引用别人的一句话就是: 系统运行时(runtime)飞行姿态的动态调整! 我可以把我们的工作称之为在快速飞行的飞机上修理零件。我们人类总是无法...
  • Nacos-配置中心优雅配置JSON数据格式

    千次阅读 2021-03-29 11:05:31
    因Nacos默认不支持Json格式配置,需要搭配监听器获取配置中心Json数据,返回给客户端。 二、搭配Nacos配置Josn数据 1. bootstrap.yml spring: application: ## 注册服务名 name: order-service cloud: ...
  • 一、Nacos作为服务配置中心 在上篇文章中我们讲解了使用Nacos作为配置中心,本篇我们继续讲下Nacos的配置中心功能。 上篇文章地址:https://blog.csdn.net/qq_43692950/article/details/122101863 在前我们讲解...
  • Nacos配置中心工作原理(超简单)

    千次阅读 2021-11-11 16:40:03
    注意是动态配置,不是配置中心。 先在大脑里面考虑3分钟,也许你有答案了。 对的,你肯定想的和下面一样: 上图是需要人工发起通知的动态配置架构,实现很简单。 但我们为什么要人工操作两次呢,可不可以...
  • Apollo 配置中心介绍

    万次阅读 2019-07-25 23:49:08
    最近我司进行基础架构升级,将配置中心从 Spring Cloud Config 迁移至 Apollo。趁此机会也学习下 Apollo,本文主要知识来自于我对官方 Wiki 的学习,如有错误,欢迎勘误。 Apollo(阿波罗)来自于携程研发的分布式...
  • Eureka: 在springcloud大火的时候官方强势推出了一波eureka该注册中心,但是很快该项目在netflix中闭源了,伴随着这个消息的产生,很多社区和个人都提出了很多替代的方法,比较成熟的就是之前的Zookepper
  • why 微服务数量多,修改配置需要重启服务或无法集中统一修改,所以...如何实现配置中心? 这里我们选择将配置保存在github中,让spring cloud config去读取github仓库中的配置。 核心功能 集中管理各个环境的配置文件
  • SpringCloud整合Nacos配置中心

    千次阅读 2022-04-20 15:37:23
    代码读写、监听nacos配置中心的方案,请移步 https://blog.csdn.net/rocklee/article/details/124006422 确定版本依赖 在parent里面定义好boot/cloud/alibaba的版本: 这里用的boot是2.4.2,springcloud的版本是2020...
  • SpringBoot+Nacos实现配置中心

    万次阅读 多人点赞 2020-08-05 00:00:36
    Nacos配置中心,你值得拥有
  • Nacos Config 服务配置中心

    千次阅读 2020-09-27 00:15:53
    先来看下微服务架构中,关于配置文件的一些问题: 配置文件相对分散。在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散在各个微服务中,不好统一配置和管理。 配置文件无法区分环境。微服务...
  • springcloud之config配置中心

    千次阅读 2021-11-07 22:06:43
    目录`config` 简介`config` 作用`config` 架构 config 简介 springcloud config 项目是一...目前 springcloud config 的使用主要是通过 git/svn 方式做一个配置中心,然后每个服务从其中获取自身配置所需的参数 spring
  • 微服务配置中心完全解读

    千次阅读 2019-04-02 14:31:31
    应用的配置数据存储在配置中心一般都会以一种配置格式存储,比如Properties、Json、Yaml等,如果配置格式错误,会导致客户端解析配置失败引起生产故障,配置中心对配置的格式校验能够有效防止人为错误操作的发生,是...
  • 一、配置中心介绍 1、Spring Cloud Config Spring Cloud Config 为分布式系统的外部配置提供了服务端和客户端的支持方案。在配置的服务端您可以在所有环境中为应用程序管理外部属性的中心位置。客户端和服务端概念上...
  • Consul3-使用consul作为配置中心

    万次阅读 热门讨论 2018-07-15 13:27:16
    在前面的文章中学习了consul在windows下的安装配置,然后consul作为spring boot的服务发现和注册中心,详细的参考:...
  • Nacos Config 微服务配置中心

    千次阅读 2021-01-12 08:13:11
    Nacos 提供用于存储配置和其他元数据的 key/value 存储,为分布式系统中的外部化配置提供服务器端和客户端支持。Spring Cloud Alibaba Nacos Config 可以在 Nacos Server 集中管理 ...Nacos既作为注册中心,同时也是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 592,219
精华内容 236,887
关键字:

配置中心

友情链接: sipclient.rar