SpringCloud学习记录
前置基础
springboot和springcloud版本匹配
获取json字符串后使用工具进行格式化
版本选择
选择官网推荐的

使用官网推荐的版本

springcloud组件
- 服务注册与发现
- EUREKA
springcloud中文网
Eureka
- 什么是服务治理?
- eureka中application 字段与 服务的application name 相同

eureka集群
eureka服务器之间相互注册
服务在多个eureka上注册
微服务集群
- 服务名称相同,服务器或者端口不同
- 消费者使用服务名称去请求
- restTemplate 作负载均衡
actuator 服务信息显示
主机名称的修改
默认显示机器名称+项目名称+端口号
```yml
instance-id: payment80011
2
3
4
5
* 访问信息有ip地址
* ```yml
prefer-ip-address: true
获取薇服务信息
主启动类上添加 @EnableDiscoveryClient注解
获取服务信息:使用import org.springframework.cloud.client.discovery.DiscoveryClient;这个类
获取具体服务实例信息:使用DiscoveryClient.getinstances
Eureka的自我保护机制
自我保护机制的原因
- 防止微服务因为与eureka服务器暂时不连通,而导致eureka服务器将微服务删除
- 宁可保留错误的信息,也不会盲目注销服务
配置
eureka服务端
1
2enable-self-preservation: false # 关闭自我保护机制
eviction-interval-timer-in-ms: 2000 #超时2s后驱逐服务eureka客户端
1
2lease-renewal-interval-in-seconds: 2 # 发送心跳的间隔,每2秒发送一次
lease-expiration-duration-in-seconds: 9 #eureka 最后一次收到心跳,之后的等待时间
Zookeeper
linux 安装zookeeper
配置文件说明

安装zookeeper遇到的问题
服务启动成功,查看状态却是失败状态,客户端也连接不上
原因是:没有安装jdk环境
springcloud整合zookeeper遇到的问题
zookeeper版本与jar包中引用的版本不兼容
解决方法;排除依赖,手动引入适合版本的依赖
1 | <dependency> |
zookeeper 策略
- zookeeper节点是临时节点
- 薇服务挂了, 一定时间内会保留
zookeeper集群
配置过程
consul
consul 中文网
consul 安装
解压命令
1 | unzip consul_1.11.4_linux_amd64.zip |
启动命令
1 | consul agent -data-dir=/usr/local/software/consul-1.11.4/data -bind=192.168.112.128 -server -bootstrap -client -ui -client=0.0.0.0 |
-data-dir consul数据目录
-bind 绑定ip地址
-server 代表以服务的方式启动
-bootstrap 指定自己为leader而不需要选举
-ui 启动一个内置管理的web界面
-client 指定客户端可以访问的IP。设置为0.0.0.0则任意访问,否则默认本机才可以访问。
整合consul遇到的问题
at lease one health check on one instance is failing
问题描述

错误

在application.yaml 文件中开启心跳,默认是关闭的
1 | spring: |
consul 配置详解
1 | spring: |
Ribbon
自SpringCloud 2020起,已经舍弃了ribbon
使用loadbalancer 代替
2020版本之后使用ribbon,会报没有初始化服务 的错误,引入依赖就会报错
- 依赖
- restTemplate 的使用
- 负载均衡策略
使用问题
java.lang.IllegalStateException: No instances available for CLOUD-PAYMENT-SERVICE

原因是使用的是2020版及之后的springcloud,ribbon已经被摒弃,推荐使用loadbalancer
Irule 组件
规则替换
添加负载均衡规则配置类(这个类不能添加在主启动类所在包及其子包下)
在主启动类上添加注释
1
自定义规则算法
Loadbalancer
springcloud 2020 后替换ribbon
修改规则
创建规则配置类
1
2
3
4
5
6
7
8
9
10
11
12
13// 这个类不添加@configuration注解
// 这个类可以放在主启动类所在包及其自包下(ribbon不允许)
public class CustomLoadBalancerConfiguration {
public ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(loadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name
);
}
}将配置类设置给使用负载均衡的配置类里restTemplate
1
2
3
4
5
6
7
8
9
10
public class ApplicationContextConfig {
public RestTemplate restTemplate () {
return new RestTemplate();
}
}这里就将负载均衡规则从默认的轮询修改为randomRule
OpenFeign
官网: https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#feign-logging
基本整合使用
超时控制
默认只等待1秒,超时会报错
2020版openfeign超时配置
1 | feign: |
打印日志
yaml 中的配置
1 | #开启日志打印 |
日志级别信息
NONE, 不记录(默认)。BASIC, 只记录请求方法和 URL 以及响应状态码和执行时间。HEADERS, 记录基本信息以及请求和响应标头。FULL, 记录请求和响应的标头、正文和元数据。
Hystrix
服务降级
服务降级一般放在客户端,当然是可以放在服务端的
- 当发生服务不可用时,使用兜底方案
- 服务器超时
- 服务器异常错误
- 服务器宕机
- 服务熔断
服务提供者 编码配置
1 | //在主启动类上添加@EnableHystrix |
消费者客户端编码配置
1 |
|
设置默认服务降级
没有特别指定降级方法,就会使用全局降级方法
1 | //在类上添加@DefaultProperties,设置全局降级方法 |
feign 和 hystrix 结合使用
开启feign 对 hystrix的支持
1 | feign: |
设置fallback降级
1 | //在@FeignClient添加服务降级配置类 |
服务熔断
降级(open)—满足一定的熔断条件—》熔断(closed)—经过休眠窗口时间—》缓慢放行(half open)—如果仍然不行—》重置休眠窗口时间
熔断服务配置
hystrix熔断参数,还有其他的,去看阳哥视频中有61
1 |
服务限流
服务监控(目前只能在在服务中监控,不能将服务监控与服务分开)
hystrix-dashboard
- 引入依赖
1 | <!--hystrix-dashboard--> |
- 配置启动类
1 |
|
- 访问路径
- 被监控程序配置要求
1 | <dependency> |

采坑解决
问题1:

解决
1 |
|
问题二:Unable to connect to Command Metric Stream.

1 | 2022-03-17 09:28:56.338 WARN 23840 --- [nio-9001-exec-4] ashboardConfiguration$ProxyStreamServlet : Origin parameter: http://localhost:8001/hystrix.stream is not in the allowed list of proxy host names. If it should be allowed add it to hystrix.dashboard.proxyStreamAllowList. |
解决
1 | hystrix: |
网关服务
为什么要添加网关?
- 只暴露网关地址接口,隐藏保护服务的地址接口
Zuul
Gateway
基本配置
- 引入依赖
1 | <!--springcloud gateway--> |
- application.yaml 配置
配置路由
- yaml配置
1 | spring: |
- 配置类方式
路由
静态路由
是将路由的映射写死
配置类配置
1 |
|
注意事项
- uri 如果添加了 path,路由器导航栏还是原来的链接;
- uri不添加path,路由器会拼接后 跳转到目标链接,导航栏链接会更新
配置文件配置
1 | spring: |
动态路由
动态路由是通过服务名称 去 注册中心去获取ip:port;
1 | spring: |
总结领悟
客户直接发来的请求使用网关做负载均衡,
服务之间的调用,使用ribbon,loadbalancer,openfeign 做负载均衡
断言
9中断言工厂
After
在xxxxx时间之前访问
时间格式获取
2022-03-21T10:49:53.861+08:00[Asia/Shanghai]
1 | public class ApplicationTest { |
过滤器
GateWayfilter(单个)
GlobalFilter(全局)
案例
1 |
|
采坑记录
问题1:
1 | Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency. |
解决方案
gateway 网关不需要springMVC,移除spring-boot-starter-web依赖
Config
服务端
spring-cloud-config 推荐与 git整合使用
配置中心的配置文件命名方式必须是 application-profile.yaml
基本配置
引入依赖
1
2
3
4
5
6
7
8
9
10<!--springcloud-config-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>启动类配置
1
2
3
4
5
6
7
8
public class ConfigCenterMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344.class, args);
}
}application.yaml配置
1
2
3
4
5
6
7
8
9
10
11
12
13spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: https://github.com/CodeWolfs/spring-cloud-config.git #这里使用http链接,使用ssh错误没有解决
search-paths:
- spring-cloud-config
# username: 2546972682@qq.com
# password: wasdsxy5210.
label: master #配置默认的分支
springcloud的访问规则
1 | /{application}/{profile}[/{label}] |
{application} 就是应用名称,对应到配置文件上来,就是配置文件的名称部分,例如我上面创建的配置文件。
{profile} 就是配置文件的版本,我们的项目有开发版本、测试环境版本、生产环境版本,对应到配置文件上来就是以 application-{profile}.yml 加以区分,例如application-dev.yml、application-sit.yml、application-prod.yml。
{label} 表示 git 分支,默认是 master 分支,如果项目是以分支做区分也是可以的,那就可以通过不同的 label 来控制访问不同的配置文件了。
采坑记录
问题1
This application has no explicit mapping for /error, so you are seeing this as a fallback

解决方案:
uri使用http链接,不要使用ssh链接
客户端
bootstrap.yaml 文件了解,以及与application.yaml文件的比较
bootstrap.yaml, 是系统级的,启动的时候会根据配置加载application.yaml
application.yaml,是用户级的。
基本配置
引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!--springcloud-config-client-->
<!--这里与config服务器不同-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--bootstrap.yaml 需要-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>启动类配置
1
2
3
4
5
6
7
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class, args);
}
}bootstrap.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20server:
port: 3355
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
spring:
application:
name: cloud-config-client
cloud:
config:
label: master
name: config
profile: dev
uri: http://config3344.com:3344
采坑记录
问题一
org.springframework.cloud.commons.ConfigDataMissingEnvironmentPostProcessor$ImportException: No spring.config.import set
解决方案
1
2
3
4
5<!--bootstrap.yaml 需要这个依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
配置动态刷新
按照上述的配置后,在github更新配置后,客户端获取不到最新的配置,需要服务器获取到配置,之后客户端才可以获取到配置。
手动刷新
引入依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>bootstrap.yaml 配置暴露
1
2
3
4
5management:
endpoints:
web:
exposure:
include: "*"在控制器上添加@RefreshScope注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConfigInfoController {
private String configInfo;
public String getConfigInfo() {
log.info(configInfo);
return configInfo;
}
}在git上的配置更新之后,需要以post的方式访问 “http://localhost:3355/actuator/refresh"
1
curl -X POST "http://localhost:3355/actuator/refresh"
自动刷新
整合bus实现自动刷新
设计思路
- 发送给微服务个体,有微服务个体想蠕虫病毒一样,相互发送
- 发送给配置中心,有配置中心通知所有微服务
这里我们选择使用第二种方法,微服务应该职责单一,不应该承担多余的不属于他的内容
基本配置
引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14<!--监控包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!--bootstrap.yaml 需要-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>application.yaml 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53# 服务端配置
spring:
application:
name: cloud-config-center
#rabbitmq 配置
rabbitmq:
host: 192.168.112.128
port: 5672
username: admin
password: admin
cloud:
# 配置中心配置
config:
server:
git:
uri: https://github.com/CodeWolfs/spring-cloud-config.git
search-paths:
- spring-cloud-config
# username: 2546972682@qq.com
# password: wasdsxy5210.
label: master
#暴露bus-refresh,刷新使用
management:
endpoints:
web:
exposure:
include: "busrefresh"
##############################################
#客户端配置
spring:
rabbitmq:
username: admin
password: admin
host: 192.168.112.128
port: 5672
application:
name: cloud-config-client
cloud:
config:
uri: http://localhost:3344
label: master
name: config
profile: dev
#客户端不需要暴露端口,但是必须添加@RefreshScope注解
management:
endpoints:
web:
exposure:
include: "*"客户端在需要更新的类上添加
@RefreshScope注解git更新后,运维人员以post方式发送http://localhost:3344/actuator/busrefresh ,这里需要注意新版本发送的是busrefresh,中间没有-。
局部通知
上述是全局通知,下面来说一下局部通知的方式
http://localhost:3344/actuator/busrefresh/{service-name}:{port}
ex:
刷新单个端口
http://localhost:3344/actuator/busrefresh/cloud-config-client:3355
刷新多个端口
http://localhost:3344/actuator/busrefresh/cloud-config-client:{3355,3366}
采坑记录
以post请求发送 http://localhost:3344/actuator/bus-refresh , 报404错误

解决方法
新版本bus-ampq,使用busrefresh,中间不加 “ **-**”

Bus
Bus 只支持 RabbitMQ 和 kafka
查看config中自动刷新内容
Stream
stream 3.1 以后推荐使用函数式编程取代注解,例如@enablebinding();
使用rabbitmq
引入依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>配置application.yaml 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51spring:
rabbitmq: #这里添加rabbitmq配置,是因为不加的话会报链接拒绝的问题
host: 192.168.112.128
port: 5672
username: admin
password: admin
application:
name: stream-rabbitmq-provider
cloud:
stream:
binders:
# 消费者环境配置
consumerRabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: 192.168.112.128
port: 5672
username: admin
password: admin
defaultRabbit: #是声明binder属性,声明使用的是什么消息中间件,以及配置中间件环境,defaultRabbit是配置名称,在binddings中使用default-binder
type: rabbit
environment:
spring:
rabbitmq:
host: 192.168.112.128
port: 5672
username: admin
password: admin
bindings:
output: #生产者管道channel,发送消息需要使用,output 是对象名
destination: studyExchange
content-type: application/json
default-binder: defaultRabbit
input: #消费者channel
destination: studyExchange
content-type: application/json
default-binder: consumerRabbit
eureka:
instance:
prefer-ip-address: true
hostname: rabbitmq-provider
instance-id: rabbitmq-provider
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka编码
生产者编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MessageProviderImpl implements MessageProvider {
private MessageChannel output; //output需要和配置文件中的一样
public String send() {
String s = UUID.randomUUID().toString();
boolean send = output.send(MessageBuilder.withPayload(s).build());
log.info(s);
return null;
}
}消费者编码
1
2
3
4
5
6
7
8
9
10
11
12
public class ReciveMessageController {
private String serverPort;
//这里试了使用其他的名称,不可以,好像只能使用input
public void reciveMessage(Message<String> message) {
System.out.println("消费者1: " + message.getPayload() + "\tport:" + serverPort);
}
}
重复消费和消息持久化
上述配置的问题

重复消费
使用group分组解决这个问题
这个group实际上在rabbitmq就是队列的意思,在没有指定的情况下,每个消费者都有一个队列,也就是说系统会为每个消费者创建一个队列(如果不指定的话)
所以如果需要避免重复消费问题,就将消费者放在同一组下就可以了。
设置的group,durable=true
1
2
3
4
5
6
7# 这个分组绑定只能在消费者端进行,因为系统会由此确定你的消费者绑定的是哪个队列
bindings:
input:
destination: studyExchange
content-type: application/json
default-binder: consumerRabbit
group: wangzheB # 配置分组消息持久化
假设情况
消费者A和消费者B是同一组的消费者,使用同一group,在两台消费者停机后,删除或者修改消费者A的分组,
此时生产者一直在往这个group里面发送消息,发送了N条消息
此时启动消费者A,消费者A 获取不到任何消息,会错过消息
此时启动消费者B,消费者B仍然可以获取到全部消息
rabbitmq整合 采坑
3.1后很多注解推荐使用函数式编程代替
rabbitmq的链接信息需要配置两次,一次是stream的enviroment,一次是在spring下配置rabbitmq
- 这个是没有问题的,是environment写错了,导致配置配有读取到。

Sleuth And Zipkin
sleuth 是实现
zipkin 是展示
zipkin启动命令
1 | java -jar zipkin-server-2.23.16-exec.jar |
服务链路跟踪
基本配置
在需要监控的服务中 引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!--不适用这个依赖了-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!--现在使用这两个依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>微服务
application.yaml配置1
2
3
4
5
6
7
8spring:
application:
name: cloud-payment-service
zipkin:
base-url: http://127.0.0.1:9411
sleuth:
sampler:
probability: 1 #采样率,0-1之间,一般是0.5效果图


