基于 Consul 的配置中心

Spring Cloud Consul Config

依赖

  • 加入 spring-cloud-starter-consul-config

启用

  • 在 bootstrap.properties | yml 中 加入相关配置
    • spring.cloud.consul.host=localhost
    • spring.cloud.consul.port=8500
    • spring.cloud.consul.config.enabled=true
    开启consul配置中心支持

Consul 中的数据怎么存

配置项

  • spring.cloud.consul.config.format=
    KEY_VALUE | YAML | PROPERTIES | FILES
    配置数据存储的形式
  • /config/应用名,profile/data
    所有的配置 在这个data节点里面
  • /config/application,profile/data

如何定制

  • spring.cloud.consul.config.data-key=data
    配置key的名字,由于Consul是K/V存储,配置存储在对应K的V中

  • spring.cloud.consul.config.root=config

  • spring.cloud.consul.config.default-context=application
    指定consul配置的配置文件父路径

  • spring.cloud.consul.config.profile-separator=’,’
    置配置的分隔符

配置项变更

自动刷新配置

  • spring.cloud.consul.config.watch.enabled=true

  • spring.cloud.consul.config.watch.delay=1000
    刷新时间为每隔一秒钟

实现原理

  • 单线程 ThreadPoolTaskScheduler
  • ConsulConfigAutoConfiguration.CONFIG_WATCH_TASK_SCHEDULER_NAME
    使用 ThreadPoolTaskScheduler 每隔一秒钟 去检测 看看有没有发生变化 如果有变化 就发一个event事件出来

实例

在这里插入图片描述
需要修改的代码

bootstrap.properties

spring.application.name=waiter-service

spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.prefer-ip-address=true

spring.cloud.consul.config.enabled=true
#是否启用配置中心功能
spring.cloud.consul.config.format=yaml
#设置配置值的格式

pom文件

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</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>
		
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-consul-discovery</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-consul-config</artifactId>
		</dependency>

		<dependency>
			<groupId>io.github.resilience4j</groupId>
			<artifactId>resilience4j-spring-boot2</artifactId>
			<version>0.14.1</version>
		</dependency>

		<dependency>
			<groupId>io.micrometer</groupId>
			<artifactId>micrometer-registry-prometheus</artifactId>
		</dependency>

		<dependency>
			<groupId>org.joda</groupId>
			<artifactId>joda-money</artifactId>
			<version>1.0.1</version>
		</dependency>
		<dependency>
			<groupId>org.jadira.usertype</groupId>
			<artifactId>usertype.core</artifactId>
			<version>6.0.1.GA</version>
		</dependency>
		<!-- 增加Jackson的Hibernate类型支持 -->
		<dependency>
			<groupId>com.fasterxml.jackson.datatype</groupId>
			<artifactId>jackson-datatype-hibernate5</artifactId>
			<version>2.9.8</version>
		</dependency>

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

结果分析

启动 consul
输入 http://localhost:8500 进入 consul 在 key/Value 中 新建一个 key/Value 为yaml格式
在这里插入图片描述

启动程序 使用 postman 进行订单的创建 我们之前在consul写入的配置 成功 配置
在这里插入图片描述

将 折扣进行调整 变成 50 控制台显示discount的变更在这里插入图片描述
在这里插入图片描述

使用 postman 继续进行测试 结果中的discount已经成功修改
在这里插入图片描述

源码分析

ConsulConfigBootstrapConfiguration

@Configuration
@ConditionalOnConsulEnabled
public class ConsulConfigBootstrapConfiguration {
    public ConsulConfigBootstrapConfiguration() {
    }

    @Configuration
    @EnableConfigurationProperties
    @Import({ConsulAutoConfiguration.class})
    @ConditionalOnProperty(
        name = {"spring.cloud.consul.config.enabled"},
        matchIfMissing = true
    )
    protected static class ConsulPropertySourceConfiguration {
        @Autowired
        private ConsulClient consul;

        protected ConsulPropertySourceConfiguration() {
        }

        @Bean
        @ConditionalOnMissingBean
        public ConsulConfigProperties consulConfigProperties() {
            return new ConsulConfigProperties();
        }

        @Bean     //配置 consulPropertySourceLocator   通过 consulPropertySourceLocator 找到对应的 PropertySource
        public ConsulPropertySourceLocator consulPropertySourceLocator(ConsulConfigProperties consulConfigProperties) {
            return new ConsulPropertySourceLocator(this.consul, consulConfigProperties);
        }
    }
}

ConsulConfigAutoConfiguration

@Configuration
@ConditionalOnConsulEnabled
@ConditionalOnProperty(
    name = {"spring.cloud.consul.config.enabled"},
    matchIfMissing = true
)
public class ConsulConfigAutoConfiguration {
    public static final String CONFIG_WATCH_TASK_SCHEDULER_NAME = "configWatchTaskScheduler";

    public ConsulConfigAutoConfiguration() {
    }

    @Configuration
    @ConditionalOnClass({RefreshEndpoint.class})
    protected static class ConsulRefreshConfiguration {
        protected ConsulRefreshConfiguration() {
        }

        @Bean
        @ConditionalOnProperty(
            name = {"spring.cloud.consul.config.watch.enabled"},
            matchIfMissing = true
        )
        //在 configWatch 中 会用到一个单线程的 ThreadPoolTaskScheduler  在创建的时候 将 name 这个常量传入
        public ConfigWatch configWatch(ConsulConfigProperties properties, ConsulPropertySourceLocator locator, ConsulClient consul, @Qualifier("configWatchTaskScheduler") TaskScheduler taskScheduler) {
            return new ConfigWatch(properties, consul, locator.getContextIndexes(), taskScheduler);
        }

        @Bean(
            name = {"configWatchTaskScheduler"}
        )
        @ConditionalOnProperty(
            name = {"spring.cloud.consul.config.watch.enabled"},
            matchIfMissing = true
        )
        public TaskScheduler configWatchTaskScheduler() {
            return new ThreadPoolTaskScheduler();
        }
    }
}

ConfigWatch

public class ConfigWatch implements ApplicationEventPublisherAware, SmartLifecycle {
    private static final Log log = LogFactory.getLog(ConfigWatch.class);
    private final ConsulConfigProperties properties;
    private final ConsulClient consul;
    private final TaskScheduler taskScheduler;
    private final AtomicBoolean running;
    private LinkedHashMap<String, Long> consulIndexes;
    private ApplicationEventPublisher publisher;
    private boolean firstTime;
    private ScheduledFuture<?> watchFuture;

    public ConfigWatch(ConsulConfigProperties properties, ConsulClient consul, LinkedHashMap<String, Long> initialIndexes) {
        this(properties, consul, initialIndexes, getTaskScheduler());
    }

    public ConfigWatch(ConsulConfigProperties properties, ConsulClient consul, LinkedHashMap<String, Long> initialIndexes, TaskScheduler taskScheduler) {
        this.running = new AtomicBoolean(false);
        this.firstTime = true;
        this.properties = properties;
        this.consul = consul;
        this.consulIndexes = new LinkedHashMap(initialIndexes);
        this.taskScheduler = taskScheduler;
    }

    private static ThreadPoolTaskScheduler getTaskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.initialize();
        return taskScheduler;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void start() {//在启动的时候 设置它的时间 从 properties 中 找出时间的参数(有默认的)
        if (this.running.compareAndSet(false, true)) {
            this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(this::watchConfigKeyValues, (long)this.properties.getWatch().getDelay());
        }

    }

    public boolean isAutoStartup() {
        return true;
    }

    public void stop(Runnable callback) {
        this.stop();
        callback.run();
    }

    public int getPhase() {
        return 0;
    }

    public void stop() {
        if (this.running.compareAndSet(true, false) && this.watchFuture != null) {
            this.watchFuture.cancel(true);
        }

    }

    public boolean isRunning() {
        return this.running.get();
    }

    @Timed("consul.watch-config-keys")
    public void watchConfigKeyValues() {//是一个定时任务 每隔一秒钟 会跑一次  在这里面 做了一个 从 Consul 中获取值的动作
        if (this.running.get()) {
            Iterator var1 = this.consulIndexes.keySet().iterator();

            while(var1.hasNext()) {
                String context = (String)var1.next();
                if (this.properties.getFormat() != Format.FILES && !context.endsWith("/")) {
                    context = context + "/";
                }

                try {
                    Long currentIndex = (Long)this.consulIndexes.get(context);
                    if (currentIndex == null) {
                        currentIndex = -1L;
                    }

                    log.trace("watching consul for context '" + context + "' with index " + currentIndex);
                    String aclToken = this.properties.getAclToken();
                    if (StringUtils.isEmpty(aclToken)) {
                        aclToken = null;
                    }

                    Response<List<GetValue>> response = this.consul.getKVValues(context, aclToken, new QueryParams((long)this.properties.getWatch().getWaitTime(), currentIndex));//取得kv的值 将取回来的值 进行判断 是否发生看变化  如果 变了 就publish一个RefreshEvent消息出去  推出去之后 我们就去刷新 这里 所有需要的refreshsource  加了source的这些bean 它好会被刷新掉 
                    if (response.getValue() != null && !((List)response.getValue()).isEmpty()) {
                        Long newIndex = response.getConsulIndex();
                        if (newIndex != null && !newIndex.equals(currentIndex)) {
                            if (!this.consulIndexes.containsValue(newIndex) && !currentIndex.equals(-1L)) {
                                log.trace("Context " + context + " has new index " + newIndex);
                                ConfigWatch.RefreshEventData data = new ConfigWatch.RefreshEventData(context, currentIndex, newIndex);
                                this.publisher.publishEvent(new RefreshEvent(this, data, data.toString()));
                            } else if (log.isTraceEnabled()) {
                                log.trace("Event for index already published for context " + context);
                            }

                            this.consulIndexes.put(context, newIndex);
                        } else if (log.isTraceEnabled()) {
                            log.trace("Same index for context " + context);
                        }
                    } else if (log.isTraceEnabled()) {
                        log.trace("No value for context " + context);
                    }
                } catch (Exception var8) {
                    if (this.firstTime && this.properties.isFailFast()) {
                        log.error("Fail fast is set and there was an error reading configuration from consul.");
                        ReflectionUtils.rethrowRuntimeException(var8);
                    } else if (log.isTraceEnabled()) {
                        log.trace("Error querying consul Key/Values for context '" + context + "'", var8);
                    } else if (log.isWarnEnabled()) {
                        log.warn("Error querying consul Key/Values for context '" + context + "'. Message: " + var8.getMessage());
                    }
                }
            }
        }

        this.firstTime = false;
    }

    static class RefreshEventData {
        private final String context;
        private final Long prevIndex;
        private final Long newIndex;

        RefreshEventData(String context, Long prevIndex, Long newIndex) {
            this.context = context;
            this.prevIndex = prevIndex;
            this.newIndex = newIndex;
        }

        public String getContext() {
            return this.context;
        }

        public Long getPrevIndex() {
            return this.prevIndex;
        }

        public Long getNewIndex() {
            return this.newIndex;
        }

        public boolean equals(Object o) {
            if (this == o) {
                return true;
            } else if (o != null && this.getClass() == o.getClass()) {
                ConfigWatch.RefreshEventData that = (ConfigWatch.RefreshEventData)o;
                return Objects.equals(this.context, that.context) && Objects.equals(this.prevIndex, that.prevIndex) && Objects.equals(this.newIndex, that.newIndex);
            } else {
                return false;
            }
        }

        public int hashCode() {
            return Objects.hash(new Object[]{this.context, this.prevIndex, this.newIndex});
        }

        public String toString() {
            return (new ToStringCreator(this)).append("context", this.context).append("prevIndex", this.prevIndex).append("newIndex", this.newIndex).toString();
        }
    }
}

ConsulConfigProperties
在这个里面 有默认的时间参数

 public static class Watch {
        private int waitTime = 55;
        private boolean enabled = true;
        private int delay = 1000;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值