Spring Cloud Stream基本使用
Spring Cloud Stream基本使用
Spring Cloud Stream是一个构建消息驱动微服务的框架,可以屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
应用程序通过inputs或者outputs来与Spring Cloud Stream中binder对象交互,通过我们配置来binding,而Spring Cloud Stream的binder对象负责与消息中间件交互,所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式,通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动
比方说我们用到了RabbitMQ和Kafka,这些中间件的差异性给我们实际项目开发造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我们想往另外一种消息队列进行迁移,这时候无疑是一个灾难性的,一大堆东西都要推倒重新做,因为它跟我们的系统耦合了,这时候Spring Cloud Stream给我们提供了一种解耦方式
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互时,由于各消息中间件实现细节上有较大的差异性,通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离,通过向应用程序暴露统一的Channel通道,使用应用程序不需要再考虑各种不同的消息中间件实现(通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离)
Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费者、分区三个核心概念,目前仅支持RabbitMQ、Kafka
代码实战
生产者
导入依赖
<dependencies>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置文件
server:
port: 8801
spring:
application:
name: stream-rabbitmq-provider
cloud:
stream:
binders:
# 配置要绑定的rabbitmq的服务信息
defaultRabbit:
# 消息组件类型
type: rabbit
# 配置rabbitmq的相关环境
environment:
spring:
rabbitmq:
host: 192.168.84.136
port: 5672
username: admin
password: 123
# 服务的整合处理
bindings:
# 这个名字是一个通道的名称
output:
# 要使用的Exchange名称
destination: streamExchange
# 设置消息类型为json,文本则设置“text/plain”
content-type: application/json
# 设置要绑定的消息服务的具体设置
defaultbinder: defaultRabbit
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2
lease-expiration-duration-in-seconds: 5
instance-id: stream-rabbit-provider-8801
prefer-ip-address: true
发送消息接口
package com.yl.stream.rabbitmq.provider.service;
/**
* 消息提供者
*
* @auther Y-wee
*/
public interface IMessageProvider {
/**
* 发送消息
*
* @return
*/
String send();
}
发送消息接口实现类
package com.yl.stream.rabbitmq.provider.service.impl;
import com.yl.stream.rabbitmq.provider.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.support.MessageBuilder;
import javax.annotation.Resource;
import org.springframework.cloud.stream.messaging.Source;
import java.util.UUID;
/**
* 消息提供者
*
* @auther Y-wee
*/
// 定义消息的推送管道,将信道和交换机绑定
@EnableBinding(Source.class)
public class MessageProviderImpl implements IMessageProvider {
/**
* 消息发送管道
*/
@Resource
private MessageChannel output;
/**
* 发送消息
*
* @return
*/
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("serial: " + serial);
return serial;
}
}
controller
package com.yl.stream.rabbitmq.provider.controller;
import com.yl.stream.rabbitmq.provider.service.IMessageProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 消息提供者
*
* @auther Y-wee
*/
@RestController
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
/**
* 发送消息
*
* @return 消息
*/
@GetMapping(value = "/sendMessage")
public String sendMessage() {
return messageProvider.send();
}
}
启动生产者,发送请求通过stream操作rabbitmq发送消息:http://localhost:8801/sendMessage
注意:启动可能会报拒绝连接异常,但是服务正常,依然可以发送消息,如果不想看到此报错,需要在配置文件单独配置rabbitmq:
spring:
rabbitmq:
host: 192.168.84.136
port: 5672
username: admin
password: 123
消费者
导入依赖:所需依赖跟生产者一样
配置文件
server:
port: 8802
spring:
application:
name: stream-rabbitmq-consumer
cloud:
stream:
binders:
defaultRabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: 192.168.84.136
port: 5672
username: admin
password: 123
bindings:
input:
destination: streamExchange
content-type: application/json
defaultbinder: defaultRabbit
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2
lease-expiration-duration-in-seconds: 5
instance-id: stream-rabbitmq-consumer-8802
prefer-ip-address: true
消费消息
package com.yl.stream.rabbitmq.consumer.controller;
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;
import javax.annotation.Resource;
/**
* 消息消费接口
*
* @auther Y-wee
*/
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
/**
* 消费消息
* 注解@StreamListener:监听队列,用于消费者队列的消息接收
*
* @param message 消息
*/
@StreamListener(Sink.INPUT)
public void input(Message<String> message) {
System.out.println("消费者1号,接受到的消息:" + message.getPayload() + "\tport:" + serverPort);
}
}
消息重复消费问题
上面配置了一个消费者和一个生产者,如果在此基础上再增加一个一样的消费者来消费消息,则会产生一个问题:生产者发送一个消息被两个消费者消费了,这种情况在实际开发中是不被允许的
重复消费原因:在spring cloud stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次,不同组是可以重复消费的(上面两个消费者默认是在两个不同组)
其实如果仔细查看rabbitmq的交换机,就会发现该交换机类型是topic,路由是#,此配置相当于是发布订阅模式(广播)了,所以消息会被重复消费
解决方法:把两个消费者配置在同一个组,组名可以自定义,组配置如下:
spring:
cloud:
stream:
bindings:
input:
group: groupA
配置分组后队列持久化了,如果没有配置分组则队列不是持久化的,消费者服务关闭后队列丢失,会造成消息也丢失
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2021-03-20 redis缓存穿透、击穿和雪崩
2021-03-20 redis哨兵模式
2021-03-20 redis主从复制配置
2021-03-20 redis发布订阅
2021-03-20 redis实现乐观锁
2021-03-20 redis事务的基本操作
2021-03-20 springboot整合redis