什么是Spring Cloud Stream
官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架
。
应用程序通过inputs或者outputs来与Spring Cloud Stream中binder对象交互(我们主要就是操作binder对象与底层mq交换)。通过我们配置来binding(绑定),而Spring Cloud Stream的binder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。
通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。目前仅支持RabbitMQ、Kafka.
Spring Cloud Stream是怎么屏蔽底层差异的?
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Spring Cloud Stream的业务流程:
消息驱动的生产者
- 建模块
cloud-stream-rabblitmq-provider8801
- 导依赖
<!--stream rabbit -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- 写yml文件
server:
port: 8801
spring :
application:
name: cloud-stream-provider
cloud:
stream:
binders: #在此处配置要绑定的rabbitmg的服务信息;
defaultRabbit: #表示定义的名称,用于Jbinding整合 注意,这个defalutRabbit是我们自定义的一个名字
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 #设置要绑定的消息服务的具体设置,指点上面我们定义的defaultRabbit
eureka:
client:
register-with-eureka: true # true向服务中心注册自己
fetch-registry: true # 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/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地址
- 主启动类
- 业务类
定义一个发送消息的接口,并编写实现类
import com.kk.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import javax.annotation.Resource;
import java.util.UUID;
// 表示当前这个类是source,负责生产消息,并且发送给channel
@EnableBinding(Source.class)
public class MessageProviderImpl implements IMessageProvider {
// channel,我们将消息发送给channel
@Resource
private MessageChannel output;
@Override
public String send() {
String serial = UUID.randomUUID().toString();
// 构建消息发送,build方法会构建一个Message类
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****************serical:"+serial);
return null;
}
}
调用send方法,将消息发送给channel,然后channel将消息发送给binder,然后发送到rabbitmq中
6. 写controller,调用接口方法
7. 启动8801后,会在rabbitmq中创建一个Exchange,就是我们配置文件中配置的exchange。访问接口方法
消息驱动的消费者
- 同样的步骤
只是修改一下yml文件中的端口以及将绑定的spring.cloud.bindings.output
改为spring.cloud.bindings.input
,instance-id也修改一下
server:
port: 8802
spring :
application:
name: cloud-stream-provider
cloud:
stream:
binders: #在此处配置要绑定的rabbitmg的服务信息; 注意,这个defalutRabbit
defaultRabbit: #表示定义的名称,用于Jbinding整合 是我们自定义的一个名字
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:
register-with-eureka: true # true向服务中心注册自己
fetch-registry: true # 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka # 注册地址和端口
instance:
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔(默认是90秒)
instance-id: send-8802.com
prefer-ip-address: true # 访问路径可以显示IP地址
- 实现监听
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
@Component
// 启动绑定,就是表示当前类是sink,负责接收channel发送过来的数据
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
// 这里表示监听sink的input,而input我们在配置文件中配置了,绑定在了指定Exchange上获取数据
@StreamListener(Sink.INPUT)
public void input(Message<String> message){
System.out.println("我是消费者1号------->接收到的消息:" + message.getPayload()+"\t"+serverPort);
}
}
- 启动8801和8802,测试 生产者生产的消息消费者是否能消费,控制台可以查看结果
重复消费问题
以8802为模板克隆一个8803消费者,都启动测试,会发现8801的生产的消息都被两个消费者消费了。
这是由于8801和8802没有配置分组,默认是不同分组的,所以都对消息进行了消费。
解决----->定义分组
修改8802,8803的配置文件,都定义为相同分组groupA
这样生产者生产的消息每一次都只会被同一组内的一个消费者消费
消息持久化问题
停掉8802和8803,删除8802的group分组属性,将8802从A组中移除。此时让8801生产消息,接着重启8802和8803服务,发现8802会发生消息丢失故障
,而8803会把未曾消费过的消息重新获得并消费。
链路跟踪
-
下载zipkin的
zipkin-server-2.12.9-exec.jar
包
地址:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server
-
jar命令运行jar包
-
运行控制台
访问http://localhost:9411/zipkin/
使用sleuth:
不需要额外创建项目,使用之前的payment8001和orde80即可
- 都导入依赖
<!-- 包含了sleuth zipkin 数据链路追踪-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
- 都添加配置
spring:
zipkin:
base-url: http://localhost:9411 #指定zipkin的地址
sleuth:
sampler:
probability: 1 #如果是1,表示不放过所有数据 #采样率值介于0到工之间,1则表示全部采集
8001controller中增加一个测试方法
@GetMapping("zipkin")
public String paymentzipkin(){
return "hhhhhhhhhhhhhhhhhhhhhhhh-----paymentzipkin";
}
80服务调用8001服务的方法
@GetMapping("/consumer/payment/zipkin")
public String paymentzipkin(){
return restTemplate.getForObject(PAYMENT_URL+"/payment/zipkin", String.class);
}
- 启动服务,调用测试,
http://localhost/consumer/payment/zipkin
,刷新zipkin窗口,可以查看调用链路和依赖关系