不为有趣之事,何遣有涯之生
不失其所者久,死而不亡者寿

SpringCloud微服务系列(7) 分布式配置中心

分布式配置中心Spring Cloud Config

起源

为分布式系统中的基础设施或者微服务提供集中化(统一)的外部配置支持,并实现在线更新。

组成

从之前的示例图上,我们可以看到它也是分为服务端和客户端两部分的,其中服务端就是分布式配置中心,它也是一个独立的微服务应用。用来为客户端提供配置,当然它的配置信息可以是本地的也可以是一个远程的配置仓库(通常我们都会是从一个独立的GIT仓库中获取,你肯定知道GIT是可以做版本管理的)。

需要实现统一管理或在线更新配置的其他微服务,扮演的就是客户端的角色。它们从服务端(配置中心)中获取业务相关的配置内容,会在启动或收到总线通知(下一章节会讲到)的时候去加载配置。

配置与映射

服务端映射关系说明

访问配置信息的URL与配置文件的映射关系有如下几种:

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

application:配置文件中除去最后环境信息的部分
profile:环境
label:分支信息,默认是master分支

示例说明:config-client-dev.properties配置文件中,config-client是application,dev是profile

实际案例

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          #仓库配置
          uri: https://github.com/hzqiuxm/SpringcloudConfig
          #相对搜索位置,可以多个
          search-paths: respo
      #分支
      label: master

server:
  port: 8888

github工程下信息:

使用postman工具进行访问:

输入:localhost:8888/config-client/dev
返回信息:

{
    "name": "config-client",
    "profiles": [
        "dev"
    ],
    "label": null,
    "version": "c6d735d09f5733200a02f82b7f89e536d8842fa5",
    "state": null,
    "propertySources": [
        {
            "name": "https://github.com/hzqiuxm/SpringcloudConfig/respo/config-client-dev.properties",
            "source": {
                "democonfigclient.message": "hello spring io",
                "foo": "foo version 1"
            }
        },
        {
            "name": "https://github.com/hzqiuxm/SpringcloudConfig/respo/config-client-dev.yml",
            "source": {
                "name": "hzqiuxm"
            }
        }
    ]
}

输入:localhost:8888/config-client/dev, 注意最后的dev代表是分支

返回:

{
    "name": "config-client",
    "profiles": [
        "dev"
    ],
    "label": "dev",
    "version": "7df1a8d5c8f0b2e2c64f27284a6e26e4e3615296",
    "state": null,
    "propertySources": [
        {
            "name": "https://github.com/hzqiuxm/SpringcloudConfig/respo/config-client-dev.properties",
            "source": {
                "democonfigclient.message": "hello spring io",
                "foo": "foo version 3"
            }
        },
        {
            "name": "https://github.com/hzqiuxm/SpringcloudConfig/respo/config-client-dev.yml",
            "source": {
                "name": "hzqiuxm"
            }
        }
    ]
}

客户端配置映射

  • 注意客户端的配置文件名:bootstrap.properties(.yml)
  • spring.application.name={application部分}
  • spring.application.config.profile={profile部分}
  • spring.application.config.label={label部分}
  • spring.application.config.uri={配置中心的地址,带端口}

原理

完整基础架构

之前的示例其实还有一个不完整的地方,就是配置中心在获取了GIT仓库中的配置后,会在本地存储一份,这样不用每次都去远程仓库去取了,即便远程仓库暂时无法访问,也可以保证能返回配置信息给客户端。

完整的基础架构示例图如下:

客户端从配置管理中获取配置流程:
1 客户端根据bootstrap.properties文件配置信息,向配置中心请求配置
2 配置中心查找客户端需要配置信息
3 通过git clone 命令将远程git仓库获取的配置下载到本地的git仓库中
4 配置中心创建spring的ApplicationContext实例,从本地获取配置信息给客户端
5 客户端得到配置文件,加载到ApplicationContext实例中(该配置优先级高于jar包内部的配置)

Git仓库配置

  • 占位符配置: 在配置文件中使用{applicaion}方式配置,服务端会根据客户端的spring.application.name来填充(类似的还有其他几种占位符)

    最佳实践:代码库和配置库名称统一,比如代码库为user-service的微服务的配置库就是user-service-config,这样配置的时候就可以使用占位符{application}来实现通用的配置了

  • 多仓库配置:除了使用application和profile模式陪陪配置仓库外,还可以使用通配符的模式,在配置文件中配置多个仓库来应对复杂的配置需求(建议不要太复杂,维护困难)

  • 指定本地仓库位置:spring.cloud.config.server.git.basedir来配置一个我们准备好的目录
  • 属性覆盖:实现服务端和客户端共同配置,客户端也可以覆盖。通过spring.cloud.config.server.overrides属性来设置键值对参数
  • 安全保护:结合SpringSerurity实现用户名密码访问

加密解密

为了保证数据库账户密码的安全性,不能把明文暴露给开发人员,所以需要对密码进行加密

配置成:{cipher}xxxxxxxxxxxxxxxxxx 的值时,因为{cipher}前缀的作用,配置中心会自动为该值进行解密

准备阶段
  • 去除密钥长度限制:因为需要用到AES加密,而JDK自带的算法密钥长度是有限制,我们要先下一个授权文件,去除长度限制
    > JCE下载:https://www.oracle.com/search/results?Nty=1&Ntk=S3&Ntt=JCE 根据自己的JDK版本选择对应版本解压的授权文件:local_policy.jarUS_export_policy.jar复制到$JAVA_HOME/jre/lib/security目录下,覆盖掉原来内容即可
  • 通过新端点验证:GET访问/encrypt/status 端点,提示你 “NO_KEY”就说明成功了
  • 配置密钥:在配置文件增加 encrypt.key=xxxx xxx是你设置的密钥(更好的方式是使用环境变量ENCRYPT_KEY进行外部存储配置)
  • 使用:使用/encrypt/decrypt端点对请求的body内容进行加解密,它们是post请求

默认采用的是AES对称加密算法,当然也可以采用非对称加密来实现,这就需要我们自己生成密钥对,配置中加入密钥对的位置信息

高可用配置

传统方式

不需要为服务端做任何的额外的配置,所有的服务端都指向一个GIT仓库,通过nginx等其它软件或硬件来实现负载均衡

微服务模式

将服务端作为微服务注册到注册中心,这样客户端调用的时候就可以去注册中心查询,再通过声明式调用或者Riboon来实现客户端的负载均衡

增加配置,注册到注册中心:

eureka:
  client:
    service-url:
      #注册中心地址
      defaultZone: http://localhost:8761/eureka/

客户端相关说明

URL指定配置中心

  • 只有我们配置了spring.cloud.config.uri的时候,客户端才会尝试去连接服务端来获取远程配置信息
  • 配置必须是在bootstrap.properties文件中

失败快速响应与重试

如果你想客户端优先判断能否从服务端获取配置,并快速响应失败的内容,增加下面参数配置:

spring.cloud.config.failFast=ture;

如果只是因为网络波动导致的失败,那代价未免大了些,所以我们希望最好能够自动重试,避免是网络波动原因造成的偶发失败。

开启自动重试需要依赖spring-retry和spring-boot-starter-aop,当然上面失败快速响应的配置也是要的。无需其他额外的配置,这样就实现了失败的自动重试(默认会重试6次,你可以对最大重试次数和重试间隔进行配置)

动态刷新配置

有时候我们需要对配置内容做一些实时更新,这个时候需要引入spring-boot-starter-actuator监控模块,我们主要是用该模块的/refresh端点进行实现的,该端点将用于实现客户端应用配置信息的重新获取与刷新。

熟悉Git仓库的人可能就想到了,我们能够和Web Hook进行关联,这样Git配置变化时,就主动给配置主机发送/refresh请求,实现配置的自动更新(不过维护刷新清单是件烦人的事情,最好的解决方案是通过消息总线)。

简单示例

配置中心(服务端)端口:8888
客户端端口:8881
注册中心端口:8761

远程GIT仓库配置:
master分支下的dev配置文件内容

客户端访问主要代码,简单的获取一个参数值:

@EnableDiscoveryClient
@SpringBootApplication
@RestController
@RefreshScope
public class ConfigClientApplication {

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

   @Value("${foo}")
   String foo;


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

访问:localhost:8881/hi
返回响应: foo version 1

修改远程仓库内容的值:

发送POST请求: localhost:8881/refresh
返回响应:

[
    "config.client.version",
    "foo"
]

如果响应不成功,提示没有权限的话,在配置中增加:
management.security.enabled = flase,其默认是true

重新访问:localhost:8881/hi
返回响应: foo version 1 add something

未经允许不得转载:菡萏如佳人 » SpringCloud微服务系列(7)

欢迎加入极客江湖

进入江湖关于作者