1. 发现问题
流量洪峰
举个栗子:
双11、618购物节,会出现特定时间点抢购某种商品的情况,瞬时流量巨大。
用户端发起下单操作,服务端包括库存检查,库存冻结,余额检查,余额冻结,订单生成,余额扣减,库存扣减,生成流水,余额解冻,库存解冻等业务逻辑。
假设20000用户端抢购1每秒内抢购100件商品,服务端接收到20000个请求,但每秒只能处理2000个请求,很有可能导致服务端系统被压垮,引发雪崩。
为了避免雪崩,常见的优化方案为削峰限流,平滑流量请求,保护服务器资源,一般使用队列缓冲,限速执行。
排队处理业务
2.RabbitMQ 削峰
加入MQ队列
RabbitMQ提供了一种服务质量保障功能,将消息设置为手动确认,如果一定数目的消息未被确认,不进行消费新的消息。
意思就是说,业务员为第一个人(0001号)办理业务,确认办完后,再请0002号接着办理。没有办理完上个业务,不呼叫下一个客户。
// 服务质量保证:// prefetchSize=0:表示消息内容大小没有限制,// prefetchCount=3:表示预读取消息数量为3,如果这三条消息均没有确认,则消费者不再读取新消息// global=false:表示prefetchCount单独应用于信道上的每个新消费者channel.basicQos(0,3,false);
削峰设置
// 服务质量保证:// 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()); } }}