spring cloud config 從0到1

1、簡介

1.爲何要配置中心?

隨着系統微服務的不斷增長,首要考慮的是系統的可伸縮、可擴展性好,隨之就是一個配置管理的問題。各自管各自的開發時沒什麼問題,到了線上以後管理就會很頭疼,到了要大規模更新就更煩了。 配置中心就是一個比較好的解決方案,下圖就是一個配置中心的解決方案: html

常見的配置中心的實現方法有:java

  1. 硬編碼(缺點:須要修改代碼,風險大)
  2. 放在xml等配置文件中,和應用一塊兒打包(缺點:須要從新打包和重啓)
  3. 文件系統中(缺點:依賴操做系統等)
  4. 環境變量(缺點:有大量的配置須要人工設置到環境變量中,不便於管理,且依賴平臺)

2.spring cloud config

特性

  1. 集中式管理分佈式環境下的應用配置
  2. 配置存儲默認基於 git 倉庫,可進行版本管理
  3. 基於spring環境和Spring應用無縫集成
  4. 提供服務端和客戶端支持(java)
  5. 基於restful接口獲取配置,客戶端可實現多語言
  6. 可集羣部署,實現高可用
  7. 配合eureke可實現服務發現,配合cloud bus可實現配置推送更新
  8. 支持數據結構豐富,yml, json, properties

演示圖

  1. 配置數據存儲在Git等版本倉庫中
  2. Config Server獲取最新的配置數據
  3. Config Client基於restful接口獲取配置

2、初級使用

1.構造config server

(1) pom.xml依賴

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.6.RELEASE</version>
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Dalston.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
</dependencies>

(2) 程序入口

@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServer.class, args);
    }
}

(3) 配置文件

在bootstrap.yml中添加配置:nginx

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: http://gitlab.**.cn/java/config-repository.git
          username: ***
          password: ***
          default-label: master
          search-paths: java*,ruby*,go*

server:
  port: 9992

search-paths表示git倉庫的存放配置文件的目錄git

(4) 啓動程序

在上面的git倉庫中添加一個config-client-test.yml配置文件,配置內容以下:spring

env: alpha

訪問http://localhost:9992/config-client/test/master 能夠查看配置信息json

表示config server啓動成功bootstrap

{
  "name": "config-client",
  "profiles": Array[1][
    "test"
  ],
  "label": "master",
  "version": "1b28f79de8c4097dc68a4be986d13232662eb036",
  "state": null,
  "propertySources": Array[1][
    {
      "name": "http://gitlab.**.cn/java/config-repository.git/java/config-client-test.yml",
      "source": {
        "env": "alpha"
      }
    }
  ]
}

URL與配置文件的映射關係以下:安全

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

http://localhost:9992/master/config-client-test.ymlruby

env: alpha

http://localhost:9992/master/config-client-test.json服務器

{
  "env": "alpha"
}

http://localhost:9992/master/config-client-test.properties

env: alpha

2.構造config client

(1) pom.xml依賴

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.6.RELEASE</version>
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Dalston.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
</dependencies>

(2) 在bootstrap.yml文件中添加相關配置

spring:
  application:
    name: config-client
  profiles:
    active: test
  cloud:
    config:
      label: master
      uri: http://localhost:9992/

(3) 使用配置

啓動類

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

Controller類

@RestController
public class EnvController {

  @Value("${env}")
  private String env;

  @RequestMapping(value = "/hi")
  public String hi() {
    return env;
  }
}

(4) 啓動觀察日誌

2017-11-08 17:56:49.730  INFO 3680 --- [  restartedMain] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at: http://localhost:9992/
2017-11-08 17:56:50.313  INFO 3680 --- [  restartedMain] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=config-client, profiles=[test], label=master, version=06ef59ccd30be2089bc8c7d214b57f0497f1f3f5, state=null
2017-11-08 17:56:50.313  INFO 3680 --- [  restartedMain] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='configService', propertySources=[MapPropertySource [name='configClient'], MapPropertySource [name='http://****/config-repository.git/java/config-client-test.yml']]]

(5) 讀取配置

訪問http://localhost:8080/hi 直接返回

alpha

3、高級使用

1.使用Spring Security進行安全控制

(1) config-server端配置

pom加入依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

bootstrap.yml文件中加入:

security:
  user:
    name: admin
    password: 123

從新啓動後進入頁面的時候要求輸入用戶名和密碼

(2) config-client端配置

config-client須要在配置文件中添加驗證信息:

spring:
  application:
    name: config-client
  profiles:
    active: test
  cloud:
    config:
      label: master
      uri: http://localhost:9992/
      username: admin
      password: 123

2.配置中心微服務化、集羣化

原理圖以下所示:

負載均衡能夠用nginx,這樣spring.cloud.uri配置寫域名就行

這裏只介紹用spring cloud服務發現組件eureka配置

(1) 準備eureka-server

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
    </dependency>
</dependencies>

spring:
  application:
    name: eureka-server
server:
  port: 9991
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9991/eureka/
    fetch-registry: false
    register-with-eureka: false

@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(EurekaServer.class);
        application.run(args);
    }
}

(2) 改造config-server

修改pom文件,添加maven依賴

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

在程序的入口Application類加上@EnableEurekaClient或者@EnableDiscoveryClient註解

配置文件中添加註冊地址,將服務註冊到eureka-server中

eureka:
  client:
    service-url:
      defaultZone: http://localhost:9991/eureka/

(3) 改造config-client

修改pom文件,添加maven依賴

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

在程序的入口Application類加上@EnableEurekaClient或者@EnableDiscoveryClient註解

修改配置文件,將其註冊到eureka-server服務

spring:
  application:
    name: config-client
  profiles:
    active: test
  cloud:
    config:
      label: master
      username: admin
      password: 123
      discovery:
        enabled: true
        service-id: config-server
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9991/eureka/

經過service-id查找config-server服務,可部署多臺config-server,實現集羣化部署達到高可用

(4) 啓動

訪問eureka-server的服務器,能夠看到config-server和config-client同時都註冊在上面:

(5) 讀取配置

訪問http://localhost:8080/hi 直接返回,表示服務部署成功

alpha

3.刷新配置信息

(1) 單臺服務配置刷新

config-client項目

修改pom文件,添加maven依賴,能夠調用/refresh接口刷新配置

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

boostrap.yml添加配置,暫時去掉安全認證

management:
  security:
    enabled: false

修改配置引用的地方,添加 @RefreshScope

@RestController
@RefreshScope
public class EnvController {

  @Value("${env}")
  private String env;

  @RequestMapping(value = "/hi")
  public String hi() {
    return env;
  }
}

從新啓動 訪問http://localhost:8080/hi 返回

alpha

修改git倉庫裏config-client-test.yml文件

env: alpha123

調用refresh接口

curl -X POST http://localhost:8080/refresh

從新訪問返回 alpha123表示配置刷新成功

alpha123

(2) 集羣配置刷新

每每咱們系統都是集羣部署,一臺一臺調用/refresh接口維護成本過高,這裏咱們介紹如何使用Spring Cloud Bus實現集羣配置的自動刷新,它使用輕量級的消息代理(如RabbitMQ、Kafka)鏈接分佈式系統的節點,廣播配置的變化或者其餘的管理指令。

(2.1) 安裝部署rabbitmq服務(這裏省略)

(2.2) 改造config-server和config-client

修改pom文件,添加maven依賴

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

修改配置文件,添加rabbitmq配置

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

(2.3) 啓動服務

這裏咱們再啓動一個8081端口的config-client服務

訪問http://localhost:15672/#/ ,新增了一個Exchange,以及三個queue

由於咱們啓動了1個config-server、2個config-client 每一個服務監聽各自的隊列消息

(2.4) 刷新配置

先訪問http://localhost:8080/hihttp://localhost:8081/hi 返回

alpha123

修改git倉庫裏config-client-test.yml文件

env: alpha123123

調用config-server的/bus/refresh接口

curl -u admin:123 -X POST http://localhost:9992/bus/refresh

從新訪問返回以下,表示集羣配置刷新成功

alpha123123

(2.5) 局部刷新配置

調用/bus/refresh,會刷新全部使用spring cloud bus連到rabbitmq的服務,若是須要局部刷新,可經過/bus/refresh的destination參數來定位要刷新的應用程序。

例如 /bus/refresh?destination=config-client:test:8080

這裏只刷新8080端口的config-client服務,另外一個8081端口的config-client配置沒有刷新

咱們打斷點發現,調用/bus/refresh的服務會向rabbitmq服務發送消息,message的body裏有"destinationService":"config-client:test:8080"

由於routing key 是 # ,這樣使用spring cloud bus鏈接rabbitmq的服務都會收到這個消息,

咱們發現有個ServiceMatcher類

public class ServiceMatcher implements ApplicationContextAware {
	private ApplicationContext context;
	private PathMatcher matcher;

	@Override
	public void setApplicationContext(ApplicationContext context) throws BeansException {
		this.context = context;
	}

	public void setMatcher(PathMatcher matcher) {
		this.matcher = matcher;
	}

	public boolean isFromSelf(RemoteApplicationEvent event) {
		String originService = event.getOriginService();
		String serviceId = getServiceId();
		return this.matcher.match(originService, serviceId);
	}

	public boolean isForSelf(RemoteApplicationEvent event) {
		String destinationService = event.getDestinationService();
		return (destinationService == null || destinationService.trim().isEmpty() || this.matcher
				.match(destinationService, getServiceId()));
	}

	public String getServiceId() {
		return this.context.getId();
	}

}

重點看isForSelf方法,發現會拿destinationService跟serviceId比較

serviceId實際就是context的id,是由ContextIdApplicationContextInitializer生成的,格式是 name:profiles:index

根據代碼可知 name是spring.application.name、index是server.port,若是相匹配就會刷新此服務的配置。

public class ContextIdApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

	private static final String NAME_PATTERN = "${spring.application.name:${vcap.application.name:${spring.config.name:application}}}";
	
	private static final String INDEX_PATTERN = "${vcap.application.instance_index:${spring.application.index:${server.port:${PORT:null}}}}";

	private final String name;

	private int order = Ordered.LOWEST_PRECEDENCE - 10;

	public ContextIdApplicationContextInitializer() {
		this(NAME_PATTERN);
	}

	public ContextIdApplicationContextInitializer(String name) {
		this.name = name;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public int getOrder() {
		return this.order;
	}

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		applicationContext.setId(getApplicationId(applicationContext.getEnvironment()));
	}

	private String getApplicationId(ConfigurableEnvironment environment) {
		String name = environment.resolvePlaceholders(this.name);
		String index = environment.resolvePlaceholders(INDEX_PATTERN);
		String profiles = StringUtils
				.arrayToCommaDelimitedString(environment.getActiveProfiles());
		if (StringUtils.hasText(profiles)) {
			name = name + ":" + profiles;
		}
		if (!"null".equals(index)) {
			name = name + ":" + index;
		}
		return name;
	}
}

4、參考連接

http://tech.lede.com/2017/06/12/rd/server/springCloudConfig/

http://www.cnblogs.com/ityouknow/p/6931958.html