Spring Reactive Web Webflux 整合 RabbitMQ

34 篇文章 0 订阅
8 篇文章 0 订阅

引言

在使用spring-web 的 websocket 时
我们可以在@RabbitListener或CloudStream @StreamListener中直接使用messagingTemplate.convertAndSend或@SendTo 广播消息。只需要一个消费者监听就可以,不管有没有客户端连接。

在webflux中如何使用mq进行消息推送呢?
首先需要知道的是,当用户请求发生时,我们才创建队列,因为这和websocket不同,我们是被拉取方,我们无法主动发送消息,SSE协议本身就是如此。
因此我们需要针对每个用户创建一个单独的队列,否则就成轮询或公平分发了。

下面不写废话,不写原理,不复制粘贴源码美其名曰分析,直接上例子。

例子(广播消息)

pom.xml

 	    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

Configuration

创建扇形交换机和RabbitAdmin,这里不创建绑定,因为用户请求时,才会创建队列和绑定,然后监听消息。
CachingConnectionFactory 是amqp的autoconfiguration自动配置的,写接口名也可以,因为代码中没有import具体包名,怕你们导入错,所以使用的CachingConnectionFactory
创建扇形交换机的原因就是为了广播,fanout不关心路由键,和此交换机绑定的所有队列都可以收到消息,有些人喜欢topic,用topic也行。

@Configuration
public class RabbitMqConfig {
    @Bean
    Exchange fanoutExchange(){
        return ExchangeBuilder.fanoutExchange("fanoutExchange").durable(true).build();
    }
    
    
    @Bean
    public RabbitAdmin rabbitAdmin(CachingConnectionFactory connectionFactory){
        return new RabbitAdmin(connectionFactory);
    }
}

写一个监听容器工厂

功能就是当用户请求时,使用工厂创建队列和进行绑定,并返回监听容器。
写了2个create,一个是可以指定队列名,一个是随机队列名,如果需要根据用户名进行队列创建可以使用第一个。
容器中的队列可以多个,有需要可以自己改一改
注意:创建容器时,我只放入了队列,并没有设置Listener,设置Listener是在用户请求创建时才会设置。因为需要在Listner代码块中使用Flux的sink去推送消息,所以无法提前创建。

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MessageListenerContainerFactory {
    @Autowired
    private CachingConnectionFactory connectionFactory;
    @Autowired
    private RabbitAdmin rabbitAdmin;
    @Autowired
    private Exchange fanoutExchange;

    public SimpleMessageListenerContainer create(String queueName) {
        Queue queue = QueueBuilder.nonDurable(queueName).maxLength(10000).autoDelete().exclusive().build();
        rabbitAdmin.declareQueue(queue);
        rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(fanoutExchange).with("").noargs());

        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
        //在容器中放入刚创建好的队列
        simpleMessageListenerContainer.setQueueNames(queue.getName());
        simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
        /*
        //设置当前的消费者数量
        simpleMessageListenerContainer.setConcurrentConsumers(1);
        simpleMessageListenerContainer.setMaxConcurrentConsumers(1);
        //设置消息是否重回队列
        simpleMessageListenerContainer.setDefaultRequeueRejected(false);
        //设置自动确认消息simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
        //设置暴露监听器通道
        simpleMessageListenerContainer.setExposeListenerChannel(true);
        */

        return simpleMessageListenerContainer;
    }
    public SimpleMessageListenerContainer create(){
        Queue queue = QueueBuilder.nonDurable().maxLength(10000).autoDelete().exclusive().build();
        rabbitAdmin.declareQueue(queue);
        rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(fanoutExchange).with("").noargs());

        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
        //在容器中放入刚创建好的队列
        simpleMessageListenerContainer.setQueueNames(queue.getName());
        simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
       
        return simpleMessageListenerContainer;
    }
}

在Controller中使用

	//我们自己写的工厂
 	@Autowired
    MessageListenerContainerFactory containerFactory;

    @RequestMapping(value = "/test",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @ResponseBody
    public Flux<String> test() {
    	//用自己写的工厂创建一个监听容器
        SimpleMessageListenerContainer container = containerFactory.create();

        return Flux.create(sink->{
        	//容器中设置监听器用于接收到消息后使用sink发送给客户端
            container.setupMessageListener((ChannelAwareMessageListener)(Message message, Channel channel)->{
                if (sink.isCancelled()) {
                    container.stop();
                    return;
                }
                String msg = new String(message.getBody());
                sink.next(msg);
            });
            
            //启动容器和停止容器
            sink.onRequest(r -> container.start());
            sink.onDispose(container::stop);
        });

    }

使用rabbitmqredis的区别是,队列名无法固定,必须一个请求对应一个队列,否则消息会成轮询或公平分发(手动ack),因此在监听容器中,队列名要么根据用户名创建,要么不写队列名会随机生成

思考

如果基于SSE协议,难道只能每个请求创建一个队列吗?10w客户端创建10w个队列?有没有办法优化呢?

首先,上面提到过,如果相同队列会产生轮询或公平分发,但是针对每个请求创建一个队列太过浪费,事实上我们可以这么做:

  1. 每个服务创建一个队列,这样达到广播的目的
  2. 在这个队列监听者中,我们进行消息消费,将消息转发到我们自己写被观察者类中。
  3. 客户端请求,我们添加为观察者,如果被观察者有变化,推送消息。

这样我们就可以做到每个服务一个队列,所有消费我们自己通知。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没事干写博客玩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值