spring cloud项目05:中心化配置-P03-高可用

Java 8

spring boot 2.5.2

spring cloud 2020.0.3

---

本文介绍 高可用配置中心 的搭建。

目录

基本架构

失败快速响应和重试

配置安全

加密解密

参考文档

思路

将 配置中心服务、依赖配置中心服务获取配置的应用服务 注册到 服务注册中心,之后,应用服务 通过配置中心 获取各个配置中心服务的信息,再从 任一配置中心获取配置。

前文

spring cloud项目01~04

基本架构

本文涉及服务

1、注册中心 service-registration-and-discovery-service

三个,端口分别为 8771、8772、8773

2、配置中心 configserver

二个,端口分别为 10000、10001

3、应用服务 web3-client

二个,端口分别为 8083、8093

注册中心:无需改造

配置中心改造:

# 文件 pom.xml

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

# 文件application.properties
# eureka client
eureka.instance.lease-renewal-interval-in-seconds=10
eureka.instance.lease-expiration-duration-in-seconds=20
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:8771/eureka/,http://localhost:8772/eureka/,http://localhost:8773/eureka/

启动配置中心服务,两台,分别使用端口 10000、10001。来自博客园

启动后,检查注册中心(任一个)的面板,CONFIGSERVER 有2个。

应用服务改造:

示例代码:获取配置中的 message属性值,没有message时,应用服务无法启动

@RestController
@RefreshScope
@RequestMapping(value="hello")
class HelloController {
	
	@Value("${message}")
	private String message;
    ...
}
# 文件 pom.xml

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

# 文件 application.properties
# 1、直接地址
#spring.config.import=optional:configserver:http://localhost:10000/
# 2、注册中心获取
# 启动失败,,还需要配置 spring.cloud.config.discovery.*
spring.config.import=optional:configserver:http://CONFIGSERVER

# 关键配置
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.service-id=CONFIGSERVER

# eureka client
eureka.instance.lease-renewal-interval-in-seconds=10
eureka.instance.lease-expiration-duration-in-seconds=20
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:8771/eureka/,http://localhost:8772/eureka/,http://localhost:8773/eureka/

启动应用服务web3-client,启动成功,也成功获取了 配置中心 的配置。

启动后,检查注册中心(任一个)的面板,WEB3-CLIENT 有2个。

application.properties中的一个变化

spring.config.import 由 http://localhost:10000/ 改为了 http://CONFIGSERVER

此时,不再局限于 单个的 配置中心服务 了。来自博客园

注册中的面板:

试验1:

关闭任一configserver,再重启 应用服务。

结果:

web3-client 启动成功,成功通过 运行中的唯一configserver 获取到了Git仓库中的配置。

实现了高可用。

这就完成了?So easy!应该 还有一些更高级的配置吧?

失败快速响应和重试

失败快速响应,英文 fail fast。配置 spring.cloud.config.fail-fast=true 可以实现。来自博客园

试验了 配置在 bootstrap.properties 中,但未生效,最后,配置到了 application.properties中生效了——不启动CONFIGSERVER时启动客户端服务。

对比日志

未配置 或 未正确配置时,启动客户端服务 输出了很多日志:

在 application.properties 中配置正确(成功)后,启动时错误日志 仅一条:来自博客园

注,上面两种情况 都是在 CONFIGSERVER 没有启动时测试。

在生产环境下,客户端服务 和 CONFIGSERVER 是经过网络访问的,在 客户端服务 启动时,网络故障 也会导致 FAIL FAST——客户端服务启动失败。

假设这个 网络故障持续时间不长,是否可以允许重试来避免 客户端服务 启动失败呢?

在 客户端服务 添加下面两个依赖包即可:spring-retry 和spring-boot-starter-aop。默认重试机制,每隔1秒+N*01秒 重试一次,总计6次。

# 文件 pom.xml
		<!-- 210818 -->
		<dependency>
			<groupId>org.springframework.retry</groupId>
			<artifactId>spring-retry</artifactId>
		</dependency>
		<!-- 210818 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

添加后,再次启动 客户端服务:

此时,出现异常的时间 会更长,大约6秒。

不过,日志中 没有看到重试。

修改 spring.cloud.config.retry.* 的一些值,让重试更明显一些。来自博客园

# 快速失败响应
spring.cloud.config.fail-fast=true
# 重试配置
spring.cloud.config.retry.initial-interval=3000
# max-interval 必须 大于 initial-interval,因此,也要配置(默认2000)
spring.cloud.config.retry.max-interval=4000

配置后,启动失败会等待更长时间了。

上面 耗时约 18秒,期间重启 CONFIGSERVER & 成功注册到 注册中心,是否能让 客户端服务 失败几次后 成功获取配置 并 启动成功呢?

CONFIGSERVER 服务启动花了 5秒:

Started ConfigserverApplication in 5.065 seconds (JVM running for 5.965)

加上 注册、客户端拉取 注册 的时间,第一次试验 失败了——客户端服务 没有启动成功。

修改配置:

# 重试配置
spring.cloud.config.retry.initial-interval=5000
spring.cloud.config.retry.max-interval=10000

改为上面的配置后,CONFIGSERVER 在 客户端服务 启动后几秒内 启动——并在5秒启动成功,此时,客户端服务 web3-client 启动成功

其启动日志现实了下面的信息:来自博客园

o.s.b.context.config.ConfigDataLoader    : Fetching config from server at : http://192.168.128.197:10000/
o.s.b.context.config.ConfigDataLoader    : Located environment: name=web3-client, profiles=[default], label=null, 
version=71b4d86615b8530bf0926b1cc5d9bc2f47382aa7, state=null

特别说明

关闭CONFIGSERVER 后,一段时间内,注册中心 还是会有它的信息,此时启动 客户端服务 没有 快速失败响应,但却有重试的日志输出,当然,客户端服务启动失败。

不过,上面日志的时间 是不准确的!

总结:

fail fast 解决了 因为 配置中心故障等 导致的 客户端服务 启动失败 时 快速响应的问题,

加上 重试机制,解决了 因为一些 偶发因素(比如,网络波动等) 导致 客户端服务启动失败的情况:来自博客园

1)CONFIGSERVER正常,网络间歇性不正常;2)启动客户端服务时,CONFIGSERVER没启动, 但在重试期间变好了并且客户端服务可以连接到它们。

配置安全

在 CONFIGSERVER 添加 安全保护,此时,客户端服务 访问 它时,就需要账号信息了。

1、改造 CONFIGSERVER:

# 文件 pom.xml

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

# 文件 application.properties
# 基本的安全保护
spring.security.user.name=client
spring.security.user.password=password2021

启动CONFIGSERVE,输出下面的信息:来自博客园

CONFIG日志
o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@42a0501e, 
org.springframework.security.web.context.SecurityContextPersistenceFilter@2ab26378, 
org.springframework.security.web.header.HeaderWriterFilter@6056232d, 
org.springframework.security.web.csrf.CsrfFilter@19a31b9d, 
org.springframework.security.web.authentication.logout.LogoutFilter@c017175, 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4462efe1, 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@18371d89, 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@6aa3bfc, 
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@2629d5dc, 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@404eca05, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@31e2232f, 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6e4599c0, 
org.springframework.security.web.session.SessionManagementFilter@107bfcb2, 
org.springframework.security.web.access.ExceptionTranslationFilter@37d28f02, 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@28237492]

2、改造 客户端服务:

# 文件 application.properties
# 用户信息
spring.cloud.config.username=client
spring.cloud.config.password=password2021

启动客户端服务:启动成功,成功获取到 配置中心的 配置信息。来自博客园

注:

上面的安全保护只是 最基本的,实际中应该还会有更多需求要实现吧。来自博客园

spring cloud config还支持更高级别的安全保护错误(比如,基于 OAuth2),本文就不介绍了(暂无能为力)。

加密解密

在S.C.config管理的配置中,使用 {cipher}前缀 表名其后的值 是敏感的加密值。

配置中心服务 会将这样的值 解密,然后,返回给 客户端服务。

通过这样的机制,线上的加密信息 就可以 放心地给 客户端服务团队了。来自博客园

注:从这样来看,配置中心 是由 运维 或 底层研发团队管理的,而业务性的微服务 则是由 业务团队管理。

相关端点(Endpoints):

/encrypt/status 检查状态,正确配置后,返回 {"status":"OK"}

/key 无需配置,默认及配置对称秘钥时,返回 {"description":"No public key available","status":"NOT_FOUND"} 

/encrypt 加密

/decrypt 解密

使用这个功能的前提是:

配置中心的 JCE版本——需要使用 不限长度的(JRE 中默认的是 长度有限的)。来自博客园

去Oracle官网下载,然后,替换掉本地的。

注,先关掉上一部的 安全保护机制 再测试,发起请求时,总是得到 未授权错误。来自博客园

晚点研究了 S.C.security 再尝试。

改造 CONFIGSERVER :

# 秘钥:加密
# 没有配置的话,/encrypt /decrypt 等端点无法使用
encrypt.key=cfserverevresfc

加解密试验:

客户端服务 的配置 加密&获取试验:

web3-client服务,将 要获取的配置的 message内容加密,然后检查 web3-client服务得到的信息 是否为 未加密内容。

明文:欢迎来到地球!@RefreshScope 明文

密文:501ad8af1671a1c9dc28c3cf4c12cf6c0d9f23674997b33550062a497d96471a5eac3e5a2ac13a149afebe5f128b76345b4e28464b46aa649f6a6012b858f6b2

注:好长的密文!明文越长,密文当然也越长了。

启动 web3-client服务,检查得到的 message值:获取明文成功

上面是对称加密,非对称加密就不再试验了。

加密解密对于保护 重要敏感信息很重要,为了安全,应该用上它。

>>>THE END<<<

210818 15:33

先写到这里,关于中心化配置的更多内容(包括 配置更新后,自动更新、通过 S.C.Bus 更新),以后再试验&介绍吧。

还得再看看 官文,补充写基础知识才行,虽然用起来了,但也存在不少盲点。

210819 08:22

读了一遍S.C.Config的文档,原来,这种 依赖注册中心的方式 存在缺陷的:

因为依赖注册中心,那么,之前 客户端服务直接 连接 CONFIGSERVER 的 高效方式 被取代了,增加了网络延迟。

这也是 上面 第一次修改重试时间 后,试验失败的原因——因为延迟更大,因此,这这种方式下,上面的配置还需要进一步调整。

除了前面介绍的 CONFIGSERVER 作为独立服务外,官文 还介绍了 嵌入CONFIGSERVER 到客户端服务中,这样的确更高效了,但缺点是每个应用服务都要配置。

除了 Git仓库这种 属性源(Property Source),官文还介绍了 JDBC兼容的数据库、SVN、Hashicorp Vault、Credhub 和 本地文件系统 等,除了一个一个使用外,可以组合使用(官文2.1.11. Composite Environment Repositories)。

210820 09:10

在前面的基础上,整合了Spring Cloud Bus,想使用其实现自动更新配置(结合Git仓库的WebHook——一个Web调用配置)。

添加后, 通过 /actuator 可以看到 暴露了 /actuator/busrefresh(还有一个 /actuator/refresh 是 actuator 本来就有的)。

执行 /actuator/busrefresh,更新配置失败,错误信息如下:

o.s.cloud.bus.event.RefreshListener      : Received remote refresh request.
o.s.b.context.config.ConfigDataLoader    : Fetching config from server at : http://CONFIGSERVER
o.s.b.context.config.ConfigDataLoader    : Connect Timeout Exception on Url - http://CONFIGSERVER. Will be trying the next url if available
# 因为配置了 spring-retry,上面的 2、3 还会重复几次

直接去访问 http://CONFIGSERVER,但是呢,CONFIGSERVER又无法识别。

定位到了 ConfigServerConfigDataLoader 类里面,Connect Timeout Exception发生在 getRemoteEnvironment 函数中,执行下面的语句超时了——restTemplate:

response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args);

这里的uri就是 http://CONFIGSERVER ,奇怪的是,启动时,这里的 uri是 CONFIGSERVER 的IP地址+端口:

o.s.b.context.config.ConfigDataLoader    : Fetching config from server at : http://192.168.184.197:10000/

启动时,配置获取成功。

可执行 busrefresh 或 refresh 端点时,失败了

还没弄清楚怎么回事。

将客户端的 spring.config.import 改为 optional:configserver:http://localhost:10000/ (直接地址),此时执行 两个refresh时,配置更新成功了。

还需调查原因,或降低版本再尝试。

还有一个更严重的问题:上面的 refresh 执行失败后,还会导致 DiscoveryClient 被 shutdown

添加下面的配置 可以避免此问题:

eureka.client.refresh.enable=false

打开 debug日志(debug=true),可看到 DiscoveryClient 一直在 更新本地的 uris

[freshExecutor-0] o.s.web.client.RestTemplate              : HTTP GET http://localhost:8772/eureka/apps/delta
...
[freshExecutor-0] o.s.b.c.c.ConfigDataLocationResolver     : Locating configserver (CONFIGSERVER) via discovery
[freshExecutor-0] o.s.b.c.c.ConfigDataLocationResolver     : Located configserver (CONFIGSERVER) via discovery. No of instances found: 1
[freshExecutor-0] o.s.b.c.c.ConfigDataLocationResolver     : Updating config uris to [http://192.168.184.197:10000/]

参考文档

1、Spring Cloud官方手册

配置相关

2、书《Spring Cloud微服务实战》 by 翟永超

第8章 分布式配置中心:Spring Cloud Config

3、

原文地址:https://www.cnblogs.com/luo630/p/15153950.html