动手实现RabbitMq消息队列拉模式

前言

Rabbitmq拉模式是什么?为什么有推模式还要拉模式?怎么在spring中实现批量拉消息消费呢?本文针对这些问题来动手实现一个Rabbitmq拉模式,消费者主动请求服务端接口来批量消费消息。

Rabbitmq的消费模式有两种:

  1. 推模式
  2. 拉模式

在spring中,加入封装好的amqp:

xml 复制代码
<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>

只需要在消费者方法上加上@RabbitListener(queues = XXXX)就能实现消费者监听订阅的主题;比如如下,监听我们订阅的队列,当有消息的时候,消费者执行发送邮件方法

java 复制代码
@RabbitListener(queues = RabbitMQConfig.QUEUE_NAME)  
public void receiveMessage(String message) {  
    log.info("开始发送邮件信息:" + message);   
    mailService.sendMail(message);  
}

探究原理

什么是拉模式?

先了解一下推模式:消息中间件主动将消息推送给消费者;可以结合观察者模式来理解,前提是消费者订阅(注册)了需要的主题,当生产者在该主题内生产了新的消息,那么服务端就会主动推送新的消息到消费者端,这样就实现了消息队列的推模式。由于推模式是信息到达RabbitMQ后,就会立即被投递给匹配的消费者,所以实时性非常好,消费者能及时得到最新的消息。

拉模式:消费者主动从消息中间件拉取消息,相较于推模式的区别,其实就跟pull和push一样,是消费者主动去服务端拉取消息消费,是主动的;由于拉模式需要消费者手动去RabbitMQ中拉取消息,所以实时性较差;消费者难以获取实时消息,具体什么时候能拿到新消息完全取决于消费者什么时候去拉取消息。

为什么要拉模式?

既然拉模式的的实时性不好,为什么还要用呢?因为在某些场景下,由于某些限制,消费者只有在某些条件成立下才能去消息中间件中批量获取消息;而且相较于推模式关注于消息的实时性,拉模式更加关注消费者的消费能力,所以是因为场景不同,我们需要拉模式。

怎么实现批量拉消息消费呢?

引入spring实现的amqp包,其中RabbitTemplate提供了获取单条消息的方法,我们可以自己实现批量消费。

动手实现

配置

java 复制代码
@Configuration
public class RabbitConfig {
    public static final String TOPIC_EXCHANGE_NAME = "topic.exchange";
    public static final String TOPIC_QUEUE_A = "topic.queue.a";
    public static final String TOPIC_QUEUE_B = "topic.queue.b";

    @Resource
    private RabbitAdmin rabbitAdmin;
    @Bean
    public RabbitTemplate rabbitTemplate() {
        return rabbitAdmin.getRabbitTemplate();
    }

    public ConnectionFactory getConnectFactory() {
        com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
        connectionFactory.setHost("XXXX");
        connectionFactory.setPort(xXXX);
        connectionFactory.setUsername("XXXX");
        connectionFactory.setPassword("XXXX");
        connectionFactory.setVirtualHost("XXXX");
        return new CachingConnectionFactory(connectionFactory);
    }

    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @Bean
    public RabbitAdmin  rabbitAdmin() {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(getConnectFactory());
        // 只有设置为 true,spring 才会加载 RabbitAdmin 这个类
        rabbitAdmin.setAutoStartup(true);
        rabbitAdmin.declareExchange(rabbitTopicExchange());
        rabbitAdmin.declareQueue(topicQueue(TOPIC_QUEUE_A));
        rabbitAdmin.declareQueue(topicQueue(TOPIC_QUEUE_B));
        return rabbitAdmin;
    }

    public TopicExchange rabbitTopicExchange() {
        return new TopicExchange(TOPIC_EXCHANGE_NAME, true, false);
    }

    public Queue topicQueue(String queueName) {
        return new Queue(queueName, true, false, false);
    }


    @Bean
    public Binding bindTopicExchangeToMail() {
        return BindingBuilder.bind(topicQueue(TOPIC_QUEUE_A))
                .to(rabbitTopicExchange())
                .with("XXXX");
    }

    @Bean
    public Binding bindTopicExchangeToSms() {
        return BindingBuilder.bind(topicQueue(TOPIC_QUEUE_B))
                .to(rabbitTopicExchange())
                .with("XXXX");
    }
}

实现类

RabbitMQService

java 复制代码
public interface RabbitMQService {
    String consumeOneMessage(String queueName);
    List<String> consumeBatchMessage(String queueName, int batchSize);
}

RabbitMQServiceImpl

java 复制代码
@Service
public class RabbitMQServiceImpl implements RabbitMQService {
    @Resource
    private RabbitTemplate rabbitTemplate;

    // 消费单条消息
    @Override
    public String consumeOneMessage(String queueName) {
        Object o = rabbitTemplate.receiveAndConvert(queueName);
        return (String) o;
    }

    // 批量消费消息
    @Override
    public List<String> consumeBatchMessage(String queueName, int batchSize) {
        List<String> result = new ArrayList<>();
        for (int i = 0; i < batchSize; i ++) {
            String message = (String) rabbitTemplate.receiveAndConvert(queueName);
            if (message == null) {
                break;
//            取200条消息,大概25ms continue;
            }
            result.add(message);
        }
        return result;
    }
}

接口

java 复制代码
@RestController
@RequestMapping("/rabbitmq")
public class RabbitMQController {
    @Resource
    private RabbitMQService rabbitMQService;

    @GetMapping("/batchConsume")
    public List<String> consumeBatch(@RequestParam(name = "queueName") String queueName, @RequestParam(name = "batchSize") int batchSize) {
        return rabbitMQService.consumeBatchMessage(queueName, batchSize);
    }

    @GetMapping("/singleConsume")
    public String consumeSingle(@RequestParam(name = "queueName") String queueName) {
        return rabbitMQService.consumeOneMessage(queueName);
    }

问题

  1. 定时任务不生效的问题;当我们的配置文件实现了BeanPostProcessor后,定时任务不生效?

  2. RabbitTemplate实现获取单条消息的原理呢?ack机制是什么?

  3. 推磨式下怎么保证消息不丢失呢?

相关推荐
向前看-3 小时前
验证码机制
前端·后端
超爱吃士力架5 小时前
邀请逻辑
java·linux·后端
AskHarries7 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion8 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp8 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder9 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚9 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes
机器之心10 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴11 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲11 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端