rabbitMQ学习记录
分布式系统通信的两种方式: 1 远程调用,2 借助消息队列
消息队列的优势: 1 应用解耦: 情况1:如果A系统直接调B,C系统,如果B/C系统报错,对A系统也有影响;情况2:如果要加个D系统,对于直接调用的方式需要修改A系统的代码,对于用消息队列的方式不用改A系统代码,D系统直接从消息队列取消息就行.
2:削峰填谷:MQ能处理的最大并发数远高于应用程序,可以将请求保存到MQ中,让消费者慢慢消费.
消息队列的劣势: 1 引入外部依赖,系统可靠性降低 2 复杂度上升,如何保证消费的顺序性,消息不丢失,不重复消费,消息一致性
总结:什么情况下适合用MQ: 1 A系统调B系统,如果需要B系统的反馈,则不能用MQ. 2
MQ工作模式
* 队列模式:
多个消费者之间是竞争关系,一条消息只能被一个消费者消费
队列模式代码就是在简单模式基础上再启动一个消费者
* 订阅模式:
生产者代码:
package com.seeyii.web.yk.pubSub; import com.rabbitmq.client.BuiltinExchangeType; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @PackageName:com.seeyii.web.yk * @Description: * @Author 杨坤 * @Date:2022/6/28 19:38 */ public class Producer { public static void main(String[] args) throws IOException, TimeoutException { //1 创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //2 设置参数 factory.setHost("192.168.xx.xx");// ip,默认localhost factory.setPort(5672);//端口,默认5672 factory.setVirtualHost("/yk");//虚拟机,默认值 / factory.setUsername("username"); //默认值 guest factory.setPassword("password"); //默认值 guest //3 创建连接 Connection connection = factory.newConnection(); //4 创建channel Channel channel = connection.createChannel(); // 创建交换机 //String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, Map<String, Object> arguments /* exchange: 交换机名 type:交换机类型: direct(定向),fanout(广播,向所有绑定的队列发消息),topic(通配符方式),headers(参数匹配) durable: 持久化 autoDelete: 自动删除 arguments:参数 */ String exchange_name = "fanout_exchange"; channel.exchangeDeclare(exchange_name, BuiltinExchangeType.FANOUT,true,false,null); //channel和交换机绑定 // 5 创建两个队列 String queue_name1="fanout_queue1"; String queue_name2="fanout_queue2"; /* queue: 对列名, durable:是否持久化,当mq重启后是否还在 exclusive:是否排他, * 如果排他,只能有一个消费者监听这个队列,exclusive为true时,concurrency必须是1,concurrency是消费者创建几个线程去消费。
* 当连接关闭时是否删除队列 autoDelete:当没有连接时,是否自动删除 arguments: 参数 **/ //如果没有这个名字的队列则会创建,如果有就不创建 channel.queueDeclare(queue_name1,true,false,false,null); channel.queueDeclare(queue_name2,true,false,false,null); //绑定交换机和队列 //String queue, String exchange, String routingKey /*queue:对列名 exchange:交换机名 routingKey:路由键名 */ channel.queueBind(queue_name1,exchange_name,"");//如果交换机类型是广播,路由键就是空串 channel.queueBind(queue_name2,exchange_name,""); //6 发消息 /* exchange:交换机名称,简单模式下默认空串 routingKey:路由键,简单模式下就是channel名 BasicProperties props: 配置信息, byte[] body:发送的消息数据 **/ String body="hello,rabbitmq+++++"; channel.basicPublish("","hello_world",null,body.getBytes()); //7 释放资源 if(channel!=null){ channel.close(); } if(connection!=null){ connection.close(); } } }
消费者1代码:
package com.seeyii.web.yk.pubSub; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @PackageName:com.seeyii.web.yk * @Description: * @Author 杨坤 * @Date:2022/6/28 19:38 */ public class Consumer1 { public static void main(String[] args) throws IOException, TimeoutException { //1 创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //2 设置参数 factory.setHost("192.168..");// ip,默认localhost factory.setPort(5672);//端口,默认5672 factory.setVirtualHost("/yk");//虚拟机,默认值 / factory.setUsername(""); //默认值 guest factory.setPassword(""); //默认值 guest //3 创建连接 Connection connection = factory.newConnection(); //4 创建channel Channel channel = connection.createChannel(); // 5 创建队列,生产者已经创建过队列,消费者不用再创建 //6 接收消息 /* String queue:对列名称, boolean autoAck:是否自动确认, Consumer callback:回调对象 **/ Consumer consumer=new DefaultConsumer(channel){ //回调方法: 当收到消息后自动执行该方法 /*consumerTag:消息标识 envelope获取交换机,路由键的信息 properties:配置信息 body: 消息*/ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); // System.out.println("consumerTag:"+consumerTag); // System.out.println("envelope-Exchange:"+envelope.getExchange()); // System.out.println("envelope-RoutingKey:"+envelope.getRoutingKey()); // System.out.println("properties:"+properties); System.out.println("消费者1收到消息: body:"+new String(body)); } }; channel.basicConsume("fanout_queue1",true,consumer); //消费者不要关闭连接资源 } }
消费者2代码:
package com.seeyii.web.yk.pubSub; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @PackageName:com.seeyii.web.yk * @Description: * @Author 杨坤 * @Date:2022/6/28 19:38 */ public class Consumer2 { public static void main(String[] args) throws IOException, TimeoutException { //1 创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //2 设置参数 factory.setHost("192.168..");// ip,默认localhost factory.setPort(5672);//端口,默认5672 factory.setVirtualHost("/yk");//虚拟机,默认值 / factory.setUsername(""); //默认值 guest factory.setPassword(""); //默认值 guest //3 创建连接 Connection connection = factory.newConnection(); //4 创建channel Channel channel = connection.createChannel(); // 5 创建队列,生产者已经创建过队列,消费者不用再创建//6 接收消息 /* String queue:对列名称, boolean autoAck:是否自动确认, Consumer callback:回调对象 **/ Consumer consumer=new DefaultConsumer(channel){ //回调方法: 当收到消息后自动执行该方法 /*consumerTag:消息标识 envelope获取交换机,路由键的信息 properties:配置信息 body: 消息*/ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); // System.out.println("consumerTag:"+consumerTag); // System.out.println("envelope-Exchange:"+envelope.getExchange()); // System.out.println("envelope-RoutingKey:"+envelope.getRoutingKey()); // System.out.println("properties:"+properties); System.out.println("消费者2收到消息: body:"+new String(body)); } }; channel.basicConsume("fanout_queue2",true,consumer); //消费者不要关闭连接资源 } }
路由模式:
生产者代码:
package com.seeyii.web.yk.routingKey; import com.rabbitmq.client.BuiltinExchangeType; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @PackageName:com.seeyii.web.yk * @Description: * @Author 杨坤 * @Date:2022/6/28 19:38 */ public class Producer { public static void main(String[] args) throws IOException, TimeoutException { //1 创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //2 设置参数 factory.setHost("192.168..");// ip,默认localhost factory.setPort(5672);//端口,默认5672 factory.setVirtualHost("/yk");//虚拟机,默认值 / factory.setUsername(""); //默认值 guest factory.setPassword(""); //默认值 guest //3 创建连接 Connection connection = factory.newConnection(); //4 创建channel Channel channel = connection.createChannel(); // 创建交换机 //String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, Map<String, Object> arguments /* exchange: 交换机名 type:交换机类型: direct(定向),fanout(广播,向所有绑定的队列发消息),topic(通配符方式),headers(参数匹配) durable: 持久化 autoDelete: 自动删除 arguments:参数 */ String exchange_name = "direct_exchange"; channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT,true,false,null); //channel和交换机绑定 // 5 创建两个队列 String queue_name1="direct_queue1"; String queue_name2="direct_queue2"; /* queue: 对列名, durable:是否持久化,当mq重启后是否还在 exclusive:是否排他, * 如果排他,只能有一个消费者监听这个队列 * 当连接关闭时是否删除队列 autoDelete:当没有连接时,是否自动删除 arguments: 参数 **/ //如果没有这个名字的队列则会创建,如果有就不创建 channel.queueDeclare(queue_name1,true,false,false,null); channel.queueDeclare(queue_name2,true,false,false,null); //绑定交换机和队列 //String queue, String exchange, String routingKey /*queue:对列名 exchange:交换机名 routingKey:路由键名 */ //队列1绑定1个路由键,队列2绑定三个路由键 channel.queueBind(queue_name1,exchange_name,"error"); channel.queueBind(queue_name2,exchange_name,"info"); channel.queueBind(queue_name2,exchange_name,"warning"); channel.queueBind(queue_name2,exchange_name,"error"); //6 发消息 /* exchange:交换机名称,简单模式下默认空串 routingKey:路由键,简单模式下就是channel名 BasicProperties props: 配置信息, byte[] body:发送的消息数据 **/ String body="向direct类型交换机发数据,routingKey是info,交换机绑定两个队列"; channel.basicPublish(exchange_name,"info",null,body.getBytes()); //7 释放资源 if(channel!=null){ channel.close(); } if(connection!=null){ connection.close(); } } }
消费者代码:
package com.seeyii.web.yk.routingKey; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @PackageName:com.seeyii.web.yk * @Description: * @Author 杨坤 * @Date:2022/6/28 19:38 */ public class Consumer1 { public static void main(String[] args) throws IOException, TimeoutException { //1 创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //2 设置参数 factory.setHost("192.168..");// ip,默认localhost factory.setPort(5672);//端口,默认5672 factory.setVirtualHost("/yk");//虚拟机,默认值 / factory.setUsername(""); //默认值 guest factory.setPassword(""); //默认值 guest //3 创建连接 Connection connection = factory.newConnection(); //4 创建channel Channel channel = connection.createChannel(); // 5 创建队列,生产者已经创建过队列,消费者不用再创建 /* queue: 对列名, durable:是否持久化,当mq重启后是否还在 exclusive:是否排他, * 如果排他,只能有一个消费者监听这个队列 * 当连接关闭时是否删除队列 autoDelete:当没有连接时,是否自动删除 arguments: 参数 **/ //如果没有这个名字的队列则会创建,如果有就不创建 // channel.queueDeclare("hello_world",true,false,false,null); //6 接收消息 /* String queue:对列名称, boolean autoAck:是否自动确认, Consumer callback:回调对象 **/ Consumer consumer=new DefaultConsumer(channel){ //回调方法: 当收到消息后自动执行该方法 /*consumerTag:消息标识 envelope获取交换机,路由键的信息 properties:配置信息 body: 消息*/ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); System.out.println("消费者1收到消息: body:"+new String(body)); } }; channel.basicConsume("direct_queue1",true,consumer); //消费者不要关闭连接资源 } }
package com.seeyii.web.yk.routingKey; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @PackageName:com.seeyii.web.yk * @Description: * @Author 杨坤 * @Date:2022/6/28 19:38 */ public class Consumer2 { public static void main(String[] args) throws IOException, TimeoutException { //1 创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //2 设置参数 factory.setHost("192.168..");// ip,默认localhost factory.setPort(5672);//端口,默认5672 factory.setVirtualHost("/yk");//虚拟机,默认值 / factory.setUsername(""); //默认值 guest factory.setPassword(""); //默认值 guest //3 创建连接 Connection connection = factory.newConnection(); //4 创建channel Channel channel = connection.createChannel(); // 5 创建队列,生产者已经创建过队列,消费者不用再创建 /* queue: 对列名, durable:是否持久化,当mq重启后是否还在 exclusive:是否排他, * 如果排他,只能有一个消费者监听这个队列 * 当连接关闭时是否删除队列 autoDelete:当没有连接时,是否自动删除 arguments: 参数 **/ //如果没有这个名字的队列则会创建,如果有就不创建 // channel.queueDeclare("hello_world",true,false,false,null); //6 接收消息 /* String queue:对列名称, boolean autoAck:是否自动确认, Consumer callback:回调对象 **/ Consumer consumer=new DefaultConsumer(channel){ //回调方法: 当收到消息后自动执行该方法 /*consumerTag:消息标识 envelope获取交换机,路由键的信息 properties:配置信息 body: 消息*/ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); System.out.println("消费者2收到消息: body:"+new String(body)); } }; channel.basicConsume("direct_queue2",true,consumer); //消费者不要关闭连接资源 } }
主题模式:
routingKey里使用通配符,*代表一个单词,#代表0或多个单词,代码和发布订阅模式基本相同
Springboot整合rabbitMQ代码:
配置文件:
spring: rabbitmq: host: 192.168.xx.xx port: 5672 username: xx password: xx
配置类:
package com.seeyii.yk.boot; import org.springframework.amqp.core.*; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BootConfig { public static final String EXCHANGE_NAME="boot_topic_exchange"; public static final String QUEUE_NAME="boot_queue"; // 1 创建交换机 @Bean("bootExchange") public Exchange bootExchange(){ return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build(); } // 2 创建队列 @Bean("bootQueue") public Queue bootQueue(){ return QueueBuilder.durable(QUEUE_NAME).build(); } // 3 队列和交换机绑定 @Bean public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange){ return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs(); } }
生产者:
package com.seeyii.yk.boot; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/bootProducer") public class ProducerBoot { @Autowired private RabbitTemplate rabbitTemplate; @PostMapping("/testSendMQ") public String testSendMQ() throws InterruptedException { rabbitTemplate.convertAndSend(BootConfig.EXCHANGE_NAME,"boot.haha","boot,MQ,hello"); return "success"; } }
消费者:
package com.seeyii.yk.boot; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class ConsumerBoot { @RabbitListener(queues = BootConfig.QUEUE_NAME) public void ListenerQueue(Message message){ System.out.println("收到消息: "+new String(message.getBody())); } }
rabbitMQ如何保证消息可靠投递:
rabbitMQ整个消息投递路径为: producer->exchange->queue->consumer
消息从producer到exchange会返回一个ConfirmCallback
消息从exchange到queue会返回returnCallback(经验来看一般returnCallBack出错都是routingkey配错了,只要打印routingKey就好),利用这两个callback保证消息可靠性
自定义连接工厂,开启这两个回调函数
@Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); connectionFactory.setHost(host); connectionFactory.setPort(port); connectionFactory.setUsername(userName); connectionFactory.setPassword(passWord); connectionFactory.setVirtualHost("/");
//ConfirmCallback设置 connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
//returnCallback设置 connectionFactory.setPublisherReturns(true); return connectionFactory; }
ConfirmCallback和returnCallback:
package com.seeyii.yk.boot; import com.rabbitmq.client.ConfirmCallback; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/bootProducer") public class ProducerBoot { @Autowired private RabbitTemplate rabbitTemplate; @Autowired ApplicationContext applicationContext; @PostMapping("/testConfirm") public String testConfirm() throws InterruptedException { // 1 在连接工厂里开启Confirm //2 定义回调函数 rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { /*CorrelationData: 相关配置信息 ack:交换机是否成功收到信息 cause: 失败原因*/ @Override public void confirm(CorrelationData correlationData, boolean b, String s) { System.out.println("confirm方法执行了"); if(b){ //接收成功 System.out.println("接收成功:"+s); }else{ //接收失败 System.out.println("接收失败:"+s); //让消息再次发送 } } }); rabbitTemplate.convertAndSend(BootConfig.EXCHANGE_NAME,"boot.haha","boot,MQ,hello"); return "success"; } @PostMapping("/testCallback") public String testCallback() throws InterruptedException { //1 连接工厂里设置开启Callback //2 设置交换机处理失败消息的模式 rabbitTemplate.setMandatory(true); //3 定义回调函数 rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() { /*message:消息对象 replayCode:错误码 replayText:错误信息 exchange:交换机 routingKey:路由键*/ @Override public void returnedMessage(Message message, int i, String s, String s1, String s2) { System.out.println("return callback执行了"); System.out.println(message); System.out.println(i); System.out.println(s); System.out.println(s1); System.out.println(s2); } }); rabbitTemplate.convertAndSend(BootConfig.EXCHANGE_NAME,"boott.haha","boot,MQ,hello"); return "success"; } }
消费端收到消息的确认方式: ack
自动ack: acknowledge=none ,消费者收到消息后不管后面的处理逻辑是否成功都会给brocker发确认消息
手动ack:acknowledge=manual,代码控制何时发确认消息,可以在业务逻辑完成后再发
如果设置了手动ack,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息.
消费者代码:
package com.seeyii.yk.boot; import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class ConsumerBoot { @RabbitListener(queues = BootConfig.QUEUE_NAME,ackMode = "MANUAL",concurrency = "4") public void ListenerQueue(Message message, String data, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throws IOException { System.out.println("收到消息: "+new String(message.getBody())); System.out.println("data: " + data); if (data.contains("boot")) { // RabbitMQ的ack机制中,第二个参数返回true,表示需要将这条消息投递给其他的消费者重新消费 channel.basicAck(deliveryTag, false); } else { // 第三个参数true,表示这个消息会重新进入队列 channel.basicNack(deliveryTag, false, true); } } }
总结:如何保证消息可靠性
1 持久化,包括消息持久化,交换机持久化,队列持久化
2 生产方确认confirmCallback,returnCallback,消费方手动ack
3 broker集群高可用
消费端限流:
1 配置文件中配置 RabbitListenerContainerFactory
@Bean public RabbitListenerContainerFactory rabbitListenerContainerFactory2(ConnectionFactory connectionFactory){ SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setPrefetchCount(2);//表示消费者每次从mq中拉取两条消息消费,直到手动确认消费完毕后,才会再拉取消息 return factory; }
2 消费者 @RabbitListener 注解里指定containerFactory,并设置手动ack
package com.seeyii.yk.boot; import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class ConsumerBoot { @RabbitListener(queues = BootConfig.QUEUE_NAME,ackMode = "MANUAL",concurrency = "2",containerFactory = "rabbitListenerContainerFactory2") public void ListenerQueue(Message message, String data, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throws IOException { System.out.println("收到消息: "+new String(message.getBody())); System.out.println("deliveryTag: " + deliveryTag); System.out.println("data: " + data); if (data.contains("boot")) { System.out.println("业务逻辑处理完成"); // RabbitMQ的ack机制中,第二个参数返回true,表示需要将这条消息投递给其他的消费者重新消费 channel.basicAck(deliveryTag, false); } else { // 第三个参数true,表示这个消息会重新进入队列 channel.basicNack(deliveryTag, false, true); } } }
TTL:过期时间,可以给队列设置过期时间,到期后队列里所有消息会被清空,也可以单独
设置消息过期时间
MessageProperties messageProperties = new MessageProperties(); //设置消息的过期时间3s messageProperties.setExpiration("3000"); Message message = new Message("boot,hello".getBytes(),messageProperties); rabbitTemplate.convertAndSend(BootConfig.EXCHANGE_NAME,"boot.haha",message);
设置队列过期时间
@Bean("bootQueue") public Queue bootQueue(){ return QueueBuilder.durable(QUEUE_NAME).ttl(5000).build(); }
死信队列:
消息成为死信的三种情况: 1:队列长度或数据大小超过限制.2消费者拒绝接收消息(basicNack),并且不把消息重新放回原目标队列.3 队列设置了超时时间,消息达到时间未被消费
设置死信队列代码
// 1 创建死信交换机 @Bean("deadExchange") public Exchange deadExchange(){ return ExchangeBuilder.topicExchange("dead_exchange").durable(true).build(); } // 3 死信队列和死信交换机绑定 @Bean public Binding deadBindQueueExchange(@Qualifier("deadQueue") Queue queue, @Qualifier("deadExchange") Exchange exchange){ return BindingBuilder.bind(queue).to(exchange).with("dead.#").noargs(); } // 2 创建队列,并绑定死信交换机和路由键 @Bean("bootQueue") public Queue bootQueue(){ return QueueBuilder.durable(QUEUE_NAME).deadLetterExchange("dead_exchange").deadLetterRoutingKey("dead.test").build(); }
队列过期时间(TTL)+死信队列实现延时队列
消息队列如何解决消息积压:消息积压基本都是因为消费者消费的慢导致的,基本不可能是生产者和mq本身问题,除非大促这种短时间内突然生产出大量消息,在程序设计之初就要保证消费者消费速度比生产者速度快,如果消息积压,排查生产者代码的方向:
是否在某个资源上卡住,是否有死锁,是否因为消息消费失败导致重复消费。解决方法还是先对消费者扩容,再排查代码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理