微服务(四)——Config(搭建服务端和客户端、配置文件加解密、配置中心服务化+动态刷新+请求失败重试)、Bus(优化动态刷新、批量刷新服务)、Stream(简单使用、消息分组、消费分区)
分布式配置中心
一、Spring Cloud Config
将来项目上线了,链接 mysql,redis,各种各样的东西,如果有变动,难道要一个个的去改动吗?如果是集群化部署就更多了。当然如果把配置的这些东西直接给开发人员,对公司来说是不安全的,这样删库跑路也就不是段子是很有可能发生的事件了。当然这些东西其实都是运维来管理,不过有的小公司可没有运维,那就是后端人员负责了。
如果能把配置文件统一集中的放到一个地方来管理,那就非常友好轻松了。这就是分布式配置中心。
1、Spring Cloud Config 引言介绍和简介
这一块国内外都有很多解决方案。
a、简介
Spring Cloud Config 是一个分布式系统配置管理的解决方案,它包含了 Client 和 Server 。配置文件放在 Server 端,通过 接口的形式提供给 Client。
Spring Cloud Config 主要功能:
- 集中管理各个环境、各个微服务的配置文件。
- 提供服务端和客户端支持。
- 配置文件修改后,可以快速生效。
- 配置文件通过 Git/SVn 进行管理,天然支持版本回退功能。
- 支持高并发查询、也支持多种开发语言。
2、准备工作
a、准备工作介绍
Spring Cloud Config 需要一个 Config Server 服务端。
这里其实放在本地或者 远程仓库(github)上测试都行,因为网络原因,这里就放在 gitee上测试。
服务端会自动的读取配置文件,提供接口,将来客户端直接访问 config server ,然后从里面读取到数据。
现在创建一个工程,这个工程去访问 gitee,将来其他项目访问这个工程,然后就能拿到想要的配置文件。
后面要选的依赖中,Config Server 不一定就要是 SpringCloud ,如果有一天用上 nacos,读取配置文件读取方式也是一样的;因为 alibaba 也遵守了相应的协议。
b、依赖导入
3、搭建服务端 config server
首先,这里先展示下配置文件里面的内容:
a、配置
这个 config server 不需要我们自己写接口,会自动的暴露接口。 但是我们要告诉这个 config server 远程仓库的地址。
然后注意,先看清楚是公开仓库还是私有仓库,如果是私有仓库还需要配置用户名和密码。
还要加个注解:
b、访问规则
实际上,访问地址有如下规则:
/{application}/{profile}/[{label}](一般用这种)
/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.yml
/{label}/{application}-{profile}.properties
applicaiton 表示配置文件名。
profile 表示配置文件 profile,例如 test、dev、prod。
label 表示 git 分支,参数可选,默认就是 master。(因为西方黑命贵事件,已经改为 main)
接下来,可以修改配置文件,并且重新提交到 GitHub,此时,刷新 ConfigServer 接口,就可以及时看到最新的配置内容。
此时根据上述规则去访问,就可以看到信息,比如:http://localhost:8080/client02/dev/main 。那么博主这里展示的信息是:
如果此时把 dev 改成 prod ,那么返回的就不是 prod,而是 dev。
这里展示的就是返回策略。这里使用的是上面规则的第一种;一般用的也是第一种。 所以后面的这里就不展示了。
c、自动克隆配置文件到本地
此时看控制台日志:
这里的意思是自动的从服务端仓库把配置文件克隆到本地了,就在这个的目录下。
4、搭建客户端
接着搭建 config client;通过这个去读取上面的配置文件。
a、前期准备
b、介绍 bootstrap.properties / yaml
这里特地介绍 bootstrap.properties / yaml,是因为这里要特地说明:这里是通过这个客户端项目去加载配置供其他微服务项目使用,当加载到 application.properties 这个文件的时候,系统已经要到正常启动的时候,这个时候再加载已经来不及了,因为里面的配置在一开始的时候是其他微服务就要使用到的。所以 SpringBoot 里面还有一个配置文件,就叫 bootstrap.properties / yaml(这里意思是用 yaml 也可以),这个配置文件的内容会先于 application 这个文件执行。
这里再小结一下:整个系统启动的时候会先去这个项目里面把配置文件取回来,接着系统启动的时候 application 就会用到这些配置文件里面的数据,所以要先去读这个文件。
配置信息:
c、接口
下图的 ${hello} ,之所以这里是 hello,是因为配置文件里面写的是 hello=123,所以这里才写的是 hello。
下图的 @RefreshScope 注解后面讲到动态刷新会有解释;这里暂时还没用到。
接着就是运行三个东西:运行 eureka、上面服务端以及这里的客户端,然后自行测试 8082 端口的 hello 接口。
d、动态获取应用名
这里如果只写应用名,那么就是写死了的,如果有其他服务接入,就不能动态对接其他服务,所以这里这么写:
这里的意思是说,如果连上的客户端名字叫什么,那么这里就显示什么:
题外话,那么 ${ } 这种又是什么意思呢?
意思引用当前变量,比如当前有 application=xxx ,那么这里才用美元符号 $ 。
5、配置文件加解密
常见加密方案:
- 不可逆加密
- 可逆加密
不可逆加密:就是理论上无法根据加密后的密文推算出明文。一般用在密码加密上,常见的算法如MD5 消息摘要算法、SHA 安全散列算法。
可逆加密:看名字就知道可以根据加密后的密文推断出明文的加密方式,可逆加密一般又分为两种:
- 对称加密
- 非对称加密
对称加密指加密的密钥和解密的密钥是一样的。常见算法des、3des、aes。
非对称加密就是加密的密钥和解密的密钥不一样,加密的叫做公钥,可以告诉任何人,解密的叫做私钥,只有自己知道。常见算法 RSA。
a、对称加密
首先下载不限长度的 JCE:http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip
将下载的文件解压,解压出来的 jar 拷贝到 Java 安装目录中:C:\Program Files\Java\jdk13.0.1\lib\security
然后,在 config-server 的 bootstrap.properties 配置文件中,添加如下内容配置密钥:
# 密钥
encrypt.key=abc
然后,启动 config-server ,访问如下地址,查看密钥配置是否OK(用 postman 测试也可):http://localhost:8081/encrypt/status
然后,访问:http://localhost:8081/encrypt
,注意这是一个 POST 请求,访问该地址,可以对一段明文进行加密。把加密后的明文存储到 Git 仓库中,存储时,要注意加一个 {cipher} 前缀。
演示:
首先是服务端的 bootstrap.properties:
接着重启,然后打开 postman。
额外说一点:现在额外提供了接口,访问这个接口就可以给配置文件加密,然后把内容上传上去。接着解密就不需要自己手动解密。客户端读到内容就会自动解密。
接着访问接口:
这种就是加密状态没问题。
然后接下来就是加密:
然后把这个密文放到配置文件中去,比如:
到这里就完毕了,可以自行测试,跟上面一样访问接口即可。
b、非对称加密
非对称加密需要我们首先生成一个密钥对。
打开 cmd,在命令行执行如下命令,生成 keystore:
指定目录保存就这样写(这里保存到 D盘的 xxx 下):
keytool -genkeypair -alias config-server -keyalg RSA -keystore D:\springcloud\config-server.keystore
不指定,即保存到当前文件夹下就这么写:
keytool -genkeypair -alias config-server -keyalg RSA -keystore config-server.keystore
config-server:密钥对的别名。
RSA:生成密钥对的算法。非对称加密主要就是这种算法,基本没有其他的了。
-keysotre:保存的秘钥文件的位置。
这个 keytool 是 jdk 自带的工具。
然后运行 cmd,执行上面命令。
接着就是需要输入密钥对口令,这里自己自定义即可,最少好像是 6 位数。后面的提问都敲回车。最后在输入:“是” 即可:
成功会生成一个 config-server.keystore 文件,把这个文件拷贝到服务端的 resources 目录下:
然后在 config-server 服务的 bootstrap.properties 目录中,添加如下配置:
encrypt.key-store.location=config-server.keystore
encrypt.key-store.alias=config-server
encrypt.key-store.password=填前面输入的密码
encrypt.key-store.secret=填前面输入的密码
注意,在 pom.xml 的 build 节点中,添加如下配置,防止 keystore 文件被过滤掉:
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.yaml</include>
<include>**/*.yml</include>
<include>**/*.keystore</include>
</includes>
</resource>
</resources>
接着到这里就配置完毕。
测试:
先重启。接着就是加密明文,然后把密文拷贝到配置文件中去;跟先前的测试方式一样。
c、安全管理
这里就体现 SpringSecurity 优势的地方了。因为上面的还不够。目前,谁都可以访问这个 config_server,还是有风险,那么怎么解决呢?很简单,这里加入 SpringSecurity 依赖即可(如果这个地方用的是 shiro ,很麻烦,要自己写很多代码):
加了这个后,所有接口都会被保护起来;接着配置用户名和密码(在服务端的 application 下配置):
然后客户端在 bootstrap 里面配置(注意:服务端跟客户端是不同配置文件配置,且配置的语句也不同):
6、配置中心服务化+动态刷新+请求失败重试
a、服务化
什么叫服务化?
前面写的还是有问题,因为已经用了 eureka 注册中心,所有的东西都会去注册中心上去查找,包括上面的 config 的服务端客户端,如果还特地去配置服务的地址,那么以后就只能去这个地址上查找。所以可以不用这么写,去 eureka 上查找即可,这就是服务化:
这里可以把 8080 注释掉。客户端的 bootstrap 里面配置:
额外提醒:如果以后见到 8761 的报错,就说明一个问题,eureka 没配对:
然后自行测试 8082 端口的 hello 接口即可。
b、动态刷新
当配置文件发生变化之后,config-server 可以及时感知到变化,但是 config-client 不会及时感知到变化,默认情况下,config-client 只有重启才能加载到最新的配置文件。那么现在希望刷新一下就能获取到最新的配置文件。
首先给 config-client(客户端) 添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
之前说的服务监控用的就是这个依赖,SpringBoot Admin,还有一个网页版的服务的数据展示出来,这些数据就是从这个 actuator 中来的。只要服务监控,都会用到这个。
然后在客户端的 bootstrap,添加配置,使 refresh 端点暴露出来(如果配置 * ,则是所有端点暴露出来,这里也可以用 *):
refresh 这个端点可以刷新当前的这个容器,把容器里面的数据重新加载出来。
接着接口还要加一个注解:
测试:
这里要特别说明,此时并不是说改了配置文件刷新网页就刷新的了。而是要访问一个接口,通过 post 请求去访问:
访问成功以后,看控制台的日志信息可以看到配置文件又重新克隆了下来。这时候再刷新网页就能能看到信息的变化。
如果这里嫌麻烦,就要到后面结合 Spring Cloud Bus 来做。这个看后面的章节即可。
c、请求失败重试
config-client 在调用 config-server 时,一样也可能发生请求失败的问题,这个时候,不应该立马就停止,而是让服务重试几次。我们可以配置一个请求重试的功能。
要给 config-client (客户端) 添加重试功能,只需要添加如下依赖即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
然后是客户端的 bootstrap 配置:
spring.cloud.config.fail-fast=true
spring.cloud.config.retry.max-interval=2000
spring.cloud.config.retry.initial-interval=1000
spring.cloud.config.retry.multiplier=1.1
spring.cloud.config.retry.max-attempts=6
到此就配置完毕了。
如果要测试,可以注释掉用户名密码测试即可。
二、Spring Cloud Bus
Spring Cloud Bus 通过轻量级的消息代理连接各个微服务,可以用来广播配置文件的更改,或者管理服务监控。
其实就是对上面动态刷新那一块的优化。
这玩意说白了就是把 MQ 那些 又封装了一层。
还是前面刷新的例子,如果有很多个服务,按照上面的办法,就需要一个个去刷新了。那么这里的办法可以简化这个操作,就是把服务都注册到 MQ 上(config server、client),当有配置文件更新的时候,给 config server 或者任意一个 client 发一条消息,然后这则消息就会发到 MQ 上,MQ 再把这则消息推给所有注册上来的微服务,那么这些服务就能自己去更新自己的内容了。
1、配置
首先就是 docker 安装 RabbitMQ,这里就不贴命令了,前面博客有。
接着就是给 config-server 和 config-client 分别加上 Spring Cloud Bus 依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
然后就是分别给 server 服务端 和 客户端 的 application 文件添加下面这个配置:
再接着就是给服务端的 application 文件添加下面这个配置:
最后分别启动 config-server(服务端) 和 config-client(客户端),然后修改配置信息,刷新 config-client 接口,查看是否有变化。
然后,发送如下 POST 请求:http://localhost:8081/actuator/bus-refresh
这个 post 是针对 config-server 的,config-server 会把这个刷新的指令传到 rabbitmq ,然后rabbitmq 再把指令传给各个 client。
到这里基本就配置好了,如果是自己有网页测试,那么到这就配置完了。如果是需要用 postman 测试,那么接着还要配置一个类,看下个小节。
2、设置 http 请求头登录(本小节专为 postman 测试才设立的)
这会有个小小的问题,因为加了 Spring Security,要登录才能调用;用 postman 测试就没法登录。所以这里特地加一个配置:
在服务端中添加这么一个类:
开始使用 postman 测试:
如果直接调用接口会调不通,如下:
因为还没有认证,认证方式如下(这里输入的是 SpringSecurity 中配置文件设置的用户名和密码):
接着再次访问就能成功。
测试方式跟上面一样,改动配置文件,再访问接口,再刷新就能看到改动过后的信息。
3、批量刷新服务
还有这么一个接口:
这个带个大括号 { } 的意思是可以省略,也可加上。比如现在有 10 个微服务连上来,但是只更新了 storage 这个微服务的配置,这个 storage 当初部署的时候是集群化部署,本身有 10 个 storage ,一模一样的,现在想要批量刷新,不想一个个去调用接口刷新配置,那么这时候就可以这么调用:/actuator/bus-refresh/storage ,这样可以精确的指定哪些服务去更新。
三、Spring Cloud Stream
消息驱动的微服务。
1、引言和介绍
Stream就是在消息队列的基础上,对其进行封装,让咱们更方便的去操作MQ消息队列。
现在是在微服务里面去调用消息中间件,如果不用 Stream,那么就像前面那样使用 MQ 去调用,不过这不太方便,所以官方推出了这个 Stream,这个 Stream 封装的更多一些。这个 Stream 完全没有消息中间件的概念,反而自己提出了一些新的概念。如果将来想要换消息中间件,代码是几乎不需要改的。
2、核心概念
Stream 自己定义了一些概念,这里来了解下。
在 Spring Cloud Stream 中,我们的微服务通过 inputs 或者 outputs 来与 Spring Cloud Stream 中的Binder 进行交互,而这里的 Binder 相当于微服务和消息中间件之间的一个粘合剂,这个 Binder 则可以负责与消息中间件如 RabbitMQ 或者 Kafka 进行交互,这个时候对于开发者而言,我们只需要关注微服务和 Spring Cloud Stream 之间的通信方式,即消息要怎么样发送,怎么样订阅,做好这些工作之后,剩下的事情就交给 Spring Cloud Stream 来做,它会帮助我们完成和消息中间件之间的交互。在整个过程中,有几个关键的概念,我们来了解下。
a、Binder
Spring Cloud Stream 默认为 Kafka 和 RabbitMQ 提供了 Binder 的实现,所谓的 Binder 实际上是一个抽象的概念,如上文所说,它是应用程序和消息中间件之间的一个粘合剂,使用 Binder ,我们可以在程序运行时,动态修改消息的 destination,具体到 RabbitMQ 中就是 exchange ,具体到 Kafka 中就是 topic ,这些我们都可以通过外部属性或者其他 Spring Boot 支持的的配置方式(如application.properties 或者 application.yaml)来实现,甚至不需要改变一行代码。
b、通信模型
Spring Cloud Stream 应用程序由中间件驱动,应用程序通过 Spring Cloud Stream 提供的输入和输出通道与外界通信,整个过程通过中间件特定的 Binder 来实现,通道连接到外部代理,官网提供了一张比较形象的工作模型图,如下:
3、前期准备
接着就是配置 RabbitMQ:
4、开始简单使用
Stream 自己提出了一些概念例如输入通道,这就类似于前面讲的 MQ 的消费者。
这个 Stream 一启动就会自动的链接上 rabbitMQ,并且自动的创建相应的队列、交换机等;这些都会自己创建好,开发者不用管。然后通信就是跟 binder 通信,用的就是这个通道。
a、输入通道
然后到这第一个案例就完成了,就可以去启动了。此时启动能看到日志有链接 MQ 的信息。
然后此时去查看 MQ 的可视化网站:
这个就是自动创建的队列。
再来看下交换机:
接着进到队列中去然后发消息:
然后看回控制台:
b、自定义消息通道
上面的案例是使用 RabbitMQ 的控制台进行消息的发送的,在真正的生产环境中,这种需求一般不多,我们都是通过代码进行消息的发送的。接下来我们就来看看如何自定义消息通道。
前面用的是自带的 Sink,这里可以点进去看看源码是怎么定义的:
自定义消息通道很简单,创建一个接口,里面有输入输出即可:
接着就是监听通道:
到这里消息通道的代码就写完了,接下来就可以进行测试:
写一个消息发送来测试下:
这样消息就能成功收发了吗?理论上来说是没错的,但是运行之后会发现收不到消息。消息确实发送成功了!消息接收到了吗?没有!原因就是消息收发目前不在一个通道上,所以发出去的消息,没法收到,那么怎么办呢?看下一个小节。
c、设置消息收发在同一通道上
看前面的通信模型,input 和 ouput 是两条不同的通道,现在是希望 ouput 发的消息能在 input 里面收到,就是说需要让这两个通道关联起来,那么消息收发才是正常的,如果发出去收不到,那么就会停在中间件里面。那么这两个通道怎么联系呢?配置方式很简单,在 application.properties 中添加如下配置:
5、消息分组
就是一条消息默认情况下被同一个微服务的所有实例消费(该微服务是集群化部署),有的时候我们只需要被其中一个实例消费即可。
说人话就是集群化部署后有很多个实例,现在给 MQ 发消息,所有消费者都会消费,分组之后相同组里面就只有一个消费。
消息分组配置(后面写什么都无所谓,但是要一样):
要演示这个问题可以把当前项目打包,然后运行两个项目,然后访问测试接口,这时就会发现两个项目都介绍到了消息。如果不想让大家都去消费,只想要一个去消费,那么就进行消息分组。分组后再去消费此时就会发现只有一个实例消费。
6、消费分区
还有一个概念叫做消息分区,就是说具有相同特征的消息总是被同一个实例处理,单纯的消息分组是无法实现这个功能,在前面的消息分组中,相同特征的消息也会被发送到不同的实例上去执行。
举个例子,比如现在连着发送十条一模一样的消息:
如果按照分组那种方式,那么有一部分消息会在这个实例,另外一部分消息在另外一个实例上,这完全是不可控的!现在希望这一模一样的十个消息都去同一个消费者中,不要分散到不同的消费者中去。只需要添加如下配置即可(下面的配置是在消息分组的基础上配置的):
注意:由于消息消费者和消息生产者在同一个项目中,因此这里的配置写在了一起,如果消息消费者和生产者是两个项目,那么前三行跟消费者(消息消费方)相关, 后两行跟生产者(消息发送方)相关。
还有,这里的名字是自己自定义的那个: