Spring Cloud笔记(六)

一. 前言

​ 上一部分添加了网关Zuul,这时候微服务架构已经基本成型,后续要增加其他功能就添加其他服务组件。但是随着微服务的数量越来越多,配置文件就越需要管理。尽管目前项目只有5-6个配置文件,都已经感觉到有点乱了。所以这部分讲述的是Spring Cloud Config,以及最后docker 简要的部署。

二. 使用Spring Cloud Config

使用Spring Cloud Config的好处:集中管理配置,可以实现不同环境不同配置,可以在运行时动态调整配置。

组成:一个Config Server,一个Config Client。很好理解,Server集中管理所有配置,然后Client调用

使用步骤:

构建Server:

①在git仓库创建一些配置文件(作为server的仓库)

②新建module,添加依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>

③编写启动类,使用注解@EnableConfigServer,声明为Config Server

1
2
3
4
5
6
7
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}

④编写配置文件yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server:
port: 8080
spring:
application:
name: microservice-config-server
cloud:
config:
server:
git:
uri: https://gitee.com/itmuch/spring-cloud-config-repo
search-paths: config-repo
username:
password:
eureka:
client:
register-with-eureka: false
fetch-registry: false

之后可以通过Config Server的端点来获取配置文件的内容,端点与配置文件的规则如下:

The HTTP service has resources in the following form:

/ {application} / {profile} [/{label}]

/ {application} - {profile}.yml

/ {label} / {application} - {profile}.yml

/ {application} - {profile}.properties

/ {label} / {application} - {profile}.properties

不知所以,仔细记录一下:

application:项目名,仓库名

profile:属性名 (默认是default)

label:分支名(默认是master)

关于端点如何分割:localhost:8080/xxx/yyy/zzz

yyy/zzz,表示yyy微服务里的zzz属性。

那么有一个疑惑,一个配置文件名为:aaa-bbb-ccc.properties,如何区分微服务名为aaa,

还是aaa-bbb?测试了一下,并不需要区分。如果找不到,也不会报错,此时会返回:

sb_46

也就是说,会默认生成一个名为default的profile,属性值为1。

接着我们分别测试其余的情况,比如,我们的配置文件之一叫:microservice-foo-test.properties,那:

①localhost:8080/microservice-foo-test.properties

sb_47

http://localhost:8080/microservice-foo/test:

sb_48

可以看到,就是name为“microservice-foo”,profile名为test,属性值为default-1.0。除此之外,还有自动生成的属性值名为default的profile。

http://localhost:8080/microservice/foo-test

sb_49

name为microservice,profile名为foo-test。但因为这个是不存在的,所以只有default属性。

④给②,③增加other branch。发现此时label要放在最后,而不是最前面。如:

http://localhost:8080/microservice-foo/dev/config-label-v2.0

sb_50

这也印证了官方文档前面那晦涩难懂的rules:

对于第2,第5点,表示配置文件的具体后缀,yml,properties,yaml都是等价的(yaml等同yml)

对于第4点,表明label是可以缺省的,此时默认为master分支。

对于第3点,和第2点一比较就可以发现,当我们指定了具体的文件(xxx.yml,xxx.properties),此时会把第一个/后的值视为label。

对于第1点,我们没有指定具体的文件,只是表明获取微服务名为{application}的属性名为{profile}的值,此时会把最后一个/的值视为label。

中间遇到的问题:Read Time out。使用GitHub地址的时候,可能会出现Read Time out的情况,过一段时间又可以。然后可能再放了几天又无法读取,再过几个小时又可以读取。而Gitee不会存在这个错误。目前比较合理的解释,就是的问题了。

构建Client:

步骤:

①导入依赖包

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

②普通的启动类,@SpringBootApplication即可

③编写application.yml,包括端口号即可。

④编写 bootstrap.yml配置文件:

1
2
3
4
5
6
7
8
spring:
application:
name: microservice-foo # 对应config server所获取配置文件的{application}
cloud:
config:
uri: http://localhost:8080/
profile: dev # 对应config server所获取配置文件的{profile}
label: master # git repository的branch

这个bootstrap叫引导上下文。具体为什么要写在这里,而不是application.yml,以后再细看,可参考:https://www.cnblogs.com/niechen/p/8968204.html

⑤ Controller

1
2
3
4
5
6
7
8
9
10
@RestController
public class ConfigClientController {
@Value("${profile}")
private String profile;

@GetMapping("/profile")
public String hello() {
return profile;
}
}

实现得还是很顺利的,通过localhost:8081/profile就能调用8080端口的server的dev属性。

唯一中间出现过的问题是,maven导入包没有写版本号,然后IDEA又不懂得报错,于是yml里就无法识别出这些配置。记得出错的时候先去看一下maven包有没有导入失败,IDEA很多时候都不会显示。

Spring Cloud Config自带了一个health结点,用于查询当前的健康状态。

例子:http://localhost:8080/health/microservice-config-server/config-label-v2.0

xxx / health / {application} / {label} (profile默认是default,label是master)

加密相关:可以安装JCE,使用对称加密和非对称加密

Symmetric-key algorithm asymmetric cryptography

关于这两者到底是什么,二者的区别,有一篇比较通俗的文章:https://segmentfault.com/a/1190000004461428

对称加密的步骤:

安装好JCE,创建bootstrap.yml(只有一个encrypt-key的属性,推测根据这个来进行hash)

加密,解密的命令:

curl http://localhost:8080/encrypt -d mysecret

会返回一串神秘代码:例如:sd23u89yw8fywe9823u92u9

解密:

curl http://localhost:8080/decrypt -d sd23u89yw8fywe9823u92u9

然后就会返回mysecret。(为什么不截图,因为写的时候已经改写为非对称了,懒得搞)

存储加密内容到git仓库:

创建一个配置文件encryption.yml

1
2
3
4
spring:
datasource:
username: dbuser
password: '{cipher}a69cfa909cbbb012b9a09c41ddf428463' # 只是举例

单引号,{cipher}都是必不可少的,如果是使用properties文件则不能加单引号。

之后使用 localhost:8080/encryption-default.yml 就可以获取明文解密后的结果。

如果不想返回明文,那么就配置: spring.cloud.config.server.encrypt.enabled = false

非对称加密:

使用keytool生成一个Key Store: (右边的图省略了)

keytool -genkeypair -alias mytestkey -keyalg RSA -dname "CN=Web Server, OU=Unit, O=Organization, L=City,S=State,C=US" -keypass change me -keystore server.jks -storepass letmein

sb_51

创建bootstrap.yml:

1
2
3
4
5
encrypt:
key-store:
location: classpath:server.jks # jks文件的路径
password: letmein # storepass
alias: mytestkey # alias

然后理论上调用curl url -d xx就可以获取加密后的结果,但一直报错,是500错误,“message”:”Cannot load keys from store: class path resource [server.jks]”。仔细看了一下,应该是因为上面图中写的忽略-keypass的原因,于是就把secret注释掉,确实就可以了:

sb_52

可以看到,每一次返回的结果都不一样,非对称的安全性更强。


使用refresh端点。

在server对配置文件进行改变之后,client并不会更新(更过分的是,即使重启client,也不会更新)。所以需要刷新操作。一种方法是手动刷新,通过导入spring-boot-starter-actuator依赖,使用refresh端点进行刷新。

当配置文件发生改变之后,使用curl命令发送POST请求到client,进行刷新(是client)

例如: curl -X POST http://localhost:8081/actuator/refresh

遇到的问题:一直显示404,也就是没有这个端点。

解决:其实404就应该想到,应该是路径的问题,因为没有这个端点路径。然后最先想到了2.x开始的actuator路径改变:从/xxx变成 /actuator/xxx,于是把书上的/refresh改成了/actuator/refresh,发现仍然不行。接着继续想到了2.x开始并不是所有端点都会exposure出来,于是还要在yml里配置:

1
2
3
4
5
6
management:
endpoints:
web:
exposure:
include: '*'
# 2.x只暴露很有限的端点,需要手动配置暴露,才能使用refresh端点。

由于手动刷新比较麻烦,尤其当微服务数量增加的时候,所以需要自动刷新,也就是需要

spring-cloud-bus,也就是加入一个RabbitMQ作为消息队列,当其中一个client发生refresh操作时,其他client也会进行refresh。但这增加了耦合度,因为client本身不应该考虑配置刷新的职责。于是应该将bus编织到server,使得server发现变化时,相应的client就像是接收广播一样,进行改变。

PS:但是这部分没有实现,因为RabbitMQ又各种错误,回头再看吧。

Spring Cloud Config与Eureka配合使用。

由于前面都是直接在Client里硬编码server的URI,这失去了微服务的灵活性,因此应该将二者都注册到Eureka,然后进行服务发现。本来应该不难,二者先增加eureka-client-serviceUrl-defaultZone属性,然后唯一特别一点的就是client的cloud-config-uri去掉,改成 cloud-config-discovery

1
2
3
4
5
6
7
8
spring:
application:
name: microservice-foo
cloud:
config:
uri: http://localhost:8080/
profile: dev
label: master

可是却一直无法发现到Server实例,只好先注释回原来的版本了。

(对于application.yml和bootstrap.yml的理解还没有,然后这些配置属性应该都写好在代码 / 文档里的,还是需要慢慢看一遍源码&文档,不然这个能解决,还会有很多不懂。)

碎碎念,毕竟也快结束了:

感觉学微服务的时候一直这样,因为版本的变更,各种配置,url,包名都发现了改变,于是也就有了很多的坑。有时候也在想是否就应该直接跳过了?确实很费时间,可是,在当中也学到了很多。比如,现在一看到无法注册到Eureka,就能立刻想到是认证的问题,然后就知道去启动类那里添加那个认证方法。还有,对于yml的各种配置,也是越来越熟悉,不再是只敢抄书,稍微做一点改动这种。更重要的是,对微服务架构的认知真的是深刻了很多。虽说微服务确实就是一个项目分成多个,但到底怎么分,为什么要这样分,各个之间的作用,如何连接?这些是只看知乎那个回答所不能解答的。总而言之,并不是说一定要把书中的每一个遗留的问题都解决,毕竟不可能,最后都一定要看文档跟源码才能弄懂的。但还是要尽量努力吧,实在搞了很久也不行就算了,时间宝贵。over。

三. 其他

依赖下载不完整的解决方法:使用mvn clean package -Dmaven.test.skip=true,可以确保依赖的完整性。 (那个参数是为了跳过单元测试,不然有一些module会报错。。但确实可以运行)

最后:把项目用Docker布置。

书中最后的一部分,确实也是很实用且重要的部分,项目是以前面Spring Cloud为例的。首先,需要为每个module分别制作成image。可选的方法有多种,包括:

①分别通过mvn clean package命令,把module打包成jar,然后传输到服务器,通过Dockerfile和这个jar文件来制作image。(主要用到了ADD命令,ADD在添加jar文件的时候,会自动解压,所以项目就被解压到了image中)优点是比较清晰简单,缺点是当微服务数量较多的时候,比较繁琐。

②使用maven的plugin进行image-build操作。但遇到了问题,也没有时间仔细研究。

所以最终就直接使用方法一了。方法一也出现了问题,就是Provider服务跟Consumer服务,因为之前我在Consumer的pom.xml中设置了依赖Provider,希望能简化配置。但这就导致了打包成jar的时候还需要一些额外的操作,否则它提示无法找到依赖包。这个问题也暂时没有解决,直接取消了依赖,然后在Consumer服务里单独添加了部分依赖,于是打包就成功了。然后在Linux服务器里,编写了Dockerfile,以此来制作image,Dockerfile如下(三个服务,Eureka,Provider,Consumer,我都用了同样的代码)

启动的时候,看到不仅是Consumer,而且Provider服务也无法连接到Eureka。其实当时就觉得localhost肯定要改,一看书,确实如此。首先在Dockerfile里,代码是给Eureka取了一个Alias,然后Provider这些服务,直接把localhost改成那个Alias即可。然后测试了一下,确实再Eureka就检测到Provider服务了。至于外网连通Eureka,同样也是在腾讯云开通8761端口即可。可是我再在腾讯云开通8001端口(Provider服务的端口),却无法访问。这是因为Docker的容器网络机制默认是bridge,所以每一个服务的IP地址是不相同的。

-------------本文结束感谢您的阅读-------------