springcloud微服务实战_07_分布式配置

只愿长相守 提交于 2020-02-29 15:09:42

7.1 spring cloud config 简介

spring cloud config 是用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持, 它分为服务端和客户端两部分.

服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置仓库并为客户端提供获取配置信息,加密/解密信息等访问接口.

客户端则是微服务架构中的各个微服务或基础设施,它们通过指定的配置中心来管理应用资源与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息.

spring cloud config 实现了对服务端和客户端中环境变量和属性配置的抽象映射,所以它除了适用于 spring 构建的应用之外,也可以在任何其他语言运行的应用程序中使用. 由于 spring cloud config 实现的配置中心默认采用 git 来存储配置信息,所以使用 spring cloud config 构建配置服务器,天然就支持对微服务应用配置信息的版本管理, 并且可以通过 git 客户端工具轻松方便的访问与管理配置内容.

快速入门

准备配置仓库

  • 准备一个git仓库,可以在码云或Github上创建都可以。比如本文准备的仓库示例:https://gitee.com/kaisesai/springcloud/config-repo

  • 假设我们读取配置中心的应用名为commonspace,那么我们可以在git仓库中该项目的默认配置文件commonspace.properties:

from=git-1.0-2
  • 为了演示加载不同环境的配置,我们可以在git仓库中再创建一个针对dev环境的配置文件commonspace-dev.properties:
from=git-dev-1.0-2

构建配置中心

通过Spring Cloud Config来构建一个分布式配置中心非常简单,只需要三步:

  • 创建一个基础的Spring Boot工程,命名为:config-server,并在build.gradle中引入下面的依赖(省略了parent和dependencyManagement部分):
dependencies {
    // 目前只需要加这个依赖, spring-cloud-config-server
    implementation 'org.springframework.cloud:spring-cloud-config-server'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    // implementation 'org.springframework.cloud:spring-cloud-bus'
    // implementation 'org.springframework.cloud:spring-cloud-starter-stream-kafka'
}
  • 创建Spring Boot的程序主类,并添加@EnableConfigServer注解,开启Spring Cloud Config的服务端功能。
@EnableDiscoveryClient
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {

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

}
  • 在application.properties中添加配置服务的基本信息以及Git仓库的相关信息,例如:
spring.application.name=config-server
server.port=7001
# 配置服务注册中心
eureka.client.service-url.defaultZone=http://localhost:8001/eureka/
# Git 管理配置
spring.cloud.config.server.git.uri=https://gitee.com/kaisesai/springcloud
#spring.cloud.config.server.git.search-paths=spring_cloud_in_action/config-repo
spring.cloud.config.server.git.search-paths=config-repo/{application}
spring.cloud.config.server.git.username=1642339060@qq.com
# 以 spring cloud config 中属性值前使用 {cipher} 前缀来标识该内容是一个加密值,
# 当微服务客户端(包含自己)加载配置时,配置中心会自动的为带 {cipher} 前缀的值进行解密
spring.cloud.config.server.git.password={cipher}3f154f95828195411579bfd432a57a1bb756fb6a9d77424c04d97cbf91a8e4e3

到这里,使用一个通过Spring Cloud Config实现,并使用Git管理配置内容的分布式配置中心就已经完成了。我们可以将该应用先启动起来,确保没有错误产生,然后再尝试下面的内容。

如果我们的Git仓库需要权限访问,那么可以通过配置下面的两个属性来实现;

  • spring.cloud.config.server.git.username:访问Git仓库的用户名
  • spring.cloud.config.server.git.password:访问Git仓库的用户密码

完成了这些准备工作之后,我们就可以通过浏览器、POSTMAN或CURL等工具直接来访问到我们的配置内容了。访问配置信息的URL与配置文件的映射关系如下:

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

上面的url会映射{application}-{profile}.properties对应的配置文件,其中{label}对应Git上不同的分支,默认为master。我们可以尝试构造不同的url来访问不同的配置内容,比如,要访问master分支,config-client应用的dev环境,就可以访问这个url:http://localhost:7001/commonspace/dev/master,并获得如下返回:

{
    "name":"commonspace",
    "profiles":[
        "dev"
    ],
    "label":"master",
    "version":"0c399b7dd2cd20f095f38b9fb4435c5ca0b81bad",
    "state":null,
    "propertySources":[
        {
            "name":"https://gitee.com/kaisesai/springcloud/config-repo/commonspace/commonspace-dev.properties",
            "source":{
                "from":"git-dev-1.0-2"
            }
        },
        {
            "name":"https://gitee.com/kaisesai/springcloud/config-repo/commonspace/commonspace.properties",
            "source":{
                "from":"git-1.0-2"
            }
        }
    ]
}

我们可以看到该Json中返回了应用名:commonspace,环境名:dev,分支名:master,以及default环境和dev环境的配置内容。

构建客户端

在完成了上述验证之后,确定配置服务中心已经正常运作,下面我们尝试如何在微服务应用中获取上述的配置信息。

  • 创建一个Spring Boot应用,命名为config-client,并在build.gradle中引入下述依赖:
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    // implementation 'org.springframework.cloud:spring-cloud-bus'
    //implementation 'org.springframework.cloud:spring-cloud-starter-stream-rabbit'
    // implementation 'org.springframework.cloud:spring-cloud-starter-stream-kafka'
}

  • 创建Spring Boot的应用主类,具体如下:
@EnableDiscoveryClient
@SpringBootApplication
public class ConfigClientApplication {

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

}
  • 创建bootstrap.properties配置,来指定获取配置文件的config-server位置,例如:
spring.application.name=config-client
server.port=7002
eureka.client.service-url.defaultZone=http://localhost:8001/eureka/
# 分布式配置中心信息
spring.cloud.config.name=commonspace
spring.cloud.config.label=master
spring.cloud.config.profile=dev
# 通过服务访问分布式配置中心
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.service-id=config-server
# 失败快速响应与重试
spring.cloud.config.fail-fast=true
# 配置中心的登录信息
#spring.cloud.config.username=user
#spring.cloud.config.password=67a602e3-299f-456a-b59c-a8cecbd69f46
# spring boot actuator 端点配置 spring boot2.0 默认的端点都是关闭的 需要打开
management.endpoints.web.exposure.include=*

# rabbitmq 配置
# spring.rabbitmq.host=localhost
# spring.rabbitmq.port=5672
# spring.rabbitmq.username=guest
# spring.rabbitmq.password=guest
  • 创建 REST 接口访问配置中心的 from 属性
@RefreshScope
@RestController
public class TestController {

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

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

}

除了通过 @Value 属性,也可以通过 Environment 对象来获取

  @Autowired
  private Environment environment;

  @RequestMapping(value = "/from")
  public String from() {
    String property = environment.getProperty("from", "undefinded");
    return property;
  }

上述配置参数与Git中存储的配置文件中各个部分的对应关系如下:

  • spring.application.name:对应配置文件规则中的{application}部分
  • spring.cloud.config.name: 对应要访问的配置文件中对应的{application}部分
  • spring.cloud.config.profile:对应配置文件规则中的{profile}部分
  • spring.cloud.config.label:对应配置文件规则中的{label}部分
  • spring.cloud.config.uri:配置中心config-server的地址

这里需要格外注意:上面这些属性必须配置在bootstrap.properties中,这样config-server中的配置信息才能被正确加载。 spring boot 对配置文件的加载顺序,对于本应用jar 包之外的配置文件加载会优先于应用 jar 包内的配置, 而通过 bootstrap.properties 对 config-server 的配置,使得该应用会从 config-server 中获取一些外部的配置信息,这些信息的优先级比本地的内容要高,从而实现了外部化配置.

启动三个项目,并且访问 http://localhost:7002/from, 返回的数据为

git-dev-1.0-2

7.2 服务端详解

基础架构

  • 远程 git 仓库: 用来存储配置文件的地方,比如 commonspace 的多环境配置文件, commonspace-{profile}.properties

  • config server: 我们构建的分布式配置中心, config-server 工程. 需要在其内部指定连接 git 需要的仓库地址,以及登录信息连接信息.

  • 本地 git 仓库: 在 config server 的文件系统中,每次客户端请求获取配置信息时, config server 会从 git 仓库中获取最新配置到本地, 然后在本地 git 仓库中读取并返回.当远程仓库无法使用时, 会使用本地仓库.

  • server A, service B: 具体的微服务应用,它们指定了 config server 的地址,从而实现从外部化获取应用自己要用的配置信息. 这些应用在启动时,会向 config server 请求获取配置信息来进行加载.

客户端应用从配置管理中获取配置信息遵循下面的流程:

  1. 启动应用时,根据 bootstrap.properties 中配置的应用名 {application},环境名{profile},分支名{lable},向 config server 请求获取配置的信息.
  2. config server 根据自己维护的 git 仓库信息和客户端传递过来的配置定位信息去查找配置信息.
  3. 通过 git clone 命令将找到的配置信息下载到 config server 的文件系统中.
  4. config server 创建 spring 的 ApplicationContext 实例,并从 git 本地仓库中加载配置信息,最后将这些配置内容读取出来返回给客户端应用.
  5. 客户端在获得外部配置文件之后加载到客户端的 ApplicationContext 实例,该配置内容的优先级高于客户端 jar 内部的配置内容,所以在 jar 内的配置内容将不会被加载.

git 配置仓库

在 application.properties 中配置 git 的仓库信息

# Git 管理配置
spring.cloud.config.server.git.uri=https://gitee.com/kaisesai/springcloud
spring.cloud.config.server.git.search-paths=config-repo/{application}
spring.cloud.config.server.git.username=1642339060@qq.com
# 以 spring cloud config 中属性值前使用 {cipher} 前缀来标识该内容是一个加密值,
# 当微服务客户端(包含自己)加载配置时,配置中心会自动的为带 {cipher} 前缀的值进行解密
spring.cloud.config.server.git.password={cipher}3f154f95828195411579bfd432a57a1bb756fb6a9d77424c04d97cbf91a8e4e3

我们也可以在 spring.cloud.config.server.git.uri 参数中通过 file:// 前缀设置一个文件地址 (windows 中使用 file:/// 来定位文件内容),那么它将会以本地仓库的方式运行,这样我们就可以脱离 git 服务端来进行开发与调试,比如:

spring.cloud.config.server.git.uri=file://${user.name}/config-repo

这样就是在当前用户的所属的目录下的 config-repo 目录下来寻找我们需要的配置信息文件.

占位符配置 URI

在 config server 中,我们通过 {application} 占位符来实现一个应用对应一个 git 仓库目录的配置效果,具体配置实现如下:

spring.cloud.config.server.git.uri=https://gitee.com/kaisesai/springcloud/{application}

{application} 代表应用名,当客户端应用向 config server 发起获取配置的请求时, config server 会根据客户端的 spring.application.name/spring.cloud.config.name 信息来填充 {application} 占位符以定位配置资源的位置,从而实现根据微服务应用的属性动态获取不同位置的配置.

我们可以对微服务应用做一下的规划:

这时我们就可以使用 spring.cloud.config.server.git.uri=http://git.oschina.net/didisapce/{application}-config 配置,来同时配置多个不同服务的配置仓库.

配置多个仓库

# 多仓库配置
spring.cloud.config.server.git.uri=https://gitee.com/kaisesai/springcloud

spring.cloud.config.server.git.repos.dev.pattern=dev/*
spring.cloud.config.server.git.repos.dev.uri=file://home/git/config-repo
spring.cloud.config.server.git.reps.test=https://gitee.com/kaisesai/springcloud/spring_cloud_in_action/config-repo/test
spring.cloud.config.server.git.repos.prod.pattern=prod/pp*,online/oo*
spring.cloud.config.server.git.repos.prod.uri=https://gitee.com/kaisesai/springcloud/spring_cloud_in_action/config-repo/prod

子目录支持

config server 还支持将配置文件定位到 git 仓库的子目录中, 也可以通过下面的配置实现一个应用一个目录的效果

# Git 管理配置
spring.cloud.config.server.git.uri=https://gitee.com/kaisesai/springcloud
spring.cloud.config.server.git.search-paths=config-repo/{application}

访问权限

使用 HTTP 或者 SSH 方式

本地仓库

在使用了 git 或者 svn 后,文件都会在 config server 的本地文件系统中存储一份,这些文件默认会存以 config-repo 为前缀的临时目录中. 我们也可以指定一个固定的位置来存储这些重要信息.

spring.cloud.config.server.git.basedir=config-repo/temp/

本地文件系统

当在 config server 中 spring.profiles.active=native时, config server 会默认从应用的 src/main/resoure 目录下搜索配置文件,可以通过 spring.cloud.config.server.native.search-locations 参数指定具体的配置文件目录.

健康监测

# 健康检查
spring.cloud.config.server.health.repositories.check.name=check-config
spring.cloud.config.server.health.repositories.check.label=master
spring.cloud.config.server.health.repositories.check.profiles=default

如果不想使用,可以设置 spring.cloud.config.server.health.enabled=false 设置.

属性覆盖

可以为开发人员为所有的应用提供配置属性.

spring.cloud.config.server.overrides.name=didi
spring.cloud.config.server.overrides.from=didi

安全保护

我们在 config server 中加入 spring security,并且进行配置.

dependencies {
 implementation 'org.springframework.boot:spring-boot-starter-security'
}

application.properties 配置安全信息

#spring.security.user.name=user
#spring.security.user.password=67a602e3-299f-456a-b59c-a8cecbd69f46

加密解密

在微服务架构中,我们通常都会采用DevOps的组织方式来降低因团队间沟通造成的巨大成本,以加速微服务应用的交付能力。这就使得原本由运维团队控制的线上信息将交由微服务所属组织的成员自行维护,其中将会包括大量的敏感信息,比如:数据库的账户与密码等。很显然,如果我们直接将敏感信息以明文的方式存储于微服务应用的配置文件中是非常危险的。针对这个问题,Spring Cloud Config提供了对属性进行加密解密的功能,以保护配置文件中的信息安全。比如下面的例子:

spring.datasource.username=didi
spring.datasource.password={cipher}dba6505baa81d78bd08799d8d4429de499bd4c2053c05f029e7cfbf143695f5b

在Spring Cloud Config中通过在属性值前使用{cipher}前缀来标注该内容是一个加密值,当微服务客户端来加载配置时,配置中心会自动的为带有{cipher}前缀的值进行解密。通过该机制的实现,运维团队就可以放心的将线上信息的加密资源给到微服务团队,而不用担心这些敏感信息遭到泄露了。下面我们来具体介绍如何在配置中心使用该项功能。

使用前提

在使用Spring Cloud Config的加密解密功能时,有一个必要的前提需要我们注意。为了启用该功能,我们需要在配置中心的运行环境中安装不限长度的JCE版本(Unlimited Strength Java Cryptography Extension)。虽然,JCE功能在JRE中自带,但是默认使用的是有长度限制的版本。我们可以从Oracle的官方网站中下载到它,它是一个压缩包,解压后可以看到下面三个文件:

README.txt
local_policy.jar
US_export_policy.jar

我们需要将local_policy.jar和US_export_policy.jar两个文件复制到$JAVA_HOME/jre/lib/security目录下,覆盖原来的默认内容。到这里,加密解密的准备工作就完成了。

相关端点

在完成了JCE的安装后,可以尝试启动配置中心。在控制台中,将会输出了一些配置中心特有的端点,主要包括:

  • /encrypt/status:查看加密功能状态的端点
  • /key:查看密钥的端点
  • /encrypt:对请求的body内容进行加密的端点
  • /decrypt:对请求的body内容进行解密的端点

可以尝试通过GET请求访问/encrypt/status端点,我们将得到如下内容:

{
  "description": "No key was installed for encryption service",
  "status": "NO_KEY"
}

配置密钥

我们可以通过在bootstrap.properties 中添加encrypt.key属性在配置文件中直接指定密钥信息(对称性密钥),比如:

# 加密解密 注意必须放在 bootstrap.properties 文件中, 或者通过外部环境变量/启动参数设置
encrypt.key=didispace

加入上述配置信息后,重启配置中心,再访问/encrypt/status端点,我们将得到如下内容:

{
  "status": "OK"
}

此时,我们配置中心的加密解密功能就已经可以使用了,不妨尝试访问一下/encrypt和/decrypt端点来进行加密和解密的功能。注意,这两个端点都是POST请求,加密和解密信息需要通过请求体来发送。比如,以curl命令为例,我们可以通过下面的方式调用加密与解密端点:

$ curl localhost:7001/encrypt -d didispace
3c70a809bfa24ab88bcb5e1df51cb9e4dd4b8fec88301eb7a18177f1769c849ae9c9f29400c920480be2c99406ae28c7

$ curl localhost:7001/decrypt -d  3c70a809bfa24ab88bcb5e1df51cb9e4dd4b8fec88301eb7a18177f1769c849ae9c9f29400c920480be2c99406ae28c7
didispace

这里,我们通过配置encrypt.key参数来指定密钥的实现方式采用了对称性加密。这种方式实现比较简单,只需要配置一个参数即可。另外,我们也可以使用环境变量ENCRYPT_KEY来进行配置,让密钥信息外部化存储。

非对称加密

7.3 客户端详解

URI 指定配置中心

客户端启动时通过配置下面的配置

spring.application.name=didispace
spring.cloud.config.profile=dev
spring.cloud.config.uri=http://locahost:7001/

服务化配置中心

推荐使用

服务端配置

添加服务治理依赖

dependencies {
    ...
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    ...
}

application.properties配置服务治理地址

eureka.client.service-url.defaultZone=http://localhost:8001/eureka/

应用主类上使用 @EnableDiscoveryClient

客户端配置

dependencies {
    ...
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    ...
}

bootstrap.properties配置服务治理地址

# 服务治理地址
eureka.client.service-url.defaultZone=http://localhost:8001/eureka/

# 通过服务访问分布式配置中心
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.service-id=config-server

应用主类上使用 @EnableDiscoveryClient

失败快速响应与重试

客户端启动时会预先加载很多其他信息,然后在连接 config server 进行属性注入, 所以当 config server 服务不可用时,那么客户端其实也没有必要进行启动需要快速失败与重试, 此时我们进行一下的配置.

# 失败快速响应与重试
spring.cloud.config.fail-fast=true

添加重试与切面的依赖

dependencies {
    ...
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.retry:spring-retry'
    ...
}

7.4 使用 config 实现 zuul 的动态路由加载

我们将 zuul 与 config 的动态刷新机制联系到一起,只需要将 API 网关服务的配置文件通过 spring cloud config 连接到 git 仓库存储与管理,就可以轻松的实现动态刷新路由规则的功能了.

  • 创建一个 api-gateway-dynamic-route 的 springboot 工程.
  • 添加 zuul,eureka,config 依赖.
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
  • 创建 bootstrap.properties 文件配置 config-server 和 eureka 地址信息
spring.application.name=api-gateway
server.port=5556

# 分布式配置中心信息
spring.cloud.config.name=api-gateway
spring.cloud.config.label=master
spring.cloud.config.profile=dev
# 注册服务中心
eureka.client.service-url.defaultZone=http://localhost:8001/eureka/
# 通过服务访问分布式配置中心
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.service-id=config-server

...其他省略

  • 创建启动应用类
package com.look.apigatewaydynamicroute;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;

@EnableZuulProxy
@SpringCloudApplication
public class ApiGatewayDynamicRouteApplication {

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

  /**
   * 系统启动后出现两个 ZuulProperties 类, 需要使用 @Primary 注解, 指定该类的优先级
   *
   * @return
   */
  @Primary
  @Bean
  @RefreshScope
  @ConfigurationProperties("zuul")
  public ZuulProperties zuulProperties() {
    return new ZuulProperties();
  }

}
  • 最后在 git 仓库增加网关的配置文件取名为 api-gateway.properties. 在配置文件中内容如下:
zuul.routes.hello-service.path=/hello-service/**
zuul.routes.hello-service.service-id=hello-service

zuul.routes.feign-consumer.path=/feign-consumer/**
zuul.routes.feign-consumer.service-id=feign-consumer
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!