1.几个重要概念
Destination Binders:目标绑定器,目标指的是 kafka 还是 RabbitMQ,绑定器就是封装了目标中间件的包。如果操作的是 kafka 就使用 kafka binder ,如果操作的是 RabbitMQ 就使用 rabbitmq binder。
Destination Bindings:外部消息传递系统和应用程序之间的桥梁,提供消息的“生产者”和“消费者”(由目标绑定器创建)
Message:一种规范化的数据结构,生产者和消费者基于这个数据结构通过外部消息系统与目标绑定器和其他应用程序通信。
2.举个例子
用到的是 rabbitmq,所以先在本地搭好 rabbitmq 环境,这样就可以访问 web UI 界面了,默认是 15672 端口。
2.1引入所需要的jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
2.2在 application.yml 中增加配置
spring:
profiles: stream-rabbit-customer-group1
cloud:
stream:
bindings: #对应上面提到到 「Destination Bindings」, 这里面可以配置多个 input 或者 output,分别表示消息的接收通道和发送通道
input: # @Input("input")代码中会用到
destination: default.messages #每个通道下的 destination 属性指 exchange 的名称
binder: local_rabbit #binder 指定在 binders 里设置的 binder,上面配置中指定了 local_rabbit
output:
destination: default.messages
binder: local_rabbit
binders: #对应上面提到到「Destination binders」
local_rabbit: #可以设置多了个 binder,适配不同的场景。
type: rabbit #指定type为rabbit,表示使用的是rabbitmq消息中间件,如果用的是 kafka ,则 type 设置为 kafka
environment: #environment 就是设置使用的消息中间件的配置信息,包括 host、port、用户名、密码等。
spring:
rabbitmq:
host: localhost
port: 32775
username: guest
password: guest
server:
port: 8201
spring.cloud.stream.bindings , 另外还可以设置 group 。因为服务很可能不止一个实例,如果启动多个实例,那么没必要每个实例都消费同一个消息,只要把功能相同的实例的 group 设置为同一个,那么就会只有一个实例来消费消息,避免重复消费的情况。如果设置了 group,那么 group 名称就会成为 queue 的名称,如果没有设置 group ,那么 queue 就会根据 destination + 随机字符串的方式命名。
2.3一个简单实例
1.首先来介绍一下 stream 内置的简单消息通道(消息通道也就是指消息的来源和去向)接口定义,一个 Source 和 一个 Sink 。
Source.java
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
public interface Source {
String OUTPUT = "output";
@Output("output")
MessageChannel output();
}
消息发送通道定义,定义了一个 MessageChannel 类型的 output() 方法,用 @Output 注解标示,并指定了 binding 的名称为 output。
Sink.java
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
public interface Sink {
String INPUT = "input";
@Input("input")
SubscribableChannel input();
}
消息接收通道定义,定义了一个 SubscribableChannel 类型的 input() 方法,表示订阅一个消息的方法,并用 @Input 注解标识,并且指定了 binging 的名称为 input 。
2.创建一个简单的消息接收方法:
@Slf4j
@Component
@EnableBinding(value = {Processor.class}) //表明启用 stream ,并指定定义的 Channel 定义接口类
public class DefaultMessageListener {
@StreamListener(Processor.INPUT) //表示对消息进行订阅监控,指定 binding 的名称,其中 Processor.INPUT 就是 Sink 的 input ,也就是字符串 input ,对应的上面的配置文件,就是 spring.cloud.stream.bindings.input。
public void processMyMessage(String message) {
log.info("接收到消息:" + message);
}
}
3.可以通过控制台进行发送消息测试
3.模拟一个日志处理
接下来模拟生产者和消费者处理消息的过程,模拟一个日志处理的过程。
原始日志发送到 kite.log.messages exchange
接收器在 kite.log.messages exchange 接收原始日志,经过处理格式化,发送到 kite.log.format.messages exchange
接收器在 kite.log.format.messages exchange 接收格式化后的日志
3.1、自定义消息通道接口,上面介绍了 stream 自带的 Sink 和 Source,也仅仅能做个演示,真正的业务中还是需要自己定义更加灵活的接口。
@Component
public interface MyProcessor {
String MESSAGE_INPUT = "log_input";
String MESSAGE_OUTPUT = "log_output";
String LOG_FORMAT_INPUT = "log_format_input";
String LOG_FORMAT_OUTPUT = "log_format_output";
@Input(MESSAGE_INPUT)
SubscribableChannel logInput();
@Output(MESSAGE_OUTPUT)
MessageChannel logOutput();
@Input(LOG_FORMAT_INPUT)
SubscribableChannel logFormatInput();
@Output(LOG_FORMAT_OUTPUT)
MessageChannel logFormatOutput();
}
3.2、创建消费者应用
配置文件
spring:
profiles: stream-rabbit-customer-group1
cloud:
stream:
bindings:
log_input:
destination: kite.log.messages
binder: local_rabbit
group: logConsumer-group1
log_output:
destination: kite.log.messages
binder: local_rabbit
group: logConsumer-group1
log_format_input:
destination: kite.log.format.messages
binder: local_rabbit
group: logFormat-group1
log_format_input:
destination: kite.log.format.messages
binder: local_rabbit
group: logFormat-group1
binders:
local_rabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: localhost
port: 32775
username: guest
password: guest
server:
port: 8201
此配置文件要参照 MyProcessor 接口查看,定义了 4 个 binding,但是 destination 两两相同,也就是两个 exchange。
消息消费者
@Slf4j
@Component
@EnableBinding(value = {MyProcessor.class})
public class LogMessageListener {
/**
* 通过 MyProcessor.MESSAGE_INPUT 接收消息
* 然后通过 SendTo 将处理后的消息发送到 MyProcessor.LOG_FORMAT_OUTPUT
* @param message
* @return
*/
@StreamListener(MyProcessor.MESSAGE_INPUT)
@SendTo(MyProcessor.LOG_FORMAT_OUTPUT)
public String processLogMessage(String message) {
log.info("接收到原始消息:" + message);
return "「" + message +"」";
}
/**
* 接收来自 MyProcessor.LOG_FORMAT_INPUT 的消息
* 也就是加工后的消息,也就是通过上面的 SendTo 发送来的
* 因为 MyProcessor.LOG_FORMAT_OUTPUT 和 MyProcessor.LOG_FORMAT_INPUT 是指向同一 exchange
* @param message
*/
@StreamListener(MyProcessor.LOG_FORMAT_INPUT)
public void processFormatLogMessage(String message) {
log.info("接收到格式化后的消息:" + message);
}
}
3.3创建一个消息生产者,用于发送原始日志消息
配置文件
spring:
cloud:
stream:
bindings:
log_output:
destination: kite.log.messages
binder: local_rabbit
group: logConsumer-group1
binders:
local_rabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: localhost
port: 32775
username: guest
password: guest
server:
port: 8202
仅仅指定了一个 binding log_output,用来发送消息,如果只做生产者就不要指定 log_input,如果指定了 log_input ,应用就会认为这个生产者服务也会消费消息,如果这时没有在此服务中订阅消息,当消息被发送到这个服务时,因为并没有订阅消息,也就是没有 @StreamListener 注解的方法,就会出现如下异常:
org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel
消息生产者
@Slf4j
@RestController
@EnableBinding(value = {MyProcessor.class})
public class MyMessageController {
@Autowired
private MyProcessor myProcessor;
@GetMapping(value = "sendLogMessage")
public void sendLogMessage(String message){
Message<String> stringMessage = org.springframework.messaging.support.MessageBuilder.withPayload(message).build();
myProcessor.logOutput().send(stringMessage);
}
}
其他参考链接
https://www.cnblogs.com/fengzheng/p/11576661.html
https://www.cnblogs.com/hellxz/p/9396282.html
https://www.jianshu.com/p/fb7d11c7f798
https://www.jianshu.com/p/bf992c23c381
https://www.imooc.com/article/290489