RabbitMQ消费者流量控制策略总结

一、简介

在实际项目中使用RabbitMQ的时候,由于消费者(接收消息的一端)自身处理消息的效率并不高,如果说这时候生产者还是不断地在生产消息,一直推送消息到消费者,那么很容易引起消费者的宕机。rabbitmq提供了一个限流机制,用于限制一次性推送到消费者客户端的消息数量,让消费者都处理完了消息之后,生产者再推送新的消息过来。

二、限流的原因

出于以下两个方面,所以需要对消费者进行一些限流策略。

【a】假设某个时候,在RabbitMQ队列中已经堆积了非常非常多的消息,这个时候,如果有一个消费者启动,那么大量的消息将会一起推送到这个消费者上面,这种瞬间超大流量,很有可能导致服务器崩溃。

【b】生产者生产消息的效率比消费者处理消息的效率高很多,两端之间的这种效率不平衡性。所以消费端需要做一些限流措施,否则可能导致消费端性能下降,服务器卡顿甚至崩溃等现象。

下面,通过一个简单的示例说明在rabbitmq中如何对消费者进行限流,保证消费者客户端的稳定。

 

三、消费者限流使用方法

限制流量相关API:

public void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException {
        this.exnWrappingRpc(new Qos(prefetchSize, prefetchCount, global));
    }

public void basicQos(int prefetchCount, boolean global) throws IOException {
        this.basicQos(0, prefetchCount, global);
    }

public void basicQos(int prefetchCount) throws IOException {
        this.basicQos(0, prefetchCount, false);
    }

【a】pom.xml:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

【b】application.yml配置文件:

server:
  port: 1211
spring:
  application:
    name: mq-message-consumer-limiting
  rabbitmq:
    host: 127.0.0.1
    virtual-host: /vhost
    username: wsh
    password: wsh
    port: 5672
    connection-timeout: 10000
    template:
      mandatory: true
    #手动ACK
    listener:
      simple:
        acknowledge-mode: manual

这里需要注意的是:必须设置spring.rabbitmq.listener.simple.acknowledge-mode = manual,否则basicQos不生效。

【c】RabbitMQ配置:

/**
 * @Description: Rabbitmq 配置类
 * @Author: weixiaohuai
 * @Date: 2019/7/10
 * @Time: 20:43
 */
@Component
public class RabbitMQConfig {
    private static final String QUEUE_NAME = "consumer_limit_queue_name";
    private static final String EXCHANGE_NAME = "consumer_limit_exchange_name";
    private static final String ROUTING_KEY = "user.#";

    @Bean
    public Queue queue() {
        return new Queue(QUEUE_NAME);
    }

    @Bean
    public TopicExchange exchange() {
        return new TopicExchange(EXCHANGE_NAME);
    }

    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(exchange()).with(ROUTING_KEY);
    }

}

【d】生产者:这个跟之前的没啥区别,就是一个普通的topic示例。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * @Description: 生产者
 * @Author: weixiaohuai
 * @Date: 2019/7/10
 * @Time: 20:42
 */
@Component
public class Producer {
    private static final Logger logger = LoggerFactory.getLogger(Producer.class);
    private static final String EXCHANGE_NAME = "consumer_limit_exchange_name";
    private static final String ROUTING_KEY = "user.add";

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessage() {
        for (int i = 1; i <= 10; i++) {
            String message = "消息" + i;
            logger.info("【生产者】发送消息:" + message);
            rabbitTemplate.convertAndSend(EXCHANGE_NAME, ROUTING_KEY, message, new CorrelationData(UUID.randomUUID().toString()));
        }
    }

}

【e】消费者:

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @Description: 消费者
 * @Author: weixiaohuai
 * @Date: 2019/7/10
 * @Time: 20:42
 */
@Component
public class Consumer {
    private static final Logger logger = LoggerFactory.getLogger(Consumer.class);
    private static final String QUEUE_NAME = "consumer_limit_queue_name";

    @RabbitListener(queues = QUEUE_NAME)
    public void receiveMessage(String msg, Message message, Channel channel) {
        try {
            //一次最多能处理多少条消息
            channel.basicQos(1);

            logger.info("【Consumer  receiveMessage】接收到消息为:[{}]", msg);
//            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            logger.info("【Consumer  receiveMessage】接收消息后的处理发生异常", e);
        }
    }

}

这里需要注意的是: channel.basicQos(1); 一次只能接受一条消息,就是说如果我这一条消息没有发送ack应答给broker,rabbitmq就认为这条消息还没处理完,不会推送新的消息过来。

【f】测试用例


@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootRabbitmqConsumerlimitingApplicationTests {

    @Autowired
    private Producer producer;

    @Test
    public void contextLoads() {
        producer.sendMessage();
    }

}

【g】启动项目,暂时先注释掉consumer消费者的basicAck发送应答方法:

运行测试用例,让发送者发送十条消息到队列中,因为我们没有basicAck,rabbitmq认为我们一条消息都还没处理完,所以不会推送新消息过来。这个可以从管理控制台看出:

这时候,放开basicAck确认,消费者就会接收到其他消息,如下图所示:

四、总结

以上通过一个示例,简单地说明了RabbitMQ消费端的限流策略,从某种意义上说,消费者的限流策略有助于那么处理消息效率高的消费者多消费一些消息,效率低一些的消费者少推送一些消息,从而可以达到能者多劳的目的,尽可能发挥消费者处理消息的能力。在项目中,为了缓解生产者和消费者两边效率不平衡的影响,通常会对消费者进行限流处理,保证消费者端正常消费消息,尽可能避免服务器崩溃以及宕机现象。

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值