一.前言
- 这两个都不是停更才不用的…
- 只是单纯被后起之秀阿里巴巴的Nacos所取代
- 当然还有很多公司在用,一共三套选择
- Config+Bus
- Spring Cloud Alibaba Nacos
- 携程的Apollo(阿波罗)-开源的
二.SpringCloud Config分布式配置中心
1.概述
A.分布式系统面临的—配置问题
- 微服务意味着将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的
- SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理…
B.是什么
-
SpringCloud Config微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置
-
SpringCloud Config分为服务端和客户端两部分
-
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
-
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容
C.能干嘛
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/bete/release
- 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
- 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
- 将配置信息以REST接口的形式暴露
- post,curl访问刷新均可
D.与GitHub整合配置
- 由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件,但最推荐的还是Git,而且使用的是http/https访问的形式)
E.官网
2.Config服务端配置与测试
-
用你自己的账户在GitHub上新建一个名为springcloud-config的新Repository
-
由上一步获得刚新建的git地址-git@githubxxx
-
本地硬盘目录上新建git仓库并clone
- 本地地址:xxx
- git命令:git clone git@github.com:xxx
-
此时在本地盘下\springcloud-config…
- 表示多个环境的配置文件
- 保存格式必须是UTF-8
- 如果需要修改,此处模拟运维人员操作git和github
- git add .
- git commit -m “init yml”
- git push origin master
-
新建Module模块cloud-config-center-3344,它即为Cloud的配置中心模块cloudConfig Center
-
POM
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.playmaker.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!--日常通用jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- yml
server:
port: 3344
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: git@github.com:18808108305/springcloud-config.git # Github上面的git仓库名字
# 搜索目录
search-paths:
- springcloud-config
# 超时时间
timeout: 500
# 默认分支
default-label: main
# 服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
- 主启动类
@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344.class,args);
}
}
- 修改windows下host文件,增加映射
127.0.0.1 config-3344.com
- 测试通过Config微服务是否可以从GitHub上获取配置内容
- 启动微服务3344
- http://config-3344.com:3344/main/config-dev.yml
- 注意,如果你启动3344项目,报这个错,原因是生成密钥的时候使用openssh版本生成导致版本过高
- 还有一点注意,github 2020.10月后把默认主分支名改成了main,而不再是master了
ssh-keygen -m PEM -t rsa 重新生成旧格式的key,变可解决
- 若出现,请在配置文件中配置私钥(公钥配置到你的github/码云上)
- 注意格式以及要加上下图那个符号
com.jcraft.jsch.jschexception: reject hostkey
- 配置读取规则
- 官网
- 官网
/{label}/{application}-{profile}.yml
/{application}-{profile}.yml
/{application}/{prifile}[/{label}]
//label:分支 name:服务名 profiles:环境(dev/test/prod)
- 成功实现了用SpringCloud Config通过GitHub获取配置信息
3.Config客户端配置与测试
A.新建cloud-config-client-3355
B.pom
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.playmaker.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!--日常通用jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
C.bootstrap.yml
a.是什么
- application.yml是用户级的资源配置项
- bootstrap.xml是系统级的,优先级更加高
- Spring Cloud会创建一个"BootStrap Context",作为Spring应用的’Applicaiton Context’的父上下文。初始化的时候,‘BootStrap Context’负责从外部源加载配置属性并解析配置。这两个上下文共享一个外部获取的’Environment’
- ‘BootStrap’属性由高优先级,默认情况下,它们不会被本地配置覆盖。"BootStrap Context"和“Application Context”有着不同的约定,所以新增一个’bootstrap.yml’文件,保证"BootStrap Context"和“Application Context”配置的分离
- 要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的
- 因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml
b.内容
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
# 分支名
label: main
# 配置文件名称
name: config
# 读取后缀名称 上述3个综合:main分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/main/config-dev.yml
profile: dev
# 配置中心地址
uri: http://localhost:3344
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
D.修改config-dev.yml配置并提交到GitHub中,比如加个变量age或者版本号version
E.主启动类
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class,args);
}
}
F.业务类
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
G.测试
- 访问 http://localhost:3355/configInfo 链接
H.成功实现了客户端3355访问SpringCloud Config3344通过GitHub获取配置信息
I.问题随时而来,分布式配置的动态刷新问题
-
修改config-dev.yml配置并提交到Github中,比如加个变量age或者版本号version
-
Linux运维修改GitHub上配置文件内容做调整
-
刷新3344,发现ConfigServer配置中心立刻响应
-
刷新3355,发现ConfigClient客户端没有任何响应
-
3355没有变化除非自己重启或者重新加载‘’
-
难到每次运维修改配置文件,客户端都要重启???噩梦
4.Config客户端之动态刷新
A.避免每次更新配置都要重启客户端微服务3355
B.动态刷新步骤
a.修改3355模块
b.pom引入actuator监控
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
c.修改yml,暴露监控端口
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
d.@RefreshScope业务类Controller修改
e.此时修改github->3344->3355
- 访问http://localhost:3355/configInfo,没有改变…
f.how
- 需要运维人员发送Post请求刷新3355
- 必须POST请求
- curl -X POST “http://localhost:3355/actuator/refresh”
g.再次
- ok
- 避免了服务重启
C.想想还有什么问题
- 假如有多个微服务客户端3355/3366/3377.。。。
- 每个微服务都要执行一次post请求,手动刷新?
- 可否广播,一次通知,处处生效?
- 我们想大范围的自动刷新,求方法?
- 所以引入了下一章消息总线!!!!!
三.SpringCloud Bus消息总线(Config加强)
- 广播型的自动版的动态刷新,与Config是绝代双骄,一般一块用
1.概述
A.上一讲的加深和扩充,一言以蔽之
- 分布式自动刷新配置功能
- Spring Cloud Bus配合Spring Cloud Config 使用可以实现配置的动态刷新
B.是什么
- Bus支持两种消息代理:RabbitMQ和Kafka
- Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新
- Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统连接起来饿框架,它整合了Java的时间处理机制和消息中间件的功能
C.能干嘛
- Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改,事件推送等,也可以当做微服务间的通信通道
D.为何被称为总线
a.什么是总线
b.基本原理
2.RabbitMQ环境配置
A.安装Erlang,下载地址
- http://erlang.org/download
B.安装RabbitMQ
C.进入RabbitMQ安装目录下的sbin目录
D.输入以下命令启动管理功能
rabbitmq-plugins enable rabbitmq_management
E.访问地址查看是否安装成功
- http://localhost:15672/
F.输入账号密码并登陆guest guest
3.SpringCloud Bus动态刷新全局广播
A.必须具备良好的RabbitMQ环境
B.演示广播效果,增加复杂度,再以3355为模板再制作一个3366
- 注意启动3355和3366要等一个启动完成后再启动另外一个,不然会报错…
a.新建
- cloud-config-client-3366
b.pom
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.playmaker</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--日常通用jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
c.yml
server:
port: 3366
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
# 分支名
label: master
# 配置文件名称
name: config
# 读取后缀名称 上述3个综合:main分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/main/config-dev.yml
profile: dev
# 配置中心地址
uri: http://localhost:3344
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
d.主启动
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3366 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3366.class,args);
}
}
e.controller
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
C.设计思想
-
利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
-
利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
-
图二中的架构显然更加适合,图一不适合原因如下
- 打破了微服务的职责单一性,因为微服务本身是业务模块,不应该承担配置刷新的职责
- 破坏了微服务各节点的对等性
- 有一定局限性,比如,微服务在迁移时,它网络地址常常发生变化,此时如果想要做到自动刷新,那就会增加更多的修改
D.给cloud-config-center-3344配置中心服务端添加消息总线支持
a.pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
b.yml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#暴露bus刷新配置的端点
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
E.给cloud-config-client-3355客户端添加消息总线支持
a.pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
b.yml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
F.给cloud-config-client-3366客户端添加消息总线支持
a.pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
b.yml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
G.测试
-
运维工程师
- 修改Github上配置文件增加版本号
- 发送POST请求
- curl -X POST “http://localhost:3344/actuator/bus-refresh”
- 一次刷新,处处生效
-
配置中心
- http://config-3344.com:3344/config-dev.yml
-
客户端
- http://localhost:3355/configInfo
- http://localhost:3366/configInfo
- 获取配置信息,发现都已经刷新了
H.一次修改,广播通知,处处生效
4.SpringCloud Bus动态刷新定点通知
A.不想全部通知,只想通知定点通知
- 只通知3355,不通知3366
B.简单一句话
- 指定具体某一个实例生效而不是全部
- http://localhost:配置中心端口号/actuator/bus-refresh/{destination}
- /bus/refresh请求不再发送到具体的服务实例上,而是发送给config server通过destination参数类指定需要更新配置的服务或实例
C.案例
- 刷新运行在3355端口上的config-client为例子
- 只通知3355,3366不终止
- curl -X POST “http://localhost:3344/actuator/bus-refresh/config-client:3355”
D.通知总结
四.SpringCloud Stream消息驱动
- 首先想到消息中间件(MQ)
//4个学起来负担重
ActiveMQ
RabbitMQ
RocketMQ
Kafka
- 类似于以前的数据库,不论用什么数据库,我只需要用Hibernate封装的session来操作
1.消息驱动概述
A.是什么
a.一句话
- 屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
b.官网
https://spring.io/projects/spring-cloud-stream
B.设计思想
a.标准MQ
- 生产者/消费者之间靠消息媒介传递信息内容
- Message
- 消息必须走特定的通道
- 消息通道MessageChannel
- 消息通道里的消息如何被消费呢,谁负责收发处理
- 消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅
- 消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅
b.为什么用Cloud Stream
-
stream凭什么可以统一底层差异?
-
Binder
- INPUT对应消费者
- OUTPUT对应生产者
c.Stream中的消息通信方式遵循了发布-订阅模式
- Topic主题进行广播
- 在RabbitMQ就是Exchange
- 在Kakfa中就是Topic
C.Spring Cloud Stream标准流程套路
- Binder:很方便的连接中间件,屏蔽差异
- Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
- Source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入
D.编码API和常用注解
2.案例说明
A.RabbitMQ环境已经OK
B.工程中新建三个子模块
- cloud-stream-rabbitmq-provider8801,作为生产者进行发消息模块
- cloud-stream-rabbitmq-consumer8802,作为消费接收模块
- cloud-stream-rabbitmq-consumer8803,作为消费接收模块
3.消息驱动之生产者
A.新建Module
- cloud-stream-rabbitmq-provider8801
B.pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.playmaker</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
C.yml
server:
port: 8801
spring:
application:
name: cloud-stream-privider
cloud:
stream:
binders: #自此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
output: #这个名字是一个通道的名称
destination: studyExchange #表示要使用的exchange名称定义
content-type: application/json #设置消息类型,本次为json
binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true #访问的路径变为IP地址
D.主启动类StreamMQMain8801
@SpringBootApplication
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class,args);
}
}
E.业务类
- 发送消息接口
public interface IMessageService {
public String send();
}
- 发送消息接口实现类
@EnableBinding(Source.class) //定义消息的推送管道
public class MessageServiceImpl implements IMessageService {
@Resource
private MessageChannel output;//消息发送管道 @Source注解里面制定了注入bean的名字 所以一定要写成output
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*******serial:"+serial);
return null;
}
}
- Controller
@RestController
public class SendMessageController {
@Resource
private IMessageService messageService;
@GetMapping("/sendMessage")
public String sendMessage(){
return messageService.send();
}
}
F.测试
- 启动7001,再启动rabbitmq,再启动8801
- 访问http://localhost:8801/sendMessage
- 连续访问会出现这种情况
4.消息驱动之消费者
A.新建Module
- cloud-stream-rabbitmq-consumer8802
B.pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.playmaker</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
C.yml
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: #自此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
input: #这个名字是一个通道的名称
destination: studyExchange #表示要使用的exchange名称定义
content-type: application/json #设置消息类型,本次为json
binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔
instance-id: receive-8802.com
prefer-ip-address: true #访问的路径变为IP地址
D.主启动类StreamMQMain8802
@SpringBootApplication
public class StreamMQMain8802 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8802.class,args);
}
}
E.业务类
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message){
System.out.println("消费者1号,----->接收到的消息:"+message.getPayload()+"\t port:"+serverPort);
}
}
F.测试8801发送8802接收消息
- 启动7001,8801和8802
- 访问http://localhost:8801/sendMessage
5.分组消费与持久化
A.依照8802,clone出来一份运行8803
- cloud-stream-rabbitmq-consumer8803
B.启动
- RabbitMQ
- 7001 服务注册
- 8801 消息生产
- 8802 消息消费
- 8803 消息消费
C.运行后有两个问题
- 有重复消费问题
- 访问http://localhost:8801/sendMessage,两个消费者同时消费了
- 消息持久化问题
D.消费
- 目前是8802/8803同时都收到了,存在重复消费问题
a.解决方法
分组和持久化属性group
b.生产实例案例
同一组内会发生竞争关系,只有其中一个可以消费
- 如下图,存在两个不同的组,所以会被8802/8803同时消费(自动生成的流水号)
E.分组
a.原理
- 微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次
- 不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费
b.8802/8803都变成不同组,group两个不同
-
group:playmakerA,playmakerB
-
8802修改yml,在binder下配置一个group
....
binder: xxx
group: playmakerA
....
- 8803修改yml
....
binder: xxx
group: playmakerB
....
-
我们自己配置
-
结论
c.8802/8803实现轮询分组,每次只有一个消费者 8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消息
d.8802/8803都变成相同组,group两个相同
F.持久化
- 通过上述,解决了重复消费问题,再看看持久化
- 停止8802/8803并去除掉8802的分组group,8803的分组group不去掉
- 8801先发送4条消息到rabbitmq
- 先启动8802,无分组属性配置,后台没有打出消息
- 再启动8803,有分组属性配置,后台打出了MQ上的消息
- 原理是:exchange数据发送到队列中,由于02重启没有设置分组,会重新创建队列并监听,而03还是监听原来的队列
五.SpringCloud Sleuth分布式请求链路跟踪
1.概述
A.为什么会出现这个技术,需要解决那些问题?
- 在服务器框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败
B.是什么
- https://github.com/spring-cloud/spring-cloud-sleuth
- Spring Cloud Sleuth提供一套完整的服务跟踪的解决方案
- 在分布式系统中提供追踪解决方案并且兼容支持了zipkin
C.解决
2.搭建链路监控步骤
A.zipkin
a.下载
-
Spring Cloud从F版已不需要自己构建ZipKin Server了 只需要调用jar包即可
-
https://zipkin.io/pages/quickstart.html
-
zipkin-server-2.12.9-exec.jar
b.运行jar
java -jar xxx
c.运行控制台
- http://localhost:9411/zipkin/
- 术语
- 完整的调用链路
- 上图what
- 名词解释
- Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
- span:表示调用链路来源,通俗的理解span就是一次请求信息
B.服务提供者
a.cloud-provider-payment8001
b.pom
<!--包含了zipkin和Sleuth-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
c.yml
spring:
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
#采样率值介于0到1之间 1表示全部采集
probability: 1
d.业务类PaymentController
@GetMapping("/payment/zipkin")
public String paymentZipKin(){
return "hi,i'am paymentzipkin server fall back,welcome to playmaker,哈哈";
}
C.服务消费者
a.cloud-consumer-order80
b.pom
<!--包含了zipkin和Sleuth-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
c.yml
spring:
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
#采样率值介于0到1之间 1表示全部采集
probability: 1
d.业务类PaymentController
@GetMapping("/consumer/payment/zipkin")
public String paymentZipKin(){
String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/",String.class);
return result;
}
D.依次启动eureka7001/8001/80
- 80调用8001几次