项目中用到了 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.
浙公网安备 33010602011771号