项目中用到了 spring cloud consul config,谈不上好用,算是能用。用笔记记录一下 config watch 的原理:

// org.springframework.cloud.consul.config.ConfigWatch#start
    @Override
    public void start() {
        if (this.running.compareAndSet(false, true)) {
            this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(this::watchConfigKeyValues,
                    this.properties.getWatch().getDelay());
        }
    }

代码很清晰,线程池定时执行轮询任务:

    @Timed("consul.watch-config-keys")
    public void watchConfigKeyValues() {
        if (!this.running.get()) {
            return;
        }
        for (String context : this.consulIndexes.keySet()) {

            // turn the context into a Consul folder path (unless our config format
            // are FILES)
            if (this.properties.getFormat() != FILES && !context.endsWith("/")) {
                context = context + "/";
            }

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

                if (log.isTraceEnabled()) {
                    log.trace("watching consul for context '" + context + "' with index " + currentIndex);
                }

                // use the consul ACL token if found
                String aclToken = this.properties.getAclToken();
                if (StringUtils.isEmpty(aclToken)) {
                    aclToken = null;
                }

                Response<List<GetValue>> response = this.consul.getKVValues(context, aclToken,
                        new QueryParams(this.properties.getWatch().getWaitTime(), currentIndex));

                // if response.value == null, response was a 404, otherwise it was a
                // 200, reducing churn if there wasn't anything
                if (response.getValue() != null && !response.getValue().isEmpty()) {
                    Long newIndex = response.getConsulIndex();

                    if (newIndex != null && !newIndex.equals(currentIndex)) {
                        // don't publish the same index again, don't publish the first
                        // time (-1) so index can be primed
                        if (!this.consulIndexes.containsValue(newIndex) && !currentIndex.equals(-1L)) {
                            if (log.isTraceEnabled()) {
                                log.trace("Context " + context + " has new index " + newIndex);
                            }
                            RefreshEventData data = new 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 e) {
                // only fail fast on the initial query, otherwise just log the error
                if (this.firstTime && this.properties.isFailFast()) {
                    log.error("Fail fast is set and there was an error reading configuration from consul.");
                    ReflectionUtils.rethrowRuntimeException(e);
                }
                else if (log.isTraceEnabled()) {
                    log.trace("Error querying consul Key/Values for context '" + context + "'", e);
                }
                else if (log.isWarnEnabled()) {
                    // simplified one line log message in the event of an agent
                    // failure
                    log.warn("Error querying consul Key/Values for context '" + context + "'. Message: "
                            + e.getMessage());
                }
            }
        }
        this.firstTime = false;
    }

着重跟一下请求参数:

 分析下这个 url 参数,wait 和 index,index 是版本号,wait 是长轮询等待的 timeout 值。

https://developer.hashicorp.com/consul/api-docs/features/blocking

When this is provided, the HTTP request will "hang" until a change in the system occurs, or the maximum timeout is reached. 
A critical note is that the return of a blocking request is no guarantee of a change. It is possible that the timeout was
reached or that there was an idempotent write that does not affect the result of the query.

 

posted on 2023-08-09 10:10  偶尔发呆  阅读(280)  评论(0)    收藏  举报