前言

在使用消息队列组件时,如果程序需要更换消息队列组件,那么代码内部可能需要做巨大的修改,而Spring Cloud Stream则解决了这个问题,应用程序只需要通过 inputs 或者 outputs 来与 Spring Cloud Stream 中binder 交互,通过我们配置来 binding ,而 Spring Cloud Stream 的 binder 负责与消息中间件交互。所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。

生产者

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
cloud:
stream:
bindings:
output: #output为默认“管道”,也可以设置为自己定义的,但是需要与自己定义的类相对应,例如自定义为myoutput
destination: testStreamExchange
binder: local_rabbit
binders:
local_rabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest

消息发送类

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@EnableBinding(Processor.class)
public class MessageController {

@Autowired
private Processor processor;

@GetMapping("/sendMessage")
public void sendMessage(String message){
processor.output().send(MessageBuilder.withPayload(message).build());
}

}

注:这里的Processor继承了Source与Sink,可以参考Processor的结构定义自己的配置类,与配置文件中自定义的output(myoutput)对应

消费者

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
spring:
cloud:
stream:
bindings:
input:
destination: testStreamExchange #要和output的destination一致, 这样才能将队列和写入消息的exchange关联起来
binder: local_rabbit
group: testStreamQueue
binders:
local_rabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
rabbit:
bindings:
input:
consumer:
requeue-rejected: false # 是否支持重新放回队列
acknowledge-mode: MANUAL # 接收模式是手工接收
recovery-interval: 3000 # 服务断开,3秒钟重连
durable-subscription: true # 是否启用持久化订阅
max-concurrency: 5 # 最大的监听数

监听类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
@EnableBinding(Processor.class)
public class Listener {

private Logger log = LoggerFactory.getLogger(Listener.class);

@StreamListener(Processor.INPUT)
public void process(Message message){
log.info("{}: message is {}", this.getClass().getSimpleName(), message);
MessageHeaders headers = message.getHeaders();
Channel channel = (Channel) headers.get(AmqpHeaders.CHANNEL);
Long deliveryTag = (Long) message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
try {
assert channel != null;
channel.basicAck(deliveryTag, false);
} catch (IOException e) {
e.printStackTrace();
}
}

}

注:这里的Processor继承了Source与Sink,可以参考Processor的结构定义自己的配置类,与配置文件中自定义的input(myinput)对应\

关于Channel

如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和message broker识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销
我们在消费这边编写两个监听器监听同一个队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Component
@EnableBinding(Processor.class)
public class Listener {

private Logger log = LoggerFactory.getLogger(Listener.class);

private Channel channel = null;

@StreamListener(Processor.INPUT)
public void process(Message message){
deal(message);
}

@StreamListener(Processor.INPUT)
public void processTwo(Message message){
deal(message);
}

private void deal(Message message) {
log.info("{}: message is {}", this.getClass().getSimpleName(), message);
MessageHeaders headers = message.getHeaders();
Channel channel = (Channel) headers.get(AmqpHeaders.CHANNEL);
if (this.channel == null){
this.channel = channel;
}else{
log.info("{}", channel == this.channel);
}
Long deliveryTag = (Long) message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
try {
assert channel != null;
channel.basicAck(deliveryTag, false);
} catch (IOException e) {
e.printStackTrace();
}
}

}

生产者发送消息(多次)

可以看到发送一次消息两个监听器同时被触发,与纯rabbitmq不同(纯rabbitmq是轮询),并且第一次判断为true,证明一个接收通道(input)只使用同一个channel,监听同一个接收通道与监听同一个队列是不同的概念。而且可以发送当两(多)个监听器监听同一个接收通道时,会导致线程死掉,重新生成channel。


本站总访问量:次本站总访问数:人
本站由 Peng Tong 使用 Stellar 1.29.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。