Spring Cloud config之三:config-server因为server端和client端的健康检查导致服务超时阻塞问题,config-server的文件更新机制
springcloud线上一个问题,当config-server连不上git时,微服务集群慢慢的都挂掉。
在入口层增加了日志跟踪问题:
org.springframework.cloud.config.server.environment.EnvironmentController.java
@RequestMapping("/{name}/{profiles}/{label:.*}") public Environment labelled(@PathVariable String name, @PathVariable String profiles, @PathVariable String label) { if (name != null && name.contains("(_)")) { // "(_)" is uncommon in a git repo name, but "/" cannot be matched // by Spring MVC name = name.replace("(_)", "/"); } if (label != null && label.contains("(_)")) { // "(_)" is uncommon in a git branch name, but "/" cannot be matched // by Spring MVC label = label.replace("(_)", "/"); } StopWatch sw = new StopWatch("labelled"); sw.start(); logger.info("EnvironmentController.labelled()开始,name={},profiles={},label={}", name, profiles, label); Environment environment = this.repository.findOne(name, profiles, label); sw.stop(); logger.info("EnvironmentController.labelled()结束,name={},profiles={},label={},耗时={}毫秒,耗时={}秒", name, profiles, label, sw.getTotalTimeMillis(), sw.getTotalTimeSeconds()); return environment; }
健康检查的入口ConfigServerHealthIndicator.java增加日志:
@Override protected void doHealthCheck(Health.Builder builder) throws Exception { StopWatch sw = new StopWatch("doHealthCheck"); sw.start(); logger.info("ConfigServerHealthIndicator.doHealthCheck()开始,builder={}", builder); builder.up(); List<Map<String, Object>> details = new ArrayList<>(); for (String name : this.repositories.keySet()) { Repository repository = this.repositories.get(name); String application = (repository.getName() == null)? name : repository.getName(); String profiles = repository.getProfiles(); try { Environment environment = this.environmentRepository.findOne(application, profiles, repository.getLabel()); HashMap<String, Object> detail = new HashMap<>(); detail.put("name", environment.getName()); detail.put("label", environment.getLabel()); if (environment.getProfiles() != null && environment.getProfiles().length > 0) { detail.put("profiles", Arrays.asList(environment.getProfiles())); } if (!CollectionUtils.isEmpty(environment.getPropertySources())) { List<String> sources = new ArrayList<>(); for (PropertySource source : environment.getPropertySources()) { sources.add(source.getName()); } detail.put("sources", sources); } details.add(detail); } catch (Exception e) { HashMap<String, String> map = new HashMap<>(); map.put("application", application); map.put("profiles", profiles); builder.withDetail("repository", map); builder.down(e); return; } } builder.withDetail("repositories", details); sw.stop(); logger.info("ConfigServerHealthIndicator.doHealthCheck()结束,耗时={}毫秒,耗时={}秒,builder={}", sw.getTotalTimeMillis(), sw.getTotalTimeSeconds(), builder); }
通过耗时统计的日志分析后,发现是EnvironmentController和ConfigServerHealthIndicator调用次数太多,这两个调用最终会调用JGitEnvironmentRepository.fetch()方法,这个fetch方法会去请求git,超时时间大概是5秒。
由于请求的数量过多,服务请求不过来,线程阻塞了很长时间。
分析:
1、EnvironmentController的调用是每个微服务模块发起的,为什么?
2、ConfigServerHealthIndicator的调用是config-server的健康检查,可以通过设置检查的间隔时间缓解问题。
consul: host: 10.200.110.100 port: 8500 enabled: true discovery: enabled: true hostname: 10.200.110.100 healthCheckInterval: 30s queryPassing: true
EnvironmentController的请求时用config-server的client端的健康检查发起的调用。看源码:
各个客户端在连接注册中心,获取到配置中心实例后,会调用上面这段代码逻辑从配置中心获取到 Environment数据变量,上线环境后,遇到了一个问题,查看日志,发现这块逻辑被不停的调用,每20多秒就会调用一次,application的name为 app,通过查看SpringCloudConfig的官方文档知道Config Server 通过一个健康指示器来检测配置的EnvironmentRepository是否正常工作。 默认情况下会向EnvironmentRepository询问一个名字为app的应用配置,EnvironmentRepository实例回应default配置。 也就是说当健康监视器默认开启的时候,会不停的调用findOne来检测,配置是否可用,是否会出现异常,
这段代码是org.springframework.cloud.config.server.config.ConfigServerHealthIndicator类里初始化名称为application名字为app的代码
@ConfigurationProperties("spring.cloud.config.server.health") public class ConfigServerHealthIndicator extends AbstractHealthIndicator { private EnvironmentRepository environmentRepository; private Map<String, Repository> repositories = new LinkedHashMap<>(); public ConfigServerHealthIndicator(EnvironmentRepository environmentRepository) { this.environmentRepository = environmentRepository; } @PostConstruct public void init() { if (this.repositories.isEmpty()) { this.repositories.put("app", new Repository()); } } //... }
如果想停止掉这样的检测可以通过配置health.config.enabled=false去关闭此功能。
看源码:org.springframework.cloud.config.client.ConfigClientAutoConfiguration.java
@Configuration public class ConfigClientAutoConfiguration { @Configuration @ConditionalOnClass(HealthIndicator.class) @ConditionalOnBean(ConfigServicePropertySourceLocator.class) @ConditionalOnProperty(value = "health.config.enabled", matchIfMissing = true) protected static class ConfigServerHealthIndicatorConfiguration { @Bean public ConfigServerHealthIndicator configServerHealthIndicator( ConfigServicePropertySourceLocator locator, ConfigClientHealthProperties properties, Environment environment) { return new ConfigServerHealthIndicator(locator, environment, properties); } } //...
二、Config配置中心的文件更新机制
server 用来获取远程的配置信息(默认为 Git 仓库),并且以接口的形式提供出去;
client 根据 server 提供的接口读取配置文件,以便于初始化自己的应用。
Git作为配置中心,将项目的配置文件放在远程,但为了更快的性能,一般会将git远程的信息同步到服务器(配置中心服务端)本地一份,那如何保证服务器本地和GIT远程的信息一致,就是我们本文要讨论的问题。
其中在远程git中更新了配置信息后,是怎么反应到微服务项目中的,分2种情况:自动更新和手动更新,其中手动更新又分2种情况,即重新启动微服务项目和手动调用刷新接口;自动刷新则通过WebHooks
1、手动更新
1.1 重新启动项目更新
即git远程的配置文件更新后,在自动化流水线中重新启动该项目,重新去配置中心获取远程git的最新信息,此时也分2种情况:
如果重新启动配置中心服务的,即configserver,则会将使用该配置中心的所有项目的配置文件都从远程拉取最新的信息到本地;
如果重新启动某个微服务项目,则只会将该服务项目对应配置文件从远程拉取到本地,其他项目的配置文件并不会同步。
配置中心服务端configserver的配置信息如下:
配置中心服务端configserver的配置信息如下: