1. Stream
是一个构建消息驱动微服务的框架。应用程序通过
inputs
或者outputs
来与Spring Cloud Stream 中binder
对象交互;我们通过配置来绑定,而binder
对象负责与消息中间件交互。我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便的使用消息驱动的方式。
通过Spring Intergration来连接消息代理中间件以实现消息事件驱动。
为一些供应商产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
支持RabbitMQ
和Kafka
。
总之它的作用是屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型。
- 截止到2022.6.14,仍然只支持RabbitMQ和Kafka
1.1 设计思想
-
标准化的消息队列,生产者和消费者之间必须走特定的信道来传递信息,信道里面的消息只有被订阅了之后才会有消息处理器来接收。
-
Stream设计方案:通过定义绑定器作为中间件,完美地实现了应用程序与消息中间件之间的隔离,通过向应用程序暴露统一的信道。使得应用程序不需要再考虑各种不同的消息中间件实现。
-
Binder:
input
:消费者;output
:生产者。
1.2 标准流程
Binder
:连接中间件,屏蔽差异;Channel
:消息通讯系统中实现存储和转发的媒介,通过Channel对队列进行配置;Soure/Sink
:参照stream自身,发出就是输出source
,接收就是输入sink
;
1.3 常用注解
@Input
:输入信道,通过输入信道进入应用程序;@Output
:输出信道,通过输出信道离开应用程序。@StreamLinster
:监听队列,消费者的队列和消息接收;@EnableBinding
:指Channel和Exchange绑定在疫情。
1.4 生产者搭建
-
新建module:
cloud-stream-rabbitmq-provider8801
-
依赖:
<dependencies> <dependency> <groupId>org.example</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <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.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>
-
配置文件:
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: service-url: defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/, # 注入7002 和 7001
-
主启动类:
@SpringBootApplication public class StreamMQMain8801 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8801.class,args); } }
-
发送消息接口:
public interface IMessageProvider { String send(); }
-
发送消息接口实现类,注意导包位置。
import com.jm.cloud.service.IMessageProvider; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; 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 java.util.UUID; /** * @Description * @date 2022/6/14 14:54 */ @Slf4j @EnableBinding(Source.class) // 定义消息推送管道 public class MessageProviderImpl implements IMessageProvider { @Autowired private MessageChannel output; // 消息发送管道 @Override public String send() { String serial = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(serial).build()); log.info("****** serial:" + serial); return serial; } }
-
controller:
@RestController @RequestMapping("/send") public class SendMessageController { @Autowired private IMessageProvider messageProvider; @GetMapping public String sendMessage(){ return messageProvider.send(); } }
-
启动测试:可以看到新的交换机
1.5 消费者搭建
-
新建两个基本相同的module
cloud-stream-rabbitmq-consumer8802
和cloud-stream-rabbitmq-consumer8803
-
依赖:
<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.example</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</version> </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.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>
-
配置文件:
server: port: 8803 # 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://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/, # 注入7002 和 7001
-
启动类:8803与整个相同。
@SpringBootApplication public class StreamMQ8802 { public static void main(String[] args) { SpringApplication.run(StreamMQ8802.class,args); } }
-
业务类:注意导包位置:
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; /** * @Description * @date 2022/6/14 15:27 */ @Component @EnableBinding(Sink.class) public class ReceiveMessageListener { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) public void input(Message<String > message){ System.out.println(serverPort + "->"+message.getPayload()); } }
-
启动测试8802和8801测试:在8801发送消息时,8802可以接收到。
1.6 重复消费
如下方场景下,一个订单被两个服务获取到,会造成数据错误。
- 启动8803;
- 8801进行发送消息:可以看到两个消费者都收到了当前消息。
1.7 group
1.7.1 解决重复消费
在Stream中可以通过分组来解决重复消费问题。同一个group中的多个消费者是竞争关系,一条消息只会被一个应用消费一次;不同组可以重复消费。
-
修改配置文件,使8802 和 8803 都处于A组
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 # 设置要绑定的消息服务的具体设置 group: A # 指定组 eureka: client: # 客户端进行Eureka注册的配置 service-url: defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/, # 注入7002 和 7001
-
启动,发送两条消息:可以看出8803和8802是轮询消费的。
1.7.2 持久化
如果不添加group属性,当消费者端宕机时再恢复时,消息不会持久化,即不会收到宕机时发送的消息,会造成消息的丢失。
添加group属性就可以完美的解决消息的持久化来避免消息丢失的方案。
2. Sleuth
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同服务器节点调用来协同产生最后的请求结果,每个前段请求都会形成一个复杂的分布式服务调用链路,链路中任何一环出现高延时或错误都会引起整个服务最后的请求失败。
Sleuth就是Spring Cloud提供的完整的服务跟踪的解决方案。并且兼容支持zipkin。
2.1 安装zipkkin和使用
- 下载添加链接描述
- 运行:
java -jar D:\zipkin\zipkin-server-2.14.1-exec.jar
-
调用原理:一条请求链路通过TreaceID唯一标识,Span表示发起的请求,各个span通过parentID关联。
-
依赖关系:
2.2 使用sleuth监控
在8001服务提供者和80消费者端都进行修改:
-
引入依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
-
修改配置文件:
spring: application: name: cloud-payment-service # 主要是修改这部分对于zipkin和sleuth zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1 # 1 表示全部采样 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://localhost:3306/jm-cloud?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root
2.3 访问测试
访问80端口的接口:http://localhost:80/consumer/payment/1
- 可以在界面搜索到服务调用的信息,点击可以查看详情。