1. 发现问题




springboot整合rabbitmq做削峰处理_System

流量洪峰



举个栗子:

双11、618购物节,会出现特定时间点抢购某种商品的情况,瞬时流量巨大。

用户端发起下单操作,服务端包括库存检查,库存冻结,余额检查,余额冻结,订单生成,余额扣减,库存扣减,生成流水,余额解冻,库存解冻等业务逻辑。

假设20000用户端抢购1每秒内抢购100件商品,服务端接收到20000个请求,但每秒只能处理2000个请求,很有可能导致服务端系统被压垮,引发雪崩。

为了避免雪崩,常见的优化方案为削峰限流,平滑流量请求,保护服务器资源,一般使用队列缓冲,限速执行。




springboot整合rabbitmq做削峰处理_服务质量_02

排队处理业务



2.RabbitMQ 削峰




springboot整合rabbitmq做削峰处理_限流_03

加入MQ队列



RabbitMQ提供了一种服务质量保障功能,将消息设置为手动确认,如果一定数目的消息未被确认,不进行消费新的消息。

意思就是说,业务员为第一个人(0001号)办理业务,确认办完后,再请0002号接着办理。没有办理完上个业务,不呼叫下一个客户。

// 服务质量保证:// prefetchSize=0:表示消息内容大小没有限制,// prefetchCount=3:表示预读取消息数量为3,如果这三条消息均没有确认,则消费者不再读取新消息// global=false:表示prefetchCount单独应用于信道上的每个新消费者channel.basicQos(0,3,false);




springboot整合rabbitmq做削峰处理_rabbitmq接口异常函数方法_04

削峰设置



// 服务质量保证:// prefetchSize=0:表示消息内容大小没有限制,// prefetchCount=3:表示预读取消息数量为3,如果这三条消息均没有确认,则消费者不再读取新消息// global=false:表示prefetchCount单独应用于信道上的每个新消费者channel.basicQos(0,3,false);
//不自动回复队列应答 -- RabbitMQ 中的消息确认机制,// 限流方式// queue:队列名称// autoAck=false:不自动确认,消费者接收到消息并处理完逻辑后需要手动确认,// 如果没有进行手动确认,通道内的消息到达basicQos设置的prefetchCount数量后便不再消费// Consumer回调:MyConsumerchannel.basicConsume(queueName,false,new MyConsumer(channel));

消费者代码

import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;import com.rabbitmq.client.QueueingConsumer;import com.rabbitmq.client.QueueingConsumer.Delivery;public class Consumer {    public static void main(String[] args) throws Exception {        // 创建连接工厂       ConnectionFactory connectionFactory = new ConnectionFactory();        // 设置 RabbitMQ 地址        connectionFactory.setHost("127.0.0.1");        connectionFactory.setPort(5672);        connectionFactory.setVirtualHost("/");        connectionFactory.setUsername("admin");        connectionFactory.setPassword("123456");        // 创建一个新的连接        Connection connection = connectionFactory.newConnection();        // 创建一个新的Channel        Channel channel = connection.createChannel();        String exchangeName = "test_qos_exchange";        String queueName = "test_qos_queue";        String routingKey = "qos.#";        channel.exchangeDeclare(exchangeName, "topic", true, false, null);        // 声明要关注的队列        channel.queueDeclare(queueName, true, false, false, null);        channel.queueBind(queueName, exchangeName, routingKey);        // 服务质量保证:        // prefetchSize=0:表示消息内容大小没有限制,        // prefetchCount=3:表示预读取消息数量为3,        // 如果这三条消息均没有确认,则消费者不再读取新消息        // global=false:表示prefetchCount单独应用于信道上的每个新消费者        channel.basicQos(0,3,false);        // 不自动回复队列应答 -- RabbitMQ 中的消息确认机制,        // 限流方式        // queue:队列名称        // autoAck=false:不自动确认,消费者接收到消息后,处理完业务逻辑后需要手动确认,        // 如果没有进行手动确认,通道内的消息到达basicQos设置的prefetchCount数量后便不再消费        // Consumer回调:MyConsumer        channel.basicConsume(queueName,false,new MyConsumer(channel));    }}

自定义消费者代码

import com.rabbitmq.client.AMQP;import com.rabbitmq.client.Channel;import com.rabbitmq.client.DefaultConsumer;import com.rabbitmq.client.Envelope;import java.io.IOException;/** * DefaultConsumer类 实现了 Consumer 接口, * 通过传入一个通道, 告诉服务器我们需要哪个通道的消息 * 如果通道中有消息, 就会执行回调函数 handleDelivery */public class MyConsumer extends DefaultConsumer {    private Channel channel ;    public MyConsumer(Channel channel) {        super(channel);        this.channel = channel;    }    @Override    public void handleDelivery(String consumerTag, Envelope envelope,                               AMQP.BasicProperties properties, byte[] body) throws IOException {        System.err.println("-----------consume message----------");        System.err.println("consumerTag: " + consumerTag);        System.err.println("envelope: " + envelope);        System.err.println("properties: " + properties);        System.err.println("body: " + new String(body));        //当我们需要确认一条消息已经被消费时,我们调用的 basicAck 方法        //第一个参数:deliveryTag(唯一标识 ID),当一个消费者向 RabbitMQ 注册后,        // 会建立起一个 Channel ,        // RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag,        // 以便 Consumer 可以在消息确认时告诉 RabbitMQ 到底是哪条消息被确认了。        // delivery tag 是一个单调递增的正整数,其范围仅限于当前 Channel,        // RabbitMQ 保证在每个信道中每条消息的 Delivery Tag 从 1 开始递增。        //第二个参数:multiple:为了减少网络流量,手动确认可以被批处理,        // 当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息        channel.basicAck(envelope.getDeliveryTag(), false);    }}

生产者代码

public class Producer {    public static void main(String[] args) throws Exception {        ConnectionFactory connectionFactory = new ConnectionFactory();        connectionFactory.setHost("127.0.0.1");        connectionFactory.setPort(5672);        connectionFactory.setVirtualHost("/");        connectionFactory.setUsername("admin");        // 创建一个新的连接        Connection connection = connectionFactory.newConnection();        // 创建一个新的通道Channel        Channel channel = connection.createChannel();        String exchange = "test_qos_exchange";        String routingKey = "qos.save";        String msg = "Hello RabbitMQ QOS Message";        for(int i =0; i<5; i ++){            channel.basicPublish(exchange, routingKey, true, null, msg.getBytes());        }    }}