Spring Cloud Stream(十三)
说明
对Spring Boot 和 Spring Integration的整合,通过Spring Cloud Stream能够简化消息中间件使用的复杂难度!让业务人员更多的精力能够花在业务层面
简单例子
consumer
1.创建一个一个项目名为spring-cloud-stream-consumer
2.引入pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
3.yml配置
spring:
application:
name: streamConsumer
rabbitmq: #更多mq配置看书331页
username: liqiang
password: liqiang
host: localhost
port: 5672
server:
port: 8081
5.创建消息监听类
//实现对定义了多个@Input和@Output的接口实现对通道的绑定 Sink定义了@Input 我们自己处理时是自己定义接口
@EnableBinding(Sink.class)
public class SkinReceiverService {
private static Logger logger = LoggerFactory.getLogger(SkinReceiverService.class);
//对input的消息监听处理
@StreamListener(Sink.INPUT)
public void receiver(Object message){
logger.info(message.toString());
}
}
6.启动rabbitmq
7.启动项目
8.通过mq管理页面发送消息


控制台打印 表示成功接收到消息

核心概念
绑定器
应用程序与消息中间件的抽象层。应用程序中间件的解耦。应用程序不需要考虑用的什么类型的消息中间件。当我们需要更换消息中间件 只需要替换绑定器
发布订阅
spring cloud stream 完全遵循发布订阅模式 当一条消息被发布到消息中间件后 将会以topic主题模式进行广播,消费者对订阅的topic主题进行相应的逻辑处理。topic是spring cloud stream的一个抽象概念,不同消息中间件topic概念可能不同 rabbitMq对应exchage
如rabbitMQ的topic

发布订阅模式能够有效避免点对点的耦合 当一种消息要增加一种处理方式时只需要增加一个消息订阅者
消费组
一般我们的消费组都会集群部署 但是我们再集群部署的情况下 会形成多个订阅者 导致消息被消费多次, 消费组则是解决一个消息只能被一个实例消费者消费
消费分区
指定统一特征的消息被指定服务实例消费 spring cloud stream消费分区提供通用的抽象实现 使不支持分区的中间件也能支持消费分区
自定义输入和输出
定义输入
Sink 是spring cloud stream 的默认实现 我们可以通过查看源码
public interface Sink {
String INPUT = "input";
@Input("input")
SubscribableChannel input();
}
通过@Input注册参数为通道名字 同时需要返回SubscribableChannel
我们通过参考Sink定义一个输入通道 比如处理订单保存的通道
1.定义第一个通道
public interface OrderMQInputChannel {
String saveOrderChannelName="saveOrder";//定义通道的名字
@Input(saveOrderChannelName)//定义为输入通道
public SubscribableChannel saveOrder();
}
2.绑定通道并监听
//通过绑定器 对OrderMQInputChannel通道进行绑定
@EnableBinding(OrderMQInputChannel.class)
public class OrderMQReceiverService {
private static Logger logger = LoggerFactory.getLogger(SkinReceiverService.class);
//对OrderMQInputChannel.saveOrderChannelName的消息监听处理
@StreamListener(OrderMQInputChannel.saveOrderChannelName)
public void receiver(Object message){
logger.info(message.toString());
}
}
定义输出
1.创建一个测试消费提供者的项目

2.引入pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
</dependencies>
2.yml配置文件配置
spring:
application:
name: streamProvider
rabbitmq: #更多mq配置看书331页
username: liqiang
password: liqiang
host: localhost
port: 5672
server:
port: 8082
3.定义一个输出通道
public interface OrderMQOutputChannel {
String saveOrderChannelName="saveOrder";
@Output(saveOrderChannelName)//定义输出管道的名字
MessageChannel saveOrder();
}
4.绑定通道
@EnableBinding(OrderMQOutputChannel.class) //绑定通道OrderMQOutputChannel
public class OrderChannelBindConfig {
}
5.添加测试contorller
@Controller
public class TestContorller {
@Autowired
OrderMQOutputChannel orderMQOutputChannel;
@RequestMapping("/saveOrder")
@ResponseBody
public boolean saveOrder(){
//发送一条保存订单的命令
return orderMQOutputChannel.saveOrder().send(MessageBuilder.withPayload("fff").build());
}
}
或者
@Controller
public class TestContorller { //直接注入对应通道是的实例
@Autowired@Qualifier(OrderMQOutputChannel.saveOrderChannelName)
MessageChannel messageChannel; @RequestMapping("/saveOrder")
@ResponseBody
public boolean saveOrder(){
//发送一条保存订单的命令
return messageChannel.send(MessageBuilder.withPayload("fff").build());
} }
6.访问
http://127.0.0.1:8082/saveOrder

7.consumer打印 表示消息被消费

spring intergration原生支持
spring cloud stream 是通过spring boot和spring intergreation的整合 所以也可以使用原生的用法实现相同的功能
provider
@EnableBinding(OrderMQOutputChannel.class) //绑定通道OrderMQOutputChannel
public class OriginalOrderMQOutPutChannelService {
//定义2秒发送一次消息
@Bean
@InboundChannelAdapter(value = OrderMQOutputChannel.saveOrderChannelName, poller = @Poller(fixedDelay = "2000"))
public MessageSource<Date> timerMessageSource() {
return () -> new GenericMessage<>(new Date()); }
}
consumer
//通过绑定器 对OrderMQInputChannel通道进行绑定
@EnableBinding(OrderMQInputChannel.class)
public class OriginalOrderMQReceiverService {
private static Logger logger = LoggerFactory.getLogger(SkinReceiverService.class); //对OrderMQInputChannel.saveOrderChannelName的消息监听处理
@ServiceActivator(inputChannel = OrderMQInputChannel.saveOrderChannelName)
public void receiver(Object message) {
logger.info(message.toString());
} //定义消息转换器 转换saveOrderChannelName 通道的的消息
@Transformer(inputChannel = OrderMQInputChannel.saveOrderChannelName, outputChannel = OrderMQInputChannel.saveOrderChannelName)
public Object transform(Date message) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(message); }
}
启动之后conusmer2秒则会受到一条消息 更多用法查看spring intergration文档
消息转换
通过上面我们可以看到原生通过@Transformer实现消息转换 spring cloud stream 只需要定义消息通道的消息类型
spring.cloud.stream.bindings.[inputname].content-type=application/json
spring:
application:
name: streamConsumer
rabbitmq: #更多mq配置看书331页
username: liqiang
password: liqiang
host: localhost
port: 5672
cloud:
stream:
bindings:
saveOrder:
content-type: application/json
server:
port: 8081
消费者
//通过绑定器 对OrderMQInputChannel通道进行绑定
@EnableBinding(OrderMQInputChannel.class)
public class OrderMQReceiverService {
private static Logger logger = LoggerFactory.getLogger(SkinReceiverService.class);
//对OrderMQInputChannel.saveOrderChannelName的消息监听处理
@StreamListener(OrderMQInputChannel.saveOrderChannelName)
public void receiver(Order order){ logger.info(order.getId()+"-"+order.getOrderCode());
}
}
消息提供者
@Controller
public class TestContorller { //直接注入对应通道是的实例
@Autowired@Qualifier(OrderMQOutputChannel.saveOrderChannelName)
MessageChannel messageChannel; @RequestMapping("/saveOrder")
@ResponseBody
public boolean saveOrder(){
com.liqiang.entity.Order order=new com.liqiang.entity.Order();
order.setId(1L);
order.setOrderCode("201901020001");
//发送一条保存订单的命令
return messageChannel.send(MessageBuilder.withPayload(order).build());
} }

消息反馈
用于将消息交给别的应用处理 处理后再回传 或者异步请求 接收处理结果
provider
public interface OrderMQOutputChannel {
String saveOrderChannelName="saveOrder";
String saveOrderCallbackChannelName="saveOrderCallback";//定义回调通道的名字
@Output(saveOrderChannelName)//定义输出管道的名字
MessageChannel saveOrder();
@Input(saveOrderCallbackChannelName)//定义为输入通道
public SubscribableChannel saveOrderCallback();
}
@EnableBinding(OrderMQOutputChannel.class) //绑定通道OrderMQOutputChannel
public class OrderChannelBindConfig {
private static Logger logger = LoggerFactory.getLogger(OrderMQOutputChannel.class);
//对OrderMQInputChannel.saveOrderChannelName的消息监听处理
@StreamListener(OrderMQOutputChannel.saveOrderCallbackChannelName)
public void receiver(boolean boo){
logger.info(String.valueOf(boo));
}
}
consumer
public interface OrderMQInputChannel {
String saveOrderChannelName="saveOrder";//定义通道的名字
String saveOrderCallbackChannelName="saveOrderCallback";//定义回调通道的名字
@Input(saveOrderChannelName)//定义为输入通道
public SubscribableChannel saveOrder();
}
//通过绑定器 对OrderMQInputChannel通道进行绑定
@EnableBinding(OrderMQInputChannel.class)
public class OrderMQReceiverService {
private static Logger logger = LoggerFactory.getLogger(SkinReceiverService.class);
//对OrderMQInputChannel.saveOrderChannelName的消息监听处理
@StreamListener(OrderMQInputChannel.saveOrderChannelName)
@SendTo(OrderMQInputChannel.saveOrderCallbackChannelName)//反馈的通道名字
public boolean receiver(Order order){ logger.info(order.getId()+"-"+order.getOrderCode());
return true;
}
}
消息分组
多实例情况下 只需要指定spring.cloud.stream.bindings.[channelname].group=gorupname 当同一组实例对同一个主题的消息只能会有一个实例消费
1.测试 创建2个配置文件 分别为
application-peer1.yml
spring:
application:
name: streamConsumer
rabbitmq: #更多mq配置看书331页
username: liqiang
password: liqiang
host: localhost
port: 5672
cloud:
stream:
bindings:
saveOrder:
group: groupA
content-type: application/json
server:
port: 8081
application-peer2.yml
spring:
application:
name: streamConsumer
rabbitmq: #更多mq配置看书331页
username: liqiang
password: liqiang
host: localhost
port: 5672
cloud:
stream:
bindings:
saveOrder:
group: groupA
content-type: application/json
server:
port: 8083
通过启动2个消费者
java -jar /Users/liqiang/Desktop/java开发环境/javadom/spring-cloud-parent/spring-cloud-stream-consumer/target/spring-cloud-stream-consumer-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar /Users/liqiang/Desktop/java开发环境/javadom/spring-cloud-parent/spring-cloud-stream-consumer/target/spring-cloud-stream-consumer-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2

只要一个实例消费了
消费分区
再某些场景 我需要指定某一类消息只能被哪些实例消费
消费者
application-peer1.yml
spring:
application:
name: streamConsumer
rabbitmq: #更多mq配置看书331页
username: liqiang
password: liqiang
host: localhost
port: 5672
cloud:
stream:
instanceCount: 2 #跟分区一起使用 有多少实例
instanceIndex: 0 #分区当前实例编号 从0开始
bindings:
saveOrder:
group: streamConsumer
content-type: application/json
consumer:
partitioned: true #开启消息分区的功能 server:
port: 8081
application-peer2.yml
spring:
application:
name: streamConsumer
rabbitmq: #更多mq配置看书331页
username: liqiang
password: liqiang
host: localhost
port: 5672
cloud:
stream:
instanceCount: 2 #跟分区一起使用 有多少实例
instanceIndex: 1 #当前实例编号 从0开始
bindings:
saveOrder:
group: streamConsumer
content-type: application/json
consumer:
partitioned: true #开启消息分区的功能 server:
port: 8083
生产者
spring:
application:
name: streamProvider
rabbitmq: #更多mq配置看书331页
username: liqiang
password: liqiang
host: localhost
port: 5672
cloud:
stream:
bindings:
saveOrder:
producer:
partitionKeyExpression: '0' #表示只有实例索引为0的才能收到消息 支持SpEL表达式
partitionCount: 2
server:
port: 8082
当启动2个消费者 和生产者 当前生产者 生产的消息只能被实例编号为0的消费
这里限制死了当前实例生产的消息被某个实例消费。如果我们需要指定 当前生产者生产的某一类服务被指定实例消费呢可以通过SpEL表达式设置
生产者yml
spring:
application:
name: streamProvider
rabbitmq: #更多mq配置看书331页
username: liqiang
password: liqiang
host: localhost
port: 5672
cloud:
stream:
bindings:
saveOrder:
producer:
partitionKeyExpression: headers['partitionKey'] #SpEL表达式 通过读取消息hearder的partitionKey属性动态指定
partitionCount: 2 #消息分区数量
server:
port: 8082
消息生产通过header动态指定
package com.liqiang.springcloudstreamprovider; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.annotation.Order;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; @Controller
public class TestContorller { //直接注入对应通道是的实例
@Autowired@Qualifier(OrderMQOutputChannel.saveOrderChannelName)
MessageChannel messageChannel;
private static int index=0; @RequestMapping("/saveOrder")
@ResponseBody
public boolean saveOrder(){
com.liqiang.entity.Order order=new com.liqiang.entity.Order();
order.setId(1L);
order.setOrderCode("201901020001"); //发送一条保存订单的命令
return messageChannel.send(MessageBuilder.withPayload(order).setHeader("partitionKey",(index++)%2==0?0:1).build());
} }
Spring Cloud Stream(十三)的更多相关文章
- Spring Cloud Stream同一通道根据消息内容分发不同的消费逻辑
应用场景 有的时候,我们对于同一通道中的消息处理,会通过判断头信息或者消息内容来做一些差异化处理,比如:可能在消息头信息中带入消息版本号,然后通过if判断来执行不同的处理逻辑,其代码结构可能是这样的: ...
- Spring Cloud Stream消费失败后的处理策略(四):重新入队(RabbitMQ)
应用场景 之前我们已经通过<Spring Cloud Stream消费失败后的处理策略(一):自动重试>一文介绍了Spring Cloud Stream默认的消息重试功能.本文将介绍Rab ...
- Spring Cloud Stream消费失败后的处理策略(三):使用DLQ队列(RabbitMQ)
应用场景 前两天我们已经介绍了两种Spring Cloud Stream对消息失败的处理策略: 自动重试:对于一些因环境原因(如:网络抖动等不稳定因素)引发的问题可以起到比较好的作用,提高消息处理的成 ...
- Spring Cloud Stream消费失败后的处理策略(二):自定义错误处理逻辑
应用场景 上一篇<Spring Cloud Stream消费失败后的处理策略(一):自动重试>介绍了默认就会生效的消息重试功能.对于一些因环境原因.网络抖动等不稳定因素引发的问题可以起到比 ...
- Spring Cloud Stream消费失败后的处理策略(一):自动重试
之前写了几篇关于Spring Cloud Stream使用中的常见问题,比如: 如何处理消息重复消费 如何消费自己生产的消息 下面几天就集中来详细聊聊,当消息消费失败之后该如何处理的几种方式.不过不论 ...
- Spring Cloud Stream如何消费自己生产的消息?
在上一篇<Spring Cloud Stream如何处理消息重复消费>中,我们通过消费组的配置解决了多实例部署情况下消息重复消费这一入门时的常见问题.本文将继续说说在另外一个被经常问到的问 ...
- Spring Cloud Stream如何处理消息重复消费?
最近收到好几个类似的问题:使用Spring Cloud Stream操作RabbitMQ或Kafka的时候,出现消息重复消费的问题.通过沟通与排查下来主要还是用户对消费组的认识不够.其实,在之前的博文 ...
- 使用 Spring Cloud Stream 构建消息驱动微服务
相关源码: spring cloud demo 微服务的目的: 松耦合 事件驱动的优势:高度解耦 Spring Cloud Stream 的几个概念 Spring Cloud Stream is a ...
- Spring Cloud Stream
Spring Cloud Stream是Spring Cloud的组件之一,是一个为微服务应用构建消息驱动能力的框架. 1.导入引用 <dependency> <groupId> ...
随机推荐
- 《解读window核心编程》 之 注冊表
1 注冊表的作用及组织形式 Windows系统使用注冊表来存储系统和应用程序配置数据.非常多系统和应用程序重要的配置的信息都存储在注冊表中. 注冊表是一种以树型结构组织的数据库.树的每个节点称 作键( ...
- 在ubuntu中安装photoshop cs6
对于很多专业的PS高手来说,真心难以找到顺手的且可以完美替代PS的软件,所以我们这里的解决办法就是用wine来安装. 虽然网上有很多的wine安装ps的方法,但是在使用过程往住会发生莫名其妙的崩溃,体 ...
- [BZOJ1601] 灌水
难点:找到正确方式建图 知识点:Kruskal 分析:这种题肯定要把点权转换到边权上,但肯定无法搞到和其他点相连的边上,怎么办呢?那就再造一个点呗,这个“超级点”和所有点相连,且边权=点权,于是就可以 ...
- POJ 2823 线段树 Or 单调队列
时限12s! 所以我用了线段树的黑暗做法,其实正解是用单调队列来做的. //By SiriusRen #include <cstdio> #include <cstring> ...
- 自己写的_top、_parent以及对iframe和frameset的理解
iframe可以写在<body>标签里,如<body><iframe src="xxx" name="xxx" style=&q ...
- 初学jQuery之jQuery虚假购物车-------与真实数据无关
初学者用jquery来写仿真的购物车,确实有点恶心,那我们今天就把这万恶的购物车剖析一下,来看看到底有什么难的. 购物车的效果图 那我们先从复选框开始吧,废话不多说,上代码!! 带有序号的,都是一些分 ...
- Ajax 传递json字符串到客户端时报 Internal server error
架构:struts2+JQuery 需求:就是前台请求后台,后台查询数据库,将数据转换成json格式,使用struts2框架赋值给action内的变量jsonStr,前台通过 response.jso ...
- Android 4.0 Launcher2源码分析——主布局文件(转)
本文来自http://blog.csdn.net/chenshaoyang0011 Android系统的一大特色是它拥有的桌面通知系统,不同于IOS的桌面管理,Android有一个桌面系统用于管理和展 ...
- mysqlslap: Error when connecting to server: 2001 Can't create UNIX socket (24) 解决方法
在用mysqlslap对mysql进行压力测试遇到mysqlslap: Error when connecting to server: 2001 Can't create UNIX socket ( ...
- Quartz+Topshelf 作业
小记: 引用Quartz.Topshelf.Topshelf.Quartz 使用方法: http://www.cnblogs.com/mushroom/p/4952461.html http://ww ...