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

相关推荐
随心Coding9 分钟前
【零基础入门Go语言】错误处理:如何更优雅地处理程序异常和错误
开发语言·后端·golang
m0_7482345210 分钟前
【Spring Boot】Spring AOP动态代理,以及静态代理
spring boot·后端·spring
咸甜适中1 小时前
go语言gui窗口应用之fyne框架-动态添加、删除一行控件(逐行注释)
开发语言·后端·golang
梁雨珈1 小时前
Groovy语言的安全开发
开发语言·后端·golang
十二同学啊2 小时前
Spring Boot 中的 InitializingBean:Bean 初始化背后的故事
java·spring boot·后端
沈霁晨2 小时前
Perl语言的语法糖
开发语言·后端·golang
DevOpsDojo3 小时前
HTML语言的数据结构
开发语言·后端·golang
谦行3 小时前
前端视角 Java Web 入门手册 1.3:Java 世界的规则
java·后端
时韵瑶3 小时前
Scala语言的云计算
开发语言·后端·golang
Jerry Lau4 小时前
大模型-本地化部署调用--基于ollama+openWebUI+springBoot
java·spring boot·后端·llama