spring cloud项目05:中心化配置-P03-高可用
Java 8
spring boot 2.5.2
spring cloud 2020.0.3
---
本文介绍 高可用配置中心 的搭建。
目录
思路
将 配置中心服务、依赖配置中心服务获取配置的应用服务 注册到 服务注册中心,之后,应用服务 通过配置中心 获取各个配置中心服务的信息,再从 任一配置中心获取配置。
前文
本文涉及服务
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/]
210914 0856 上面问题的解决方案:
https://q.cnblogs.com/q/136449/
使用 spring-cloud-starter-bootstrap、bootstrap.properties 即可。但是,官文说使用 spring-cloud-starter-bootstrap 是过时方案。搞不定。
1、Spring Cloud官方手册
配置相关
2、书《Spring Cloud微服务实战》 by 翟永超
第8章 分布式配置中心:Spring Cloud Config
3、