Spring Cloud Config Server迁移节点或容器化带来的问题 原因,解决

版本

  • springboot 2.0.1.RELEASE
  • springcloud Finchley.RC1

问题

看程序猿 dd 的博客 http://blog.didispace.com/Spring-Cloud-Config-Server-ip-change-problem/

容器中 config-server 重启或迁移导致ip port 变动, config-client 依旧使用老的(从eureka 第一次取到)的config-server 地址.导致 bus 无法刷新配置,healbeat config 失败.

进一步发现问题

ConfigServicePropertySourceLocator.java

1private Environment getRemoteEnvironment(RestTemplate restTemplate, ConfigClientProperties properties, 2 String label, String state) { 3 String path = "/{name}/{profile}"; 4 String name = properties.getName(); 5 String profile = properties.getProfile(); 6 String token = properties.getToken(); 7 String uri = properties.getRawUri(); 8 9 Object[] args = new String[] { name, profile }; 10 if (StringUtils.hasText(label)) { 11 args = new String[] { name, profile, label }; 12 path = path + "/{label}"; 13 } 14 ResponseEntity<Environment> response = null; 15 16 try { 17 HttpHeaders headers = new HttpHeaders(); 18 if (StringUtils.hasText(token)) { 19 headers.add(TOKEN_HEADER, token); 20 } 21 if (StringUtils.hasText(state)) { //TODO: opt in to sending state? 22 headers.add(STATE_HEADER, state); 23 } 24 final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers); 25 //uri 是直接从配置中拿的 26 response = restTemplate.exchange(uri + path, HttpMethod.GET, 27 entity, Environment.class, args); 28 } 29 catch (HttpClientErrorException e) { 30 if (e.getStatusCode() != HttpStatus.NOT_FOUND) { 31 throw e; 32 } 33 } 34 35 if (response == null || response.getStatusCode() != HttpStatus.OK) { 36 return null; 37 } 38 Environment result = response.getBody(); 39 return result; 40 }

虽然从上面代码可以看出,config-client 获取 config-server 地址,是从配置中获取.

但是有下面代码.

1@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false) 2@Configuration 3@Import({ UtilAutoConfiguration.class }) 4@EnableDiscoveryClient 5public class DiscoveryClientConfigServiceBootstrapConfiguration { 6 7 private static Log logger = LogFactory 8 .getLog(DiscoveryClientConfigServiceBootstrapConfiguration.class); 9 10 @Autowired 11 private ConfigClientProperties config; 12 13 @Autowired 14 private ConfigServerInstanceProvider instanceProvider; 15 16 private HeartbeatMonitor monitor = new HeartbeatMonitor(); 17 18 @Bean 19 public ConfigServerInstanceProvider configServerInstanceProvider( 20 DiscoveryClient discoveryClient) { 21 return new ConfigServerInstanceProvider(discoveryClient); 22 } 23 24 @EventListener(ContextRefreshedEvent.class) 25 public void startup(ContextRefreshedEvent event) { 26 //刷新配置 27 refresh(); 28 } 29 30 @EventListener(HeartbeatEvent.class) 31 public void heartbeat(HeartbeatEvent event) { 32 if (monitor.update(event.getValue())) { 33 //刷新配置 34 refresh(); 35 } 36 } 37 38 private void refresh() { 39 try { 40 String serviceId = this.config.getDiscovery().getServiceId(); 41 ServiceInstance server = this.instanceProvider 42 .getConfigServerInstance(serviceId); 43 String url = getHomePage(server); 44 if (server.getMetadata().containsKey("password")) { 45 String user = server.getMetadata().get("user"); 46 user = user == null ? "user" : user; 47 this.config.setUsername(user); 48 String password = server.getMetadata().get("password"); 49 this.config.setPassword(password); 50 } 51 if (server.getMetadata().containsKey("configPath")) { 52 String path = server.getMetadata().get("configPath"); 53 if (url.endsWith("/") && path.startsWith("/")) { 54 url = url.substring(0, url.length() - 1); 55 } 56 url = url + path; 57 } 58 this.config.setUri(url); 59 } 60 catch (Exception ex) { 61 if (config.isFailFast()) { 62 throw ex; 63 } 64 else { 65 logger.warn("Could not locate configserver via discovery", ex); 66 } 67 } 68 } 69 70 private String getHomePage(ServiceInstance server) { 71 return server.getUri().toString() + "/"; 72 } 73 74} 75

两个事件,ContextRefreshedEvent 和 HeartbeatEvent 都会触发刷新 ConfigClientProperties 中的 uri 地址. HeartbeatEvent 的触发是在 CloudEurekaClient.java

1 @Override 2 protected void onCacheRefreshed() { 3 super.onCacheRefreshed(); 4 5 if (this.cacheRefreshedCount != null) { //might be called during construction and will be null 6 long newCount = this.cacheRefreshedCount.incrementAndGet(); 7 log.trace("onCacheRefreshed called with count: " + newCount); 8 this.publisher.publishEvent(new HeartbeatEvent(this, newCount)); 9 } 10 } 11

即DiscoveryClient.java 中

1private boolean fetchRegistry(boolean forceFullRegistryFetch) { 2 Stopwatch tracer = FETCH_REGISTRY_TIMER.start(); 3 4 try { 5 // If the delta is disabled or if it is the first time, get all 6 // applications 7 Applications applications = getApplications(); 8 9 if (clientConfig.shouldDisableDelta() 10 || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress())) 11 || forceFullRegistryFetch 12 || (applications == null) 13 || (applications.getRegisteredApplications().size() == 0) 14 || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta 15 { 16 logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta()); 17 logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress()); 18 logger.info("Force full registry fetch : {}", forceFullRegistryFetch); 19 logger.info("Application is null : {}", (applications == null)); 20 logger.info("Registered Applications size is zero : {}", 21 (applications.getRegisteredApplications().size() == 0)); 22 logger.info("Application version is -1: {}", (applications.getVersion() == -1)); 23 getAndStoreFullRegistry(); 24 } else { 25 getAndUpdateDelta(applications); 26 } 27 applications.setAppsHashCode(applications.getReconcileHashCode()); 28 logTotalInstances(); 29 } catch (Throwable e) { 30 logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e); 31 return false; 32 } finally { 33 if (tracer != null) { 34 tracer.stop(); 35 } 36 } 37 38 // Notify about cache refresh before updating the instance remote status 39 onCacheRefreshed(); 40 41 // Update remote status based on refreshed data held in the cache 42 updateInstanceRemoteStatus(); 43 44 // registry was fetched successfully, so return true 45 return true; 46 }

那么,我们是不是可以认为,当 eureka 同步注册信息的后,会触发事件,修改ConfigClientProperties的配置.

然后这个问题就已经解决了呢(只有当 eureka client 还没同步注册信息,且 config-server 的 ip port 变动,这时候有 bus 和 healbeat 才会出问题 ps:几率相当之低,可以忽略)

事情没有这么简单.

笔者变更 config-server 的地址后,debug config-client 发现,在上述DiscoveryClientConfigServiceBootstrapConfiguration.refresh 方法的时候,取到的 url, 依旧是老的可不用的 config-server 地址.

输入图片说明

输入图片说明

图一是 DiscoveryClientConfigServiceBootstrapConfiguration.refresh() debug

图二是 DiscoveryClient.fetchRegistry debug

明显可以看到 两个 CloudEurekaClient 不一致,图一叫9962,图二叫9926.

说明CloudEurekaClient 创建了两次.且 DiscoveryClientConfigServiceBootstrapConfiguration 中获得 config-server 地址的CloudEurekaClient是无效的.

再进一步深究

在 spring-cloud-config-client 的 spring.factories中

1# Bootstrap components 2org.springframework.cloud.bootstrap.BootstrapConfiguration=\ 3org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\ 4org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration

DiscoveryClientConfigServiceBootstrapConfiguration 的初始化等级太高,ConfigServerInstanceProvider 持有的DiscoveryClient 无法修改,永远都是老对象.导致每次refresh 操作,获取的 config-server 地址都是老的.

解决方案

想了半天,没有找到不侵入的方式修改.只好魔改 spring-cloud-config-client 的代码.

修改DiscoveryClientConfigServiceBootstrapConfiguration.java

1/* 2 * Copyright 2013-2014 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package org.springframework.cloud.config.client; 18 19import org.apache.commons.logging.Log; 20import org.apache.commons.logging.LogFactory; 21import org.springframework.beans.factory.annotation.Autowired; 22import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 23import org.springframework.cloud.client.ServiceInstance; 24import org.springframework.cloud.client.discovery.DiscoveryClient; 25import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 26import org.springframework.cloud.client.discovery.event.HeartbeatEvent; 27import org.springframework.cloud.client.discovery.event.HeartbeatMonitor; 28import org.springframework.cloud.commons.util.UtilAutoConfiguration; 29import org.springframework.context.annotation.Bean; 30import org.springframework.context.annotation.Configuration; 31import org.springframework.context.annotation.Import; 32import org.springframework.context.event.ContextRefreshedEvent; 33import org.springframework.context.event.EventListener; 34 35/** 36 * Bootstrap configuration for a config client that wants to lookup the config server via 37 * discovery. 38 * 39 * @author Dave Syer 40 */ 41@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false) 42@Configuration 43@Import({ UtilAutoConfiguration.class }) 44@EnableDiscoveryClient 45public class DiscoveryClientConfigServiceBootstrapConfiguration { 46 47 private static Log logger = LogFactory 48 .getLog(DiscoveryClientConfigServiceBootstrapConfiguration.class); 49 50 @Autowired 51 private ConfigClientProperties config; 52 53 @Autowired 54 private ConfigServerInstanceProvider instanceProvider; 55 56// private HeartbeatMonitor monitor = new HeartbeatMonitor(); 57 58 @Bean 59 public ConfigServerInstanceProvider configServerInstanceProvider( 60 DiscoveryClient discoveryClient) { 61 return new ConfigServerInstanceProvider(discoveryClient); 62 } 63 64 @EventListener(ContextRefreshedEvent.class) 65 public void startup(ContextRefreshedEvent event) { 66 refresh(); 67 } 68 69// @EventListener(HeartbeatEvent.class) 70// public void heartbeat(HeartbeatEvent event) { 71// if (monitor.update(event.getValue())) { 72// refresh(); 73// } 74// } 75 76 private void refresh() { 77 try { 78 String serviceId = this.config.getDiscovery().getServiceId(); 79 ServiceInstance server = this.instanceProvider 80 .getConfigServerInstance(serviceId); 81 String url = getHomePage(server); 82 if (server.getMetadata().containsKey("password")) { 83 String user = server.getMetadata().get("user"); 84 user = user == null ? "user" : user; 85 this.config.setUsername(user); 86 String password = server.getMetadata().get("password"); 87 this.config.setPassword(password); 88 } 89 if (server.getMetadata().containsKey("configPath")) { 90 String path = server.getMetadata().get("configPath"); 91 if (url.endsWith("/") && path.startsWith("/")) { 92 url = url.substring(0, url.length() - 1); 93 } 94 url = url + path; 95 } 96 this.config.setUri(url); 97 } 98 catch (Exception ex) { 99 if (config.isFailFast()) { 100 throw ex; 101 } 102 else { 103 logger.warn("Could not locate configserver via discovery", ex); 104 } 105 } 106 } 107 108 private String getHomePage(ServiceInstance server) { 109 return server.getUri().toString() + "/"; 110 } 111 112} 113

添加 DiscoveryClientConfigServiceConfiguration.java

1/* 2 * Copyright 2013-2014 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package org.springframework.cloud.config.client; 18 19import org.apache.commons.logging.Log; 20import org.apache.commons.logging.LogFactory; 21import org.springframework.beans.factory.annotation.Autowired; 22import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 23import org.springframework.cloud.client.ServiceInstance; 24import org.springframework.cloud.client.discovery.DiscoveryClient; 25import org.springframework.cloud.client.discovery.event.HeartbeatEvent; 26import org.springframework.cloud.client.discovery.event.HeartbeatMonitor; 27import org.springframework.context.annotation.Configuration; 28import org.springframework.context.event.ContextRefreshedEvent; 29import org.springframework.context.event.EventListener; 30 31import java.util.List; 32 33 34/** 35 * 将HeartbeatEvent事件初始化后移. 36 * 从BootstrapConfiguration移动到EnableAutoConfiguration中 37 * @author superwen 38 */ 39@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false) 40@Configuration 41public class DiscoveryClientConfigServiceConfiguration { 42 43 private static Log logger = LogFactory 44 .getLog(DiscoveryClientConfigServiceConfiguration.class); 45 46 @Autowired 47 private ConfigClientProperties config; 48 49 @Autowired 50 private DiscoveryClient discoveryClient; 51 52 private HeartbeatMonitor monitor = new HeartbeatMonitor(); 53 54 55// @EventListener(ContextRefreshedEvent.class) 56// public void startup(ContextRefreshedEvent event) { 57// refresh(); 58// } 59 60 @EventListener(HeartbeatEvent.class) 61 public void heartbeat(HeartbeatEvent event) { 62 if (monitor.update(event.getValue())) { 63 refresh(); 64 } 65 } 66 67 private void refresh() { 68 try { 69 String serviceId = this.config.getDiscovery().getServiceId(); 70 logger.debug("Locating configserver (" + serviceId + ") via discovery"); 71 List<ServiceInstance> server = discoveryClient.getInstances(serviceId); 72 if (server.isEmpty()) { 73 throw new IllegalStateException( 74 "No instances found of configserver (" + serviceId + ")"); 75 } 76 ServiceInstance instance = server.get(0); 77 logger.debug( 78 "Located configserver (" + serviceId + ") via discovery: " + instance); 79 String url = getHomePage(instance); 80 if (instance.getMetadata().containsKey("password")) { 81 String user = instance.getMetadata().get("user"); 82 user = user == null ? "user" : user; 83 this.config.setUsername(user); 84 String password = instance.getMetadata().get("password"); 85 this.config.setPassword(password); 86 } 87 if (instance.getMetadata().containsKey("configPath")) { 88 String path = instance.getMetadata().get("configPath"); 89 if (url.endsWith("/") && path.startsWith("/")) { 90 url = url.substring(0, url.length() - 1); 91 } 92 url = url + path; 93 } 94 logger.debug("url=" + url); 95 this.config.setUri(url); 96 } catch (Exception ex) { 97 if (config.isFailFast()) { 98 throw ex; 99 } else { 100 logger.warn("Could not locate configserver via discovery", ex); 101 } 102 } 103 } 104 105 private String getHomePage(ServiceInstance server) { 106 return server.getUri().toString() + "/"; 107 } 108 109}

修改 spring.factories

1# Auto Configure 2org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 3org.springframework.cloud.config.client.ConfigClientAutoConfiguration,\ 4org.springframework.cloud.config.client.DiscoveryClientConfigServiceConfiguration 5 6# Bootstrap components 7org.springframework.cloud.bootstrap.BootstrapConfiguration=\ 8org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\ 9org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration 10

后置 HeartbeatEvent 事件,将其获取 config-server 的地址.改为从新的 eurekaclient 中获取即可.

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )