SpringCloud统一配置中心

本篇简介

经过上两篇的介绍咱们已经掌握了SpringCloud中的注册中心组件Eureka以及服务间的调用方式RestTemplate和Feign。那么经过这两篇的内容,咱们基本能够知足一些简单项目的开发需求了。但一样上述的项目架构仍是有一些问题的。例如:java

  • 不方便维护: 由于在公司开发项目时,是有多个团队多个成员同时开发的,这样就避免不了若是有人修改项目的相关配置,那么可能会对其它项目组产生影响,甚至能够会致使项目代码冲突。
  • 信息不安全: 由于按照咱们以前的项目架构,咱们是须要将全部配置写到项目中的。例如数据库帐号及密码等。但若是将这些敏感的数据信息放到项目中的话,不免会有一些安全性的问题。由于全部项目开发者都有这些敏感信息的全部权限。
  • 开发效率低: 由于咱们以前的文章中介绍过了,每当咱们修改配置文件时,都是须要从新启动项目的。当咱们平时开发时还好,但若是修改生产已经运行的项目配置的话,那么就会涉及到项目上线及其重启,这可能会致使一些线上问题的产生。

SpringCloud为了解决上述的问题,因而提供了统一注册中心组件Config。经过这个组件,不一样的服务均可以从这个组件获取相关的配置信息。而这个组件也不存储其它服务的配置信息,而是经过远程仓库获取相关的配置信息。例如:Git、Svn等。这样当咱们修改不一样服务的配置信息时,咱们只要在远程仓库更改后,其它的服务也就能够经过配置中心获取到最新的配置信息了。而且这样还有一个好处,就是咱们能够经过远程仓库来设置不一样人员的权限,这样就解决了敏感信息的泄漏问题了。下面咱们来详细介绍一下怎么搭建一个配置中心。git


搭建配置中心

  1. 仍是和以前项目同样建立一个SpringBoot的项目。

title

  1. 这一步骤也是和以前建立的项目同样,设置项目相关参数。

title    
 

  1. 由于咱们配置中心也是一个Eureka的客户端,因此咱们在这一步还要选择Eureka的客户端的依赖。

  
title
    
除了要添加Eureka的客户端的依赖外,还要添加一个很是重要的依赖,也就是统一配置中心的组建Config依赖。
  
title 
  github

  1. 这一步骤没什么介绍的了,只要点击完成就能够了。

  
title

  

    
这样咱们就完成了配置中心的建立了,但这还没完。咱们尚未搭建完一个配置中心。由于按照以前咱们搭建注册中心的方式,咱们仍是须要在启动类上添加配置中心的注解的,下面咱们看一下启动类的源码:
  web

package com.jilinwula.springcloudconfig;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class SpringcloudConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudConfigApplication.class, args);
    }

}

由于配置中心是一个Eureka的客户端,因此咱们须要在启动类上添加了@EnableEurekaClient注解。还有另外一个注解就是配置中心的核心注解也就是@EnableConfigServer。下面咱们看一下该项目的配置信息:
 spring

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
spring:
  application:
    name: springcloud-config
server:
  port: 8081   

  
配置信息很简单和一个普通的Eureka客户端的配置同样,下面咱们启动这个项目来看一下项目的运行结果。下面为项目启动日志:
    数据库

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-03-17 18:41:54.442 ERROR 1522 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Invalid config server configuration.

Action:

If you are using the git profile, you need to set a Git URI in your configuration.  If you are using a native profile and have spring.cloud.config.server.bootstrap=true, you need to use a composite configuration.

当咱们满怀期待的觉的项目能够正常运行的时候,咱们发现项目启动竟然报错了。这是为何呢?按照咱们以前搭建注册中心时咱们的理解。配置中心是否是也会提供一个什么管理界面之类的。但结果让咱们很意外,程序竟然抛出了异常。这究竟是为何呢?这是由于咱们以前介绍过配置中心也不会保存其它服务的配置信息的,而是从远程仓库中获取配置信息,而后将配置信息暂存到本地,而后在对外提供服务。因此上述错误的根本缘由是由于咱们没有配置远程仓库的地址。若是咱们观察仔细的话,咱们能够经过上述的错误日志发现,日志已经提示咱们没有配置远程仓库的地址了,也就是日志中的Git URI。下面咱们配置一下配置中心的远程仓库地址。在配置以前,咱们首先要建立一个远程仓库。apache


  

建立配置中心仓库

  
建立远程仓库能够有不少种方式。比较经常使用的例如:GitHub、GitLab等。为了演示方便咱们就不在手动搭建Git服务器了,而是直接使用免费的GitHub了。下面咱们看一下怎么在GitHub中建立远程仓库。咱们首先访问GitHub的官网。下面为官方地址:
          json

https://github.com

title  
  
在使用GitHub建立仓库时须要先注册帐号,由于我本人已经注册过了,因此咱们就直接演示登录了。也就上图中红色标识的地方。
  
title   bootstrap

当咱们登录成功后,就会显示本身的我的主页,也就是上图所示,咱们直接点击上图中红色表示,来建立一个新的仓库。
  
title  
  
当咱们输完仓库的名字及其仓库描述后,而后点下方绿色的按钮便可。  
  
title
  
当咱们建立完仓库后,咱们点击上图中红色的连接,在该仓库中建立一个新的文件,而后将服务的相关配置写到这个文件中。 
 
title      api

咱们直接点击下图中的绿色按钮便可。    
  
title  
  
下图是保存完文件后返回的页面。
  
title  
  
咱们看上图中浏览器地址栏显示的路径不是咱们默认建立的仓库地址,而显示了分支的名字。咱们在配置中心配置时是不能配置这个地址的。咱们点击一下仓库地址。这样浏览器地址栏就会显示仓库正确的地址了。也就是下图中的地址。
  
title


  

指定配置中心仓库地址

  
既然配置中心的仓库咱们已经建立完了,下面咱们将该仓库的地址配置到配置中心的项目中,而后在启动一下项目看看项目还会不会抛出异常。下图为配置中心仓库的配置:
  

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
spring:
  application:
    name: springcloud-config
  cloud:
    config:
      server:
        git:
          uri: https://github.com/jilinwula/springcloud-config
server:
  port: 8081  

若是咱们此时启动项目并观察启动日志,咱们会发现日志在启动时已经不会抛出异常了,这就说明咱们的远程仓库配置成功了。下面咱们看一下怎么访问配置中心返回的配置。

  

  

访问配置中心

  
按照咱们以前的理解,咱们直接访问该项目的端口,而后看看会返回什么样的信息。下面为访问路径:     

GET http://127.0.0.1:8081

返回结果:

GET http://127.0.0.1:8081

HTTP/1.1 404 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 18 Mar 2019 03:27:23 GMT

{
  "timestamp": "2019-03-18T03:27:23.930+0000",
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/"
}

Response code: 404; Time: 81ms; Content length: 121 bytes
  

咱们看程序竟然又抛出了异常,但这异常咱们应该比较常见了,由于以前文章中都介绍过,这是由于咱们没有配置Controller致使。实际在配置中心中咱们是不须要写任何Controller的,甚至连一行代码都不须要写的。由于上面已经介绍过,配置中心只是将远程仓库的配置信息拉取到本地,而后在对外提供服务。这时可能有人会想,既然不须要写Controller那咱们怎么访问配置中心返回的配置信息呢?不用着急,既然配置中心不须要咱们写任何代码而能够支持其它服务访问配置信息,那结果必定是SpringCloud默认为咱们封装好了,咱们只须要按照配置中心中默认的访问规则就能够返回配置信息的结果了。下面咱们看一下配置中心的访问规则。

  

  

配置中心访问规则

  
还记着咱们在远程仓库中新建立的client.yml文件吗?咱们已经将相关的配置信息添加到了该文件中。配置中心实际上就是返回该文件的内容。也就是经过配置中内心的Git URI找到仓库中的配置文件。因此下面咱们直接访问这个文件看看会返回什么结果。

GET http://127.0.0.1:8081/client.yml

返回结果:

GET http://127.0.0.1:8081/client.yml

HTTP/1.1 404 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 18 Mar 2019 03:42:49 GMT

{
  "timestamp": "2019-03-18T03:42:49.473+0000",
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/client.yml"
}

Response code: 404; Time: 30ms; Content length: 131 bytes

  
咱们看接口的返回仍是出错。刚刚不是说SpringCloud自动为咱们处理吗?这是由于咱们在经过配置中心访问远程仓库文件时有一个特殊的规则。就是:仓库文件名-随机名.后缀。只有这样配置中心才会正确的返回远程仓库中的配置信息。也就是下面的访问路径:
    

http://127.0.0.1:8081/client-test.yml

返回结果:
 

GET http://127.0.0.1:8081/client-test.yml

HTTP/1.1 200 
Content-Type: text/plain
Content-Length: 156
Date: Mon, 18 Mar 2019 03:59:35 GMT

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
server:
  port: 8082
spring:
  application:
    name: springcloud-client


Response code: 200; Time: 2306ms; Content length: 156 bytes
  

  
咱们看这时接口就成功的返回远程仓库中的配置信息了。为了更直观的感觉,咱们修改一下远程仓库的配置,而后在请求一下上述的接口,看看还能不能返回仓库中最新的配置。下面为远程仓库中配置文件中的更改。
原仓库配置:
  

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
spring:
  application:
    name: springcloud-client
server:
  port: 8082

现仓库配置:

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
spring:
  application:
    name: springcloud-client
server:
  port: 8082
userinfo:
  username: jilinwula
  password: 123456789

  
下面咱们继续访问下面的接口,而后看一下配置中心返回的结果。
    

GET http://127.0.0.1:8081/client-test.yml

返回结果:

GET http://127.0.0.1:8081/client-test.yml

HTTP/1.1 200 
Content-Type: text/plain
Content-Length: 210
Date: Mon, 18 Mar 2019 07:10:10 GMT

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
server:
  port: 8082
spring:
  application:
    name: springcloud-client
userinfo:
  password: 123456789
  username: jilinwula


Response code: 200; Time: 4266ms; Content length: 210 bytes
  

咱们看配置中心已经成功的返回了远程仓库中最新的配置信息了。(备注:特别注意若是咱们在远程仓库中的配置信息包含中文的话,配置中心在返回时可能会致使中文乱码,这种问题咱们在以后的文章中在作介绍)。下面咱们介绍了一个配置中心返回配置的规则。所有的路径为:      

/{label}/{application}-{profile}.yml

下面咱们详细介绍上面参数的含义:

  • label: 远程仓库的分支名字。若是我没有指定该参数默认按照master访问,但在访问时,若是分支为master能够省略。
  • application: 远程仓库文件的名字,也就是上图中的client。
  • profile: 不一样环境的名字。例如:dev(开发环境)、test(测试环境)、pro(生产环境)等。

这时可能有人会产生疑问?上面的远程仓库中也没有什么client-dev.yml文件啊为何咱们访问这个路径还能够成功的返回配置信息呢?这里面还有一个默认的约定。那就是配置中心在从远程仓库获取配置信息时,除了要获取client-dev.yml文件外,还会获取client.yml。而后将这两份文件的内容合并在一块儿给服务返回。由于咱们没有client-dev.yml文件,因此咱们在访问client-dev.yml时就会直接获取client.yml文件的内容。为了测试咱们上面所说的,咱们在远程仓库新建立一个client-dev.yml文件,而后咱们故意将这两份文件的内容编写的不一致。而后看一下配置中返回的结果。
client-dev.yml:

profile:
  active: dev  

访问如下接口地址:
    

GET http://127.0.0.1:8081/client-dev.yml

返回结果:
  

GET http://127.0.0.1:8081/client-dev.yml

HTTP/1.1 200 
Content-Type: text/plain
Content-Length: 233
Date: Mon, 18 Mar 2019 08:05:39 GMT

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
profile:
  active: dev
server:
  port: 8082
spring:
  application:
    name: springcloud-client
userinfo:
  password: 123456789
  username: jilinwula


Response code: 200; Time: 2360ms; Content length: 233 bytes  

咱们看配置中心返回的结果已经包含远程仓库中client-dev.yml的内容了。那此时若是咱们访问client-test.yml呢?会返回什么样的结果呢?咱们验证一下。

GET http://127.0.0.1:8081/client-test.yml 
  
返回的结果:
GET http://127.0.0.1:8081/client-test.yml

HTTP/1.1 200 
Content-Type: text/plain
Content-Length: 210
Date: Mon, 18 Mar 2019 08:08:19 GMT

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
server:
  port: 8082
spring:
  application:
    name: springcloud-client
userinfo:
  password: 123456789
  username: jilinwula


Response code: 200; Time: 2291ms; Content length: 210 bytes

  
咱们看配置中心返回的结果仍是client.yml文件而没有返回client-dev.yml的内容。但为何配置中心要这样处理呢?这样处理有什么好处呢?若是咱们熟悉SpringBoot开发咱们就会知道application.yml文件,该文件就是不一样环境的共有文件。咱们能够将不一样环境配置的信息配置在这个文件中,这样就减小了不一样环境中有大量相同配置的问题了。因此配置中心中的上述功能也是解决这样问题的。除了上述的功能外,配置中心还对远程仓库中返回的数据的格式作了优化。由于按照咱们远程仓库中的名字是yml文件。但配置中心能够直接支持返回json格式的配置信息。也就是下面的请求路径:
  

GET http://127.0.0.1:8081/client-dev.json

返回结果:

GET http://127.0.0.1:8081/client-dev.json

HTTP/1.1 200 
Content-Type: application/json
Content-Length: 246
Date: Mon, 18 Mar 2019 08:19:11 GMT

{
  "eureka": {
    "client": {
      "service-url": {
        "defaultZone": "http://127.0.0.1:8761/eureka"
      }
    }
  },
  "profile": {
    "active": "dev"
  },
  "server": {
    "port": 8082
  },
  "spring": {
    "application": {
      "name": "springcloud-client"
    }
  },
  "userinfo": {
    "password": 123456789,
    "username": "jilinwula"
  }
}

Response code: 200; Time: 3762ms; Content length: 246 byte  

咱们看这时配置中心返回的格式就是json类型了。上述内容就是配置中心的访问规则,固然咱们尚未测试不一样分支的状况,在这里就不依依演示了,和上述的内容基本一致。下面咱们介绍一下怎么在客户端服务中使用配置中心返回配置。


  

配置中心客户端配置

  
为了方便测试,咱们需新建一个Eureka客户端项目,而后在这个项目中新建立的项目中使用配置中心获取该项目的相关配置。由于咱们以前已经介绍过了怎么建立一个Eureka客户端项目了。在这里咱们就不演示怎么建立了。而是直接在该项目中添加配置中心的依赖。具体配置以下:
pom.xml:

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-config-client</artifactId>
</dependency>  

application.yml:             

spring:
  application:
    name: client
  cloud:
    config:
      discovery:
        enabled: true
        service-id: springcloud-config
      profile: dev
  

下面咱们详细介绍一下上述配置中参数的说明:

  • service-id: 配置中心中的项目名字
  • profile: 对应远程仓库中环境的名字
  • name: 远程仓库里配置名字的名字

正是上面这3个参数的配置,客户端就能够按照配置中心访问的规则获取远程仓库的信息。下面咱们启动项目并访问一下注册中心看一下该服务是否注册成功。
  
title  
咱们看注册中心已经成功的检测到了该服务。下面咱们试一下能不能正确的经过配置中心获取远程仓库的配置。咱们在项目中新建立一个用户类而后按照咱们以前介绍过的知识,将配置文件中的信息读取到该类属性中,而后经过Controller返回该类的信息。下面为具体代码:
UserInfo:

package com.jilinwula.springcloudclient;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties("userinfo")
public class UserInfo {
    private String username;
    private String password;
}

UserInfoController:

package com.jilinwula.springcloudclient;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/userinfo")
public class UserInfoController {

    @Autowired
    private UserInfo userInfo;

    @GetMapping("/get")
    public Object get() {
        return userInfo;
    }
}
  

下面咱们访问一下UserInfoController看一下可否返回远程仓库的配置。

GET http://127.0.0.1:8082/userinfo/get

返回结果:

org.apache.http.conn.HttpHostConnectException: Connect to 127.0.0.1:8082 [/127.0.0.1] failed: Connection refused (Connection refused)

咱们接口返回的结果竟然抛出了异常。这是为何呢?这是由于程序在启动时经过@ConfigurationProperties注解直接获取项目中配置文件中的配置。但又因为咱们的配置是从配置中心中远程仓库获取的,因此该注解就获取不到配置中心中的配置了。那怎么解决呢?其实解决这样的问题也很简单,咱们只要保证在注解获取配置时,项目已经从配置中心获取到了配置就能够了。那怎么保证呢?SpringBoot为了解决配置加载的顺序的问题,因而提供了除application.yml文件外,还提供了bootstrap.yml文件,而且默认优先加载bootstrap.yml文件。也就是在程序启动时就已经加载完了。这样就解决了上述的错误了。下面咱们将application.yml文件名字修改成bootstrap.yml文件在访问一下上述接口。

GET http://127.0.0.1:8082/userinfo/get

返回结果:

GET http://127.0.0.1:8082/userinfo/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 18 Mar 2019 15:36:14 GMT

{
  "username": "jilinwula",
  "password": "123456789"
}

Response code: 200; Time: 147ms; Content length: 47 bytes
  

咱们看接口此次返回远程仓库中的配置了。为了更直观的感觉,咱们在修改一下远程仓库中的配置信息,而后在访问一下该接口。下面为远程仓库配置:

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka    
server:
  port: 8082
userinfo:
  username: admin
  password: 123456789
  

咱们将username参数的值由jilinwula修改为了admin。下面咱们在访问一下接口看一下返回结果。  

GET http://127.0.0.1:8082/userinfo/get

返回结果:

GET http://127.0.0.1:8082/userinfo/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 18 Mar 2019 15:42:33 GMT

{
  "username": "jilinwula",
  "password": "123456789"
}

Response code: 200; Time: 14ms; Content length: 47 bytes
  

咱们发现接口返回的竟然仍是以前的信息。这是为何呢?这个缘由是项目只有在启动时才经过配置中心获取远程仓库中的信息。咱们虽然改了远程仓库中的信息,但该项目获取的仍是上一次启动时的远程仓库的信息,因此该接口返回的仍是以前的信息。因此解决这样的问题很简单,咱们只要重启一下项目就能够了。下面咱们启动项目后在看一下该接口的返回结果。

GET http://127.0.0.1:8082/userinfo/get

返回结果:

GET http://127.0.0.1:8082/userinfo/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 18 Mar 2019 15:44:27 GMT

{
  "username": "admin",
  "password": "123456789"
}

Response code: 200; Time: 131ms; Content length: 43 bytes
  

咱们看这时该接口就正确的返回了远程仓库中最新的配置信息了。下面咱们介绍一下多配置中心的配置。


多配置中心

  
为了解决让程序高可用,一般在项目开发时,若是只有一个配置中心是不稳定的。若是该配置中心出现了问题就会致使整个项目运行失败。因此咱们一般会配置多个配置中心。下面咱们建立第二个配置中心。咱们一样不演示配置中心具体的建立了,而是直接看该配置中心的配置。

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
spring:
  application:
    name: springcloud-config
  cloud:
    config:
      server:
        git:
          uri: https://github.com/jilinwula/springcloud-config
server:
  port: 8083  

下面咱们启动该项目并看一下注册中心看看是否注册成功。
  
title 
下面咱们启动客户端服务看一下客户端服务会调用哪一个配置中心获取配置信息。下面启动日志。

2019-03-19 10:01:07.493  INFO 13533 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Multiple Config Server Urls found listed.
2019-03-19 10:01:07.494  INFO 13533 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://bogon:8081/  

咱们看客户端的日志输出了8081端口,这也就是说当前项目调用的是8081端口的配置中心服务。若是咱们屡次重启项目,咱们就会发现客户端服务获取配置中心的服务端口是随机变化的。这也就是说明配置中心服务的负载策略是随机,至于怎么修改,在这里咱们就不详细介绍了,若有不了解的,能够看一下注册中那篇文章中的负载策略。

  

  

修改注册中心默认端口

  
咱们知道注册中心的默认端口为8761。那若是咱们修改了注册中心中的默认端口而后在启动客户端的项目会怎么样呢?下面咱们测试一下。下面咱们将配置中心的端口修改成8084。而后在启动一下客户端的服务看一下启动日志。(注意别忘记将配置中内心的注册中心的地址修改成8084)。下面我客户端项目的启动日志。

2019-03-19 10:22:57.833 ERROR 13635 --- [           main] c.n.d.s.t.d.RedirectingEurekaHttpClient  : Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}

com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused (Connection refused)  

咱们发现,咱们只是修改了注册中心的默认端口,客户端在启动的时候,竟然抛出了异常了。而且经过启动日志发现,客户端里竟然仍是调用了8761端口。这是为何呢?若是咱们仔细想一想,好像有一个注册中心的配置的地方没有改。也就是远程仓库中的地址。下面咱们访问下面的地址看一下远程仓库中的配置。  

GET http://127.0.0.1:8081/client-dev.yml

返回结果:

GET http://127.0.0.1:8081/client-dev.yml

HTTP/1.1 200 
Content-Type: text/plain
Content-Length: 154
Date: Tue, 19 Mar 2019 02:30:37 GMT

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
server:
  port: 8082
userinfo:
  password: 123456789
  username: admin


Response code: 200; Time: 2638ms; Content length: 154 bytes
  

咱们发现远程仓库中的注册中心地址仍是8761端口。下面咱们将远程仓库中的注册中心端口也修改成8084。而后咱们在访问上述端口看一下配置中心返回的配置是否是最新的。  

GET http://127.0.0.1:8081/client-dev.yml

返回结果:

GET http://127.0.0.1:8081/client-dev.yml

HTTP/1.1 200 
Content-Type: text/plain
Content-Length: 154
Date: Tue, 19 Mar 2019 02:36:32 GMT

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8084/eureka
server:
  port: 8082
userinfo:
  password: 123456789
  username: admin


Response code: 200; Time: 3622ms; Content length: 154 bytes
  

如今咱们在启动一下客户端项目看一下启动日志。

2019-03-19 10:43:05.672 ERROR 13686 --- [nfoReplicator-0] c.n.d.s.t.d.RedirectingEurekaHttpClient  : Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}

com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused (Connection refused)  

咱们发现项目启动时仍是会抛出异常,而且日志显示仍是获取的是8761端口。这时有人就会感受到奇怪了,为何还会抛出异常呢?咱们已经将项目中全部注册中心配置的地方都修改成了8084了。为何项目启动时还找8761端口呢?咱们再看一下客户端中的配置信息,而后在分析一下。

spring:
  application:
    name: client
  cloud:
    config:
      discovery:
        enabled: true
        service-id: springcloud-config
      profile: dev
  

上面的配置咱们以前介绍过,主要是经过name+profile参数来经过配置中心返回远程仓库的信息。但这里咱们忽略了一个很重要的问题。就是客户端自己首先要经过注册中心获取到配置中心的地址,而后在经过配置中心在获取远程仓库的配置。上面咱们的配置中没有指定注册中心的地址,因此程序在启动时就会抛出异常。由于它根本就获取不到配置中心。这时可能有人会问,那为何咱们没有改注册中心默认端口时,项目启动不会抛出异常呢?这是由于SpringCloud默认会找项目中配置文件里的注册中心地址,若是没有找到则调用默认的注册中心地址。若是找到了就按照配置文件中注册中心地址进行服务调用。因此咱们以前的项目在启动时是不会抛出异常的。既然咱们知道了问题所在,那解决就比较容易了,咱们只要将远程仓库中注册中心的配置放到项目里就能够了。下面为客户端项目配置:

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8084/eureka
spring:
  application:
    name: client
  cloud:
    config:
      discovery:
        enabled: true
        service-id: springcloud-config
      profile: dev
  

这时咱们在启动项目时就会发现项目能够正常启动了,如些此时查看注册中心发现客户端服务已经在注册中心注册成功了。
  
title 

  

  

自动更新配置 

  
咱们以前介绍过若是咱们直接更改远程仓库的配置时,客户端是获取不到最新的配置的。只有当咱们重启自动客户端服务才能够。但这明显不太方便。下面咱们分析一下为何当远程仓库更新配置时,客户端获取不到最新的配置。答案很简单,由于服务只有在启动的时候才经过配置中心获取远程仓库的配置,当服务启动后,若是远程仓库中的配置进行了修改,客户端服务由于在启动时已经获取了配置信息了,如今就不会在获取了。因此也就不会获取到最新的配置了。那怎么解决了呢?解决的方式很简单,就是每当远程仓库配置作修改后,都通知客户端服务,这样客户端服务就能够获取到最新的配置信息了。那怎么通知呢?那就是使用消息队列。每当远程仓库更新时,都向消息队列中发起一条消息,而后客户端服务发现消息队列里的消息后,而后在从新获取最新的配置。那咱们使用哪一个消息队列呢?SpringCloud默认为咱们支持了RabbitMQ。固然咱们也可使用其它的消息队列,只不过若是使用RabbitMQ的话,SpringCloud会为咱们提供自动化默认配置。也就是咱们什么都不须要配置就能够直接使用该队列服务。下面咱们看一下具体怎么配置。若是要使用队列服务时,那就须要在使用的项目中添加相应的依赖,下面咱们看一下配置中内心依赖的修改。

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>  

咱们只要在配置中心的项目里添加上面的依赖就能够。而后咱们一样在客户端的服务中添加上面的依赖。而后咱们启动项目后,在看一下消息队列里的消息是否有变化。怎么安装RabbitMQ的内容咱们就不在这里作详细介绍了,咱们直接访问消息队列的管理界面便可。下面地址为RabbitMQ默认的管理界面地址:           

http://127.0.0.1:15672

title 
  
咱们看上图中的消息队列中默认多了两个队列并对应着配置中心服务和客户端服务。若是咱们这时修改远程仓库中的配置,而后在请求客户端接口时,那么客户端获取仍是和以前同样是旧的配置信息。虽然咱们使用了队列可是当配置中心修改时,消息队列也不知道配置进行了修改,因此咱们须要一个触发点让消息队列知道配置信息进行了更改。在SpringCloud中因而提供了bus-refresh 接口,该接口的目的就是告诉配置中心,远程仓库中的配置进行了修改。下面咱们访问一下该接口。  

POST http://127.0.0.1:8081/actuator/bus-refresh

返回结果:

POST http://127.0.0.1:8081/actuator/bus-refresh

HTTP/1.1 405 
Allow: GET
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 19 Mar 2019 13:45:28 GMT

{
  "timestamp": "2019-03-19T13:45:28.346+0000",
  "status": 405,
  "error": "Method Not Allowed",
  "message": "Request method 'POST' not supported",
  "path": "/actuator/bus-refresh"
}

Response code: 405; Time: 142ms; Content length: 165 bytes
  

当咱们访问SpringCloud为咱们提供的bus-refresh接口时,竟然抛出了异常。这是为何呢?这是由于SpringCloud默认配置了访问权限,而且该权限默认是禁止访问的。因此咱们在使用bus-refresh接口时,必须先将该接口的访问权限设置为容许,这样就能够了访问该接口了。下面为配置中内心配置的修改:

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8084/eureka
spring:
  application:
    name: springcloud-config
  cloud:
    config:
      server:
        git:
          uri: https://github.com/jilinwula/springcloud-config
server:
  port: 8081
management:
  endpoints:
    web:
      exposure:
        include: "bus-refresh"  

下面咱们从新启动配置中心后,在访问一下下面的接口。 

POST http://127.0.0.1:8081/actuator/bus-refresh

返回结果:

POST http://127.0.0.1:8081/actuator/bus-refresh

HTTP/1.1 204 
Date: Tue, 19 Mar 2019 13:56:43 GMT

<Response body is empty>

Response code: 204; Time: 3487ms; Content length: 0 bytes
  

咱们看这时接口就访问正常了。下面咱们测试一下,看看这样的方式,会不会自动更新配置。咱们首先访问下面客户端接口看一下该接口返回的信息是什么?  

GET http://127.0.0.1:8082/userinfo/get

返回结果:

GET http://127.0.0.1:8082/userinfo/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 19 Mar 2019 14:23:34 GMT

{
  "username": "admin",
  "password": "12345"
}

Response code: 200; Time: 134ms; Content length: 39 bytes
  

下面咱们修改一下远程仓库的配置信息。

server:
  port: 8082
userinfo:
  username: admin
  password: 54321  

若是咱们此时在访问上述的接口的话,那该接口返回的结果必定仍是早的信息。这是我们以前测试过的。下面咱们先调用一下bus-refresh接口。 

POST http://127.0.0.1:8081/actuator/bus-refresh

返回结果:

POST http://127.0.0.1:8081/actuator/bus-refresh

HTTP/1.1 204 
Date: Tue, 19 Mar 2019 14:28:52 GMT

<Response body is empty>

Response code: 204; Time: 3344ms; Content length: 0 bytes
  

咱们这时在访问客户端接口看一下客户端能不能获取到最新的配置信息。   

GET http://127.0.0.1:8082/userinfo/get

返回结果:

GET http://127.0.0.1:8082/userinfo/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 19 Mar 2019 14:30:20 GMT

{
  "username": "admin",
  "password": "12345"
}

Response code: 200; Time: 13ms; Content length: 39 bytes
  

咱们发现客户端服务仍是没有获取到最新远程仓库中的配置,这又是怎么回事呢?这是由于咱们还差最后一个环节,也就是须要在咱们获取动态参数的地方添加@RefreshScope注解。只有添加了该注解,才能达到动态刷新的功能。下面咱们在客户端的Controller中添加@RefreshScope注解。而后在从新启动一下客户端的服务。(备注:特别注意当咱们客户端服务从新启动时,就会从新经过配置中心获取远程仓库最新的配置了。因此咱们为了测试动态刷新的功能,应该在该服务启动后,而后在从新修改一下远程仓库的配置,而后在调用bus-refresh接口,看一下客户端服务是否能获取到最新的配置)。下面为访问的客户端接口:  

GET http://127.0.0.1:8082/userinfo/get

返回结果:

GET http://127.0.0.1:8082/userinfo/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 19 Mar 2019 14:30:20 GMT

{
  "username": "admin",
  "password": "54321"
}

Response code: 200; Time: 13ms; Content length: 39 bytes
  

下面咱们将远程仓库中的参数修改一下而后在测试一下。

server:
  port: 8082
userinfo:
  username: admin
  password: 12345  

咱们在访问一下客户端的接口。(备注:不要忘记了咱们应该先访问一下bus-refresh接口)。下面继续请求客户端接口:

GET http://127.0.0.1:8082/userinfo/get

返回的结果:

GET http://127.0.0.1:8082/userinfo/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 19 Mar 2019 14:56:26 GMT

{
  "username": "admin",
  "password": "12345"
}

Response code: 200; Time: 13ms; Content length: 39 bytes
  

咱们看这回咱们就达到了动态刷新的功能了。但咱们发现还有一点不太方便就是每当咱们修改远程仓库配置时,都要手动调用了bus-refresh接口。那有没有什么办法呢?答案必定仍是有的。但这不是SpringCloud的功能,而是GitHub的功能。下面咱们详细介绍一下。

  

  

Webhooks配置

  
GitHub中有一个功能就是当文件变动、提交、评论时,能够自动触发一个请求。咱们正好能够利用这个功能来知足咱们自动刷新的功能。下面咱们看一下怎么配置Webhooks。下面咱们打开远程仓库中的项目。而后点击红色连接。
title 
title   
title   
title   
咱们看上面最后一张图中须要咱们指定的Payload URL参数,也就是当远程仓库中有变化时,GitHub就会调用咱们指定的这个URL。由于Payload URL参数是须要外网调用的,因此为了测试方便,咱们直接使用了NATAPP工具,经过该工具能够作内网穿透,也就是经过该工具为咱们分配的随机的域名能够映射到咱们本地的接口。咱们只要将该工具外网域名地址映射到咱们配置中心的端口便可。下面咱们在更新一下远程仓库中的配置,而后直接访问客户端的接口,看看能不能直接获取到最新的远程仓库中的配置。

server:
  port: 8082
userinfo:
  username: jilinwula
  password: jilinwula
  

下面咱们直接访问客户端的服务的接口看一下返回的结果。      

GET http://127.0.0.1:8082/userinfo/get

返回的结果:

GET http://127.0.0.1:8082/userinfo/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 20 Mar 2019 05:46:34 GMT

{
  "username": "admin",
  "password": "12345"
}

Response code: 200; Time: 204ms; Content length: 49 bytes
  

咱们发现客户端服务获取的配置信息仍是旧的配置,仍是没有获取到最新的配置,这是为何呢?咱们查看一下GitHub上的WebHook,在网页下面竟然有警告信息。
  
title 
咱们看一下这警告信息是什么错误。

{"timestamp":"2019-03-19T16:02:46.565+0000","status":400,"error":"Bad Request","message":"JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token\n at [Source: (PushbackInputStream); line: 1, column: 300] (through reference chain: java.util.LinkedHashMap[\"commits\"])","path":"/actuator/bus-refresh"}  

咱们看上述报的错误是格式化的错误。这是为何呢?咱们在本地调用一下这个接口看一下返回的结果。        

POST http://mzqha4.natappfree.cc/a...

返回的结果:

POST http://mzqha4.natappfree.cc/actuator/bus-refresh

HTTP/1.1 204 
Date: Wed, 20 Mar 2019 06:15:14 GMT

<Response body is empty>

Response code: 204; Time: 4074ms; Content length: 0 bytes
  

咱们看这是该接口返回的结果。但咱们发现上述接口中Response返回的Code码是204,意味着返回的结果是空的。由于该接口压根就不须要有返回值。若是咱们本地调用该接口的话,没有任何问题,可是WebHook中对返回的Code码有要求,必须返回200才会认为该请求发送成功。那怎么解决呢?很简单,咱们本身在配置中心新建立一个Controller,让WebHook直接调用这个Controller中的接口,而后咱们在这个Controller中本身在调用一下actuator/bus-refresh 接口。由于是咱们本身写的Controller了,因此咱们能够很方便的控制该接口的返回值,这样也就能保证该接口返回的Code码为200了。(备注:只要咱们正常返回数据,Code码默认就会是200)。下面咱们看一下这个Contrller中的源码:

package com.jilinwula.springcloudconfig;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/config")
public class ConfigController {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private RestTemplate restTemplate;


    @PostMapping("/sync")
    public Object sync() {
        Map<String, Integer> map = new HashMap<String, Integer>();
        ServiceInstance serviceInstance = loadBalancerClient.choose("springcloud-config");
        String url = String.format("http://%s:%s/actuator/bus-refresh", serviceInstance.getHost(), serviceInstance.getPort());
        restTemplate.postForObject(url, map, Object.class);
        map.put("code", 0);
        return map;
    }
}
  

上述的代码比较简单咱们就不详细介绍了。下面咱们先在本地调用一下,看一下该接口是否可以达到自动刷新配置。   

POST http://127.0.0.1:8081/config/sync

返回结果:

POST http://127.0.0.1:8081/config/sync

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 20 Mar 2019 06:57:58 GMT

{
  "code": 0
}

Response code: 200; Time: 54531ms; Content length: 10 bytes
  

咱们看这回接口返回的Code码为200了。下面咱们将上述接口配置到WebHook中。而后咱们在修改一下配置,看看WebHook中是否还有警告信息。
  
title 
  
咱们看这回WebHook不显示警告信息了。若是咱们这时在访问一下客户端的服务,咱们就会发现,客户端接口能够返回最新的配置信息了。

  

  
上述内容就是本篇的所有内容,若有不正确的地方欢迎留言,谢谢。

  

  

源码地址

https://github.com/jilinwula/jilinwula-springcloud-config          

相关文章
相关标签/搜索