动手实现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. 推磨式下怎么保证消息不丢失呢?

相关推荐
全栈派森6 分钟前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse11 分钟前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭1 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架1 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱1 小时前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
☞无能盖世♛逞何英雄☜2 小时前
Flask框架搭建
后端·python·flask
进击的雷神2 小时前
Perl语言深度考查:从文本处理到正则表达式的全面掌握
开发语言·后端·scala
进击的雷神2 小时前
Perl测试起步:从零到精通的完整指南
开发语言·后端·scala
豌豆花下猫3 小时前
Python 潮流周刊#102:微软裁员 Faster CPython 团队(摘要)
后端·python·ai
秋野酱3 小时前
基于javaweb的SpringBoot驾校预约学习系统设计与实现(源码+文档+部署讲解)
spring boot·后端·学习