SpringCloud Netflix-springcloudnetflix(四)https://developer.aliyun.com/article/1469571
RabbitMQ环境配置
安装Erlang,下载地址:http://erlang.org/download/otp_win64_21.3.exe
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yAMb74mp-1648908821935)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/ img /image-20210911012803187.png)]
安装RabbitMQ,下载地址 :https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe
进入RabbitMQ安装目录下的sbin目录
D:\rabbitmq_server-3.7.14\sbin
打开命令行 : rabbitmq-plugins enable rabbitmq_management
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3MXua8zG-1648908821938)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/ img /image-20210911013147089.png)]
执行完就可以看到
访问地址查看是否安装成功 : http://localhost:15672/
SpringCloud Bus动态刷新全局广播
必须先具备良好的RabbitMQ环境先
演示广播效果,增加复杂度,再以3355为模板再制作一个3366
设计思想设计思想
- 利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
- 利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置(更加推荐)
- 图二的架构显然更加合适,图一不适合的原因如下
- 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新职责
- 破坏了微服务各节点的对等性
- 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改
给cloud-config-center-3344配置中心服务端添加消息总线支持
pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
更新配置文件
server: port: 3344 spring: application: name: cloud-config-center cloud: config: server: git: uri: https://github.com/hhf19906/springcloud-config.git #git@github.com:hhf19906/springcloud-config.git search-paths: - springcloud-config label: master rabbitmq: host: localhost port: 5672 username: guest password: guest eureka: client: service-url: defaultZone: http://localhost:7001/eureka management: endpoints: web: exposure: include: 'bus-refresh'
给cloud-config-center-3355客户端添加消息总线支持
pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
配置
server: port: 3355 spring: application: name: config-client cloud: config: label: master name: config profile: dev uri: http://localhost:3344 rabbitmq: host: localhost port: 5672 username: guest password: guest eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka management: endpoints: web: exposure: include: "*"
3366和3355照猫画虎
测试
修改Github上配置文件增加版本号
发送Post请求
curl -X POST "http://localhost:3344/actuator/bus-refresh"
一次发送,处处生效
此时查看配置中心
http://config-3344.com/config-dev.yml
查看客户端
http://localhost:3355/configInfo
http://localhost:3366/configInfo
获取配置信息,发现都已经刷新了 , 一次修改,广播通知,处处生效
SpringCloud Bus动态刷新置指定通知
不想全部通知,只想定点通知
- 只通知3355
- 不通知3366
指定具体某一个实例生效而不是全部
公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
/bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例
发送完之后会发现
只有3355更新了
3366没更新
全局通知流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tCMdlQYB-1648908821942)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/ img /image-20210911014053498.png)]
Stream消息驱动
消息驱动概述
什么是SpringCloudStream : 官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架。
- 应用程序通过inputs或者outputs来与Spring Cloud Stream中binder对象交互。
- 通过我们配置来binding(绑定),而Spring Cloud Stream的 binder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。
- 通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
- Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,
- 引用了发布-订阅、消费组、分区的三个核心概念。
目前仅支持RabbitMQ、Kafka。
屏蔽底层消息中间件的差异,降低切换版本,统一消息的编程模型
官网 : https://spring.io/projects/spring-cloud-stream#overview
中文指导手册 : https://m.wang1314.com/doc/webapp/topic/20971999.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuCmGUaT-1648908821942)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/ img /image-20210911014511919.png)]
设计思想
标准的mq
- 生产者/消费者之间靠消息媒介传递信息内容 : Message
- 消息必须走特定的通道 消息通道MessageChannel
- 消息通道里的消息如何被消费呢,谁负责收发处理 :消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器订阅
为什么用Cloud Stream
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SOsiX8sO-1648908821943)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/ img /image-20210911014655210.png)]
这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。
stream凭什么可以统一底层差异?
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性
- 通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。
- 通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
默认情况下,RabbitMQ绑定器实现将每个目标映射到TopicExchange。对于每个消费者群体。
Binder绑定器
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性.通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。
Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ytNoZzAl-1648908821944)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/ img /image-20210911015213198.png)]
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
- INPUT对应于消费者
- OUTPUT对应于生产者
Stream中的消息通信方式遵循了发布-订阅模式
Topic主题进行广播
- 在RabbitMQ就是Exchange
- 在kafka中就是Topic
Spring Cloud Stream标准流程套路
- Binder 很方便的连接中间件,屏蔽差异
- Channel 通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过对Channel对队列进行配置
- Source和Sink 简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入
编码API和常用注解
案例说明
RabbitMQ环境已经OK
工程中新建三个子模块
- cloud-stream-rabbitmq-provider8801,作为生产者进行发消息模块
- cloud-stream-rabbitmq-consumer8802,作为消息接收模块
- cloud-stream-rabbitmq-consumer8803,作为消息接收模块
消息驱动之生产者
cloud-stream-rabbitmq-provider8801
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
配置文件
server: port: 8801 spring: application: name: cloud-stream-provider 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,文本则设置“text/plain” binder: defaultRabbit # 设置要绑定的消息服务的具体设置 eureka: client: # 客户端进行Eureka注册的配置 service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒) lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒) instance-id: send-8801.com # 在信息列表时显示主机名称 prefer-ip-address: true # 访问的路径变为IP地址
主启动类StreamMQMain8801
@SpringBootApplication public class StreamMQMain8801 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8801.class, args); } }
业务类
发送消息接口
public interface IMessageProvider { public String send(); }
发送消息接口实现类
@EnableBinding(Source.class) //定义消息的推送管道 public class MessageProviderImpl implements IMessageProvider { @Resource private MessageChannel 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 IMessageProvider messageProvider; @GetMapping(value = "/sendMessage") public String sendMessage() { return messageProvider.send(); } }
测试 访问: http://localhost:8801/sendMessage
看到控制带输出的流水号和端口号
消息驱动之消费者
cloud-stream-rabbitmq-consumer8802
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
配置文件
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,文本则设置“text/plain” binder: defaultRabbit # 设置要绑定的消息服务的具体设置 eureka: client: # 客户端进行Eureka注册的配置 service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒) lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒) instance-id: receive-8802.com # 在信息列表时显示主机名称 prefer-ip-address: true # 访问的路径变为IP地址
主启动类StreamMQMain8802
@SpringBootApplication public class StreamMQMain8802 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8802.class, args); } }
业务类
@Component @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); } }
测试8801发送8802接收消息
http://localhost:8801/sendMessage
这个时候8802会就显示收到的消息
依照8802,clone出来一份运行8803
运行后两个问题
有重复消费问题
目前是8802/8803同时都收到了,存在重复消费问题
如何解决?
同一组的消费者是竞争关系,只有一个可以消费
原理
微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。
8802/8803实现了轮询分组,每次只有一个消费者 8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3fpi7N3K-1648908821945)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/ img /image-20210911020259779.png)]
消息持久化问题
通过上述,解决了重复消费问题,再看看持久化
- 8803的分组group:atguiguA没有去掉
- 8803的分组group:atguiguA没有去掉
8801先发送4条信息到rabbitmq
- 先启动8802,无分组属性配置,后台没有打出来消息
- 先启动8803,有分组属性配置,后台打出来了MQ上的消息
总结:有分组的消费者,在启动后可以读取分组的信息
Sleuth分布式请求链路追踪
概述
为什么会出现这个技术?需要解决哪些问题?
官网:https://github.com/spring-cloud/spring-cloud-sleuth
- Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案
- 在分布式系统中提供追踪解决方案并且兼容支持了zipkin
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2WRKDbHA-1648908821946)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/ img /image-20210911020733858.png)]
在我们服务调用的时候经常会有
一个服务调一个服务多个微服务调用
为了方便我们查看服务之间的调用层次
我们产生的了链路追踪
搭建链路监控步骤
1.zipkin
SpringCloud从F版起已不需要自己构建Zipkin server了,只需要调用jar包即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gx7k62J2-1648908821946)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/ img /image-20210911021126436.png)]
运行jar
查看 http://localhost:9411/zipkin/
上图看起来链路十分的复杂
下图相对简单清晰一些
2.服务提供者
继续找到我们最初的服务提供者cloud-provider-payment8001
依赖
<!--包含了sleuth+zipkin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
配置
server: port: 8001 spring: application: name: cloud-payment-service zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: username: root password: mybatis: mapperLocations: classpath:mapper/*.xml type-aliases-package: com.atguigu.springcloud.entities eureka: client: register-with-eureka: true fetchRegistry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版 instance: instance-id: payment8001 prefer-ip-address: true
添加一个新方法
@GetMapping("/payment/zipkin") public String paymentZipkin() { return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~"; }
3.服务消费者(调用方)
久违的服务者 cloud-consumer-order80
依赖
<!--包含了sleuth+zipkin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
配置
server: port: 80 spring: application: name: cloud-order-service zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1 eureka: client: #表示是否将自己注册进EurekaServer默认为true。 register-with-eureka: false #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: #单机 #defaultZone: http://localhost:7001/eureka # 集群 defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
业务类OrderController
// ====================> zipkin+sleuth @GetMapping("/consumer/payment/zipkin") public String paymentZipkin() { String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class); return result; }
4.依次启动eureka7001/8001/80
80调用8001几次测试下
5.打开浏览器访问:http:localhost:9411
查看层级 查看依赖关系
到这里 springcloud netflix 的学习就结束啦 !!! 完结撒花